blob: 3bc0f0b5ca0995910e0e517b545b5fb9af626a8b [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);
Jakub Narebski3962f1d72010-11-09 19:27:54 +010020use Time::HiRes qw(gettimeofday tv_interval);
Kay Sievers10bb9032005-11-23 04:26:40 +010021binmode STDOUT, ':utf8';
Kay Sievers161332a2005-08-07 19:49:46 +020022
Jakub Narebski3962f1d72010-11-09 19:27:54 +010023our $t0 = [ gettimeofday() ];
Jakub Narebskiaa7dd052009-09-01 13:39:16 +020024our $number_of_git_cmds = 0;
25
Jakub Narebskib1f5f642006-12-28 00:00:52 +010026BEGIN {
Jakub Narebski3be8e722007-04-01 22:22:21 +020027 CGI->compile() if $ENV{'MOD_PERL'};
Jakub Narebskib1f5f642006-12-28 00:00:52 +010028}
29
Junio C Hamano06c084d2006-08-02 13:50:20 -070030our $version = "++GIT_VERSION++";
Kay Sievers44ad2972005-08-07 19:59:24 +020031
Jakub Narebskic2394fe2010-05-07 14:54:04 +020032our ($my_url, $my_uri, $base_url, $path_info, $home_link);
33sub evaluate_uri {
34 our $cgi;
Giuseppe Bilotta81d3fe92009-02-15 10:18:36 +010035
Jakub Narebskic2394fe2010-05-07 14:54:04 +020036 our $my_url = $cgi->url();
37 our $my_uri = $cgi->url(-absolute => 1);
38
39 # Base URL for relative URLs in gitweb ($logo, $favicon, ...),
40 # needed and used only for URLs with nonempty PATH_INFO
41 our $base_url = $my_url;
42
43 # When the script is used as DirectoryIndex, the URL does not contain the name
44 # of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
45 # have to do it ourselves. We make $path_info global because it's also used
46 # later on.
47 #
48 # Another issue with the script being the DirectoryIndex is that the resulting
49 # $my_url data is not the full script URL: this is good, because we want
50 # generated links to keep implying the script name if it wasn't explicitly
51 # indicated in the URL we're handling, but it means that $my_url cannot be used
52 # as base URL.
53 # Therefore, if we needed to strip PATH_INFO, then we know that we have
54 # to build the base URL ourselves:
Jakub Narebski84d9e2d2012-02-03 13:44:54 +010055 our $path_info = decode_utf8($ENV{"PATH_INFO"});
Jakub Narebskic2394fe2010-05-07 14:54:04 +020056 if ($path_info) {
Jay Soffiancacfc092012-08-08 22:29:26 -040057 # $path_info has already been URL-decoded by the web server, but
58 # $my_url and $my_uri have not. URL-decode them so we can properly
59 # strip $path_info.
60 $my_url = unescape($my_url);
61 $my_uri = unescape($my_uri);
Jakub Narebskic2394fe2010-05-07 14:54:04 +020062 if ($my_url =~ s,\Q$path_info\E$,, &&
63 $my_uri =~ s,\Q$path_info\E$,, &&
64 defined $ENV{'SCRIPT_NAME'}) {
65 $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
66 }
Giuseppe Bilotta81d3fe92009-02-15 10:18:36 +010067 }
Jakub Narebskic2394fe2010-05-07 14:54:04 +020068
69 # target of the home link on top of all pages
70 our $home_link = $my_uri || "/";
Giuseppe Bilottab65910f2008-09-29 15:07:42 +020071}
72
Alp Tokere130dda2006-07-12 23:55:10 +010073# core git executable to use
74# this can just be "git" if your webserver has a sensible PATH
Junio C Hamano06c084d2006-08-02 13:50:20 -070075our $GIT = "++GIT_BINDIR++/git";
Jakub Narebski3f7f27102006-06-21 09:48:04 +020076
Kay Sieversb87d78d2005-08-07 20:21:04 +020077# absolute fs-path which will be prepended to the project path
Dennis Stosberg4a87b432006-06-21 15:07:08 +020078#our $projectroot = "/pub/scm";
Junio C Hamano06c084d2006-08-02 13:50:20 -070079our $projectroot = "++GITWEB_PROJECTROOT++";
Kay Sieversb87d78d2005-08-07 20:21:04 +020080
Luke Luca5e9492007-10-16 20:45:25 -070081# fs traversing limit for getting project list
82# the number is relative to the projectroot
83our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++";
84
Yasushi SHOJI2de21fa2006-08-15 07:50:49 +090085# string of the home link on top of all pages
86our $home_link_str = "++GITWEB_HOME_LINK_STR++";
87
Tony Finchad9c2e22013-07-04 18:02:12 +010088# extra breadcrumbs preceding the home link
89our @extra_breadcrumbs = ();
90
Alp Toker49da1da2006-07-11 21:10:26 +010091# name of your site or organization to appear in page titles
92# replace this with something more descriptive for clearer bookmarks
Petr Baudis8be28902006-10-24 05:18:39 +020093our $site_name = "++GITWEB_SITENAME++"
94 || ($ENV{'SERVER_NAME'} || "Untitled") . " Git";
Alp Toker49da1da2006-07-11 21:10:26 +010095
Lénaïc Huardc1355b72011-10-21 09:09:29 +020096# html snippet to include in the <head> section of each page
97our $site_html_head_string = "++GITWEB_SITE_HTML_HEAD_STRING++";
Alan Chandlerb2d34762006-10-03 13:49:03 +010098# filename of html text to include at top of each page
99our $site_header = "++GITWEB_SITE_HEADER++";
Kay Sievers8ab1da22005-08-07 20:22:53 +0200100# html text to include at home page
Junio C Hamano06c084d2006-08-02 13:50:20 -0700101our $home_text = "++GITWEB_HOMETEXT++";
Alan Chandlerb2d34762006-10-03 13:49:03 +0100102# filename of html text to include at bottom of each page
103our $site_footer = "++GITWEB_SITE_FOOTER++";
Kay Sievers8ab1da22005-08-07 20:22:53 +0200104
Alan Chandlerb2d34762006-10-03 13:49:03 +0100105# URI of stylesheets
106our @stylesheets = ("++GITWEB_CSS++");
Petr Baudis887a6122006-10-26 14:41:25 +0200107# URI of a single stylesheet, which can be overridden in GITWEB_CONFIG.
108our $stylesheet = undef;
Jakub Narebski9a7a62f2006-10-06 12:31:05 +0200109# URI of GIT logo (72x27 size)
Junio C Hamano06c084d2006-08-02 13:50:20 -0700110our $logo = "++GITWEB_LOGO++";
Jakub Narebski0b5deba2006-09-04 20:32:13 +0200111# URI of GIT favicon, assumed to be image/png type
112our $favicon = "++GITWEB_FAVICON++";
Jakub Narebski4af819d2009-09-01 13:39:17 +0200113# URI of gitweb.js (JavaScript code for gitweb)
114our $javascript = "++GITWEB_JS++";
Jakub Narebskiaedd9422006-06-17 11:23:56 +0200115
Jakub Narebski9a7a62f2006-10-06 12:31:05 +0200116# URI and label (title) of GIT logo link
117#our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/";
118#our $logo_label = "git documentation";
Wincent Colaiuta69fb8282009-07-12 14:31:28 +0200119our $logo_url = "http://git-scm.com/";
Jakub Narebski9a7a62f2006-10-06 12:31:05 +0200120our $logo_label = "git homepage";
Junio C Hamano51a7c662006-09-23 12:36:01 -0700121
Kay Sievers09bd7892005-08-07 20:21:23 +0200122# source of projects list
Junio C Hamano06c084d2006-08-02 13:50:20 -0700123our $projects_list = "++GITWEB_LIST++";
Kay Sieversb87d78d2005-08-07 20:21:04 +0200124
Michael Hendricks55feb122007-07-04 18:36:48 -0600125# the width (in characters) of the projects list "Description" column
126our $projects_list_description_width = 25;
127
Sebastien Ceveyd940c902011-04-29 19:52:01 +0200128# group projects by category on the projects list
129# (enabled if this variable evaluates to true)
130our $projects_list_group_categories = 0;
131
132# default category if none specified
133# (leave the empty string for no category)
134our $project_list_default_category = "";
135
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +0200136# default order of projects list
137# valid values are none, project, descr, owner, and age
138our $default_projects_order = "project";
139
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +0200140# show repository only if this file exists
141# (only effective if this variable evaluates to true)
142our $export_ok = "++GITWEB_EXPORT_OK++";
143
Kacper Kornet5710be42012-04-24 19:39:15 +0200144# don't generate age column on the projects list page
145our $omit_age_column = 0;
146
Kacper Kornet0ebe7822012-04-26 18:45:44 +0200147# don't generate information about owners of repositories
148our $omit_owner=0;
149
Alexander Gavrilovdd7f5f12008-11-06 01:36:23 +0300150# show repository only if this subroutine returns true
151# when given the path to the project, for example:
152# sub { return -e "$_[0]/git-daemon-export-ok"; }
153our $export_auth_hook = undef;
154
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +0200155# only allow viewing of repositories also shown on the overview page
156our $strict_export = "++GITWEB_STRICT_EXPORT++";
157
Jakub Narebski19a87212006-08-15 23:03:17 +0200158# list of git base URLs used for URL to where fetch project from,
159# i.e. full URL is "$git_base_url/$project"
Jakub Narebskid6b7e0b2006-10-26 12:26:44 +0200160our @git_base_url_list = grep { $_ ne '' } ("++GITWEB_BASE_URL++");
Jakub Narebski19a87212006-08-15 23:03:17 +0200161
Jakub Narebskif5aa79d2006-06-17 13:32:15 +0200162# default blob_plain mimetype and default charset for text/plain blob
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200163our $default_blob_plain_mimetype = 'text/plain';
164our $default_text_plain_charset = undef;
Jakub Narebskif5aa79d2006-06-17 13:32:15 +0200165
Petr Baudis2d007372006-06-18 00:01:06 +0200166# file to use for guessing MIME types before trying /etc/mime.types
167# (relative to the current git repository)
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200168our $mimetypes_file = undef;
Petr Baudis2d007372006-06-18 00:01:06 +0200169
Martin Koegler00f429a2007-06-03 17:42:44 +0200170# assume this charset if line contains non-UTF-8 characters;
171# it should be valid encoding (see Encoding::Supported(3pm) for list),
172# for which encoding all byte sequences are valid, for example
173# 'iso-8859-1' aka 'latin1' (it is decoded without checking, so it
174# could be even 'utf-8' for the old behavior)
175our $fallback_encoding = 'latin1';
176
Jakub Narebski69a9b412007-07-20 02:15:09 +0200177# rename detection options for git-diff and git-diff-tree
178# - default is '-M', with the cost proportional to
179# (number of removed files) * (number of new files).
180# - more costly is '-C' (which implies '-M'), with the cost proportional to
181# (number of changed files + number of removed files) * (number of new files)
182# - even more costly is '-C', '--find-copies-harder' with cost
183# (number of files in the original tree) * (number of new files)
184# - one might want to include '-B' option, e.g. '-B', '-M'
185our @diff_opts = ('-M'); # taken from git_commit
186
Matt McCutchen7e1100e2009-02-07 19:00:09 -0500187# Disables features that would allow repository owners to inject script into
188# the gitweb domain.
189our $prevent_xss = 0;
190
Christopher Wilson7ce896b2010-09-21 00:25:19 -0700191# Path to the highlight executable to use (must be the one from
192# http://www.andre-simon.de due to assumptions about parameters and output).
193# Useful if highlight is not installed on your webserver's PATH.
194# [Default: highlight]
195our $highlight_bin = "++HIGHLIGHT_BIN++";
196
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200197# information about snapshot formats that gitweb is capable of serving
198our %known_snapshot_formats = (
199 # name => {
200 # 'display' => display name,
201 # 'type' => mime type,
202 # 'suffix' => filename suffix,
203 # 'format' => --format for git-archive,
204 # 'compressor' => [compressor command and arguments]
Mark A Rada1bfd3632009-08-06 10:25:39 -0400205 # (array reference, optional)
206 # 'disabled' => boolean (optional)}
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200207 #
208 'tgz' => {
209 'display' => 'tar.gz',
210 'type' => 'application/x-gzip',
211 'suffix' => '.tar.gz',
212 'format' => 'tar',
Fraser Tweedale0c8c3852011-04-26 11:32:00 +1000213 'compressor' => ['gzip', '-n']},
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200214
215 'tbz2' => {
216 'display' => 'tar.bz2',
217 'type' => 'application/x-bzip2',
218 'suffix' => '.tar.bz2',
219 'format' => 'tar',
220 'compressor' => ['bzip2']},
221
Mark A Radacbdefb52009-08-06 10:28:25 -0400222 'txz' => {
223 'display' => 'tar.xz',
224 'type' => 'application/x-xz',
225 'suffix' => '.tar.xz',
226 'format' => 'tar',
227 'compressor' => ['xz'],
228 'disabled' => 1},
229
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200230 'zip' => {
231 'display' => 'zip',
232 'type' => 'application/x-zip',
233 'suffix' => '.zip',
234 'format' => 'zip'},
235);
236
237# Aliases so we understand old gitweb.snapshot values in repository
238# configuration.
239our %known_snapshot_format_aliases = (
240 'gzip' => 'tgz',
241 'bzip2' => 'tbz2',
Mark A Radacbdefb52009-08-06 10:28:25 -0400242 'xz' => 'txz',
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200243
244 # backward compatibility: legacy gitweb config support
245 'x-gzip' => undef, 'gz' => undef,
246 'x-bzip2' => undef, 'bz2' => undef,
247 'x-zip' => undef, '' => undef,
248);
249
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200250# Pixel sizes for icons and avatars. If the default font sizes or lineheights
251# are changed, it may be appropriate to change these values too via
252# $GITWEB_CONFIG.
253our %avatar_size = (
254 'default' => 16,
255 'double' => 32
256);
257
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +0100258# Used to set the maximum load that we will still respond to gitweb queries.
259# If server load exceed this value then return "503 server busy" error.
260# If gitweb cannot determined server load, it is taken to be 0.
261# Leave it undefined (or set to 'undef') to turn off load checking.
262our $maxload = 300;
263
Alejandro R. Sedeño61bf1262010-07-28 14:40:53 -0400264# configuration for 'highlight' (http://www.andre-simon.de/)
265# match by basename
266our %highlight_basename = (
267 #'Program' => 'py',
268 #'Library' => 'py',
269 'SConstruct' => 'py', # SCons equivalent of Makefile
270 'Makefile' => 'make',
271);
272# match by extension
273our %highlight_ext = (
274 # main extensions, defining name of syntax;
275 # see files in /usr/share/highlight/langDefs/ directory
Richard Hubbell048b3992012-11-04 09:45:55 -0800276 (map { $_ => $_ } qw(py rb java css js tex bib xml awk bat ini spec tcl sql)),
Alejandro R. Sedeño61bf1262010-07-28 14:40:53 -0400277 # alternate extensions, see /etc/highlight/filetypes.conf
Richard Hubbell048b3992012-11-04 09:45:55 -0800278 (map { $_ => 'c' } qw(c h)),
279 (map { $_ => 'sh' } qw(sh bash zsh ksh)),
280 (map { $_ => 'cpp' } qw(cpp cxx c++ cc)),
281 (map { $_ => 'php' } qw(php php3 php4 php5 phps)),
282 (map { $_ => 'pl' } qw(pl perl pm)), # perhaps also 'cgi'
283 (map { $_ => 'make'} qw(make mak mk)),
284 (map { $_ => 'xml' } qw(xml xhtml html htm)),
Alejandro R. Sedeño61bf1262010-07-28 14:40:53 -0400285);
286
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530287# You define site-wide feature defaults here; override them with
288# $GITWEB_CONFIG as necessary.
Jakub Narebski952c65f2006-08-22 16:52:50 +0200289our %feature = (
Jakub Narebski17848fc2006-08-26 19:14:22 +0200290 # feature => {
291 # 'sub' => feature-sub (subroutine),
292 # 'override' => allow-override (boolean),
293 # 'default' => [ default options...] (array reference)}
294 #
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200295 # if feature is overridable (it means that allow-override has true value),
Jakub Narebski17848fc2006-08-26 19:14:22 +0200296 # then feature-sub will be called with default options as parameters;
297 # return value of feature-sub indicates if to enable specified feature
298 #
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200299 # if there is no 'sub' key (no feature-sub), then feature cannot be
Ralf Wildenhues22e5e582010-08-22 13:12:12 +0200300 # overridden
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200301 #
Giuseppe Bilottaff3c0ff2008-12-02 14:57:28 -0800302 # use gitweb_get_feature(<feature>) to retrieve the <feature> value
303 # (an array) or gitweb_check_feature(<feature>) to check if <feature>
304 # is enabled
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530305
Petr Baudis45a3b122006-10-07 15:17:47 +0200306 # Enable the 'blame' blob view, showing the last commit that modified
307 # each line in the file. This can be very CPU-intensive.
308
309 # To enable system wide have in $GITWEB_CONFIG
310 # $feature{'blame'}{'default'} = [1];
311 # To have project specific config enable override in $GITWEB_CONFIG
312 # $feature{'blame'}{'override'} = 1;
313 # and in project config gitweb.blame = 0|1;
Jakub Narebski952c65f2006-08-22 16:52:50 +0200314 'blame' => {
Matt Kraaicdad8172008-12-15 22:16:19 -0800315 'sub' => sub { feature_bool('blame', @_) },
Jakub Narebski952c65f2006-08-22 16:52:50 +0200316 'override' => 0,
317 'default' => [0]},
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530318
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200319 # Enable the 'snapshot' link, providing a compressed archive of any
Petr Baudis45a3b122006-10-07 15:17:47 +0200320 # tree. This can potentially generate high traffic if you have large
321 # project.
322
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200323 # Value is a list of formats defined in %known_snapshot_formats that
324 # you wish to offer.
Petr Baudis45a3b122006-10-07 15:17:47 +0200325 # To disable system wide have in $GITWEB_CONFIG
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200326 # $feature{'snapshot'}{'default'} = [];
Petr Baudis45a3b122006-10-07 15:17:47 +0200327 # To have project specific config enable override in $GITWEB_CONFIG
Uwe Zeisbergerbbee1d92006-12-08 12:44:31 +0100328 # $feature{'snapshot'}{'override'} = 1;
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200329 # and in project config, a comma-separated list of formats or "none"
330 # to disable. Example: gitweb.snapshot = tbz2,zip;
Jakub Narebski952c65f2006-08-22 16:52:50 +0200331 'snapshot' => {
332 'sub' => \&feature_snapshot,
333 'override' => 0,
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200334 'default' => ['tgz']},
Jakub Narebski04f7a942006-09-11 00:29:27 +0200335
Robert Fitzsimons6be93512006-12-23 03:35:16 +0000336 # Enable text search, which will list the commits which match author,
337 # committer or commit text to a given string. Enabled by default.
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200338 # Project specific override is not supported.
Jakub Narebskie0ca3642011-06-22 17:28:52 +0200339 #
340 # Note that this controls all search features, which means that if
341 # it is disabled, then 'grep' and 'pickaxe' search would also be
342 # disabled.
Robert Fitzsimons6be93512006-12-23 03:35:16 +0000343 'search' => {
344 'override' => 0,
345 'default' => [1]},
346
Petr Baudise7738552007-05-17 04:31:12 +0200347 # Enable grep search, which will list the files in currently selected
348 # tree containing the given string. Enabled by default. This can be
349 # potentially CPU-intensive, of course.
Jakub Narebskia598ded2011-06-21 08:41:16 +0200350 # Note that you need to have 'search' feature enabled too.
Petr Baudise7738552007-05-17 04:31:12 +0200351
352 # To enable system wide have in $GITWEB_CONFIG
353 # $feature{'grep'}{'default'} = [1];
354 # To have project specific config enable override in $GITWEB_CONFIG
355 # $feature{'grep'}{'override'} = 1;
356 # and in project config gitweb.grep = 0|1;
357 'grep' => {
Matt Kraaicdad8172008-12-15 22:16:19 -0800358 'sub' => sub { feature_bool('grep', @_) },
Petr Baudise7738552007-05-17 04:31:12 +0200359 'override' => 0,
360 'default' => [1]},
361
Petr Baudis45a3b122006-10-07 15:17:47 +0200362 # Enable the pickaxe search, which will list the commits that modified
363 # a given string in a file. This can be practical and quite faster
364 # alternative to 'blame', but still potentially CPU-intensive.
Jakub Narebskia598ded2011-06-21 08:41:16 +0200365 # Note that you need to have 'search' feature enabled too.
Petr Baudis45a3b122006-10-07 15:17:47 +0200366
367 # To enable system wide have in $GITWEB_CONFIG
368 # $feature{'pickaxe'}{'default'} = [1];
369 # To have project specific config enable override in $GITWEB_CONFIG
370 # $feature{'pickaxe'}{'override'} = 1;
371 # and in project config gitweb.pickaxe = 0|1;
Jakub Narebski04f7a942006-09-11 00:29:27 +0200372 'pickaxe' => {
Matt Kraaicdad8172008-12-15 22:16:19 -0800373 'sub' => sub { feature_bool('pickaxe', @_) },
Jakub Narebski04f7a942006-09-11 00:29:27 +0200374 'override' => 0,
375 'default' => [1]},
Martin Waitz9e756902006-10-01 23:57:48 +0200376
Jakub Narebskie4b48ea2009-09-07 14:40:00 +0200377 # Enable showing size of blobs in a 'tree' view, in a separate
378 # column, similar to what 'ls -l' does. This cost a bit of IO.
379
380 # To disable system wide have in $GITWEB_CONFIG
381 # $feature{'show-sizes'}{'default'} = [0];
382 # To have project specific config enable override in $GITWEB_CONFIG
383 # $feature{'show-sizes'}{'override'} = 1;
384 # and in project config gitweb.showsizes = 0|1;
385 'show-sizes' => {
386 'sub' => sub { feature_bool('showsizes', @_) },
387 'override' => 0,
388 'default' => [1]},
389
Petr Baudis45a3b122006-10-07 15:17:47 +0200390 # Make gitweb use an alternative format of the URLs which can be
391 # more readable and natural-looking: project name is embedded
392 # directly in the path and the query string contains other
393 # auxiliary information. All gitweb installations recognize
394 # URL in either format; this configures in which formats gitweb
395 # generates links.
396
397 # To enable system wide have in $GITWEB_CONFIG
398 # $feature{'pathinfo'}{'default'} = [1];
399 # Project specific override is not supported.
400
401 # Note that you will need to change the default location of CSS,
402 # favicon, logo and possibly other files to an absolute URL. Also,
403 # if gitweb.cgi serves as your indexfile, you will need to force
404 # $my_uri to contain the script name in your $GITWEB_CONFIG.
Martin Waitz9e756902006-10-01 23:57:48 +0200405 'pathinfo' => {
406 'override' => 0,
407 'default' => [0]},
Petr Baudise30496d2006-10-24 05:33:17 +0200408
409 # Make gitweb consider projects in project root subdirectories
410 # to be forks of existing projects. Given project $projname.git,
411 # projects matching $projname/*.git will not be shown in the main
412 # projects list, instead a '+' mark will be added to $projname
413 # there and a 'forks' view will be enabled for the project, listing
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +0200414 # all the forks. If project list is taken from a file, forks have
415 # to be listed after the main project.
Petr Baudise30496d2006-10-24 05:33:17 +0200416
417 # To enable system wide have in $GITWEB_CONFIG
418 # $feature{'forks'}{'default'} = [1];
419 # Project specific override is not supported.
420 'forks' => {
421 'override' => 0,
422 'default' => [0]},
Petr Baudisd627f682008-10-02 16:36:52 +0200423
424 # Insert custom links to the action bar of all project pages.
425 # This enables you mainly to link to third-party scripts integrating
426 # into gitweb; e.g. git-browser for graphical history representation
427 # or custom web-based repository administration interface.
428
429 # The 'default' value consists of a list of triplets in the form
430 # (label, link, position) where position is the label after which
Jakub Narebski2b11e052008-10-12 00:02:32 +0200431 # to insert the link and link is a format string where %n expands
Petr Baudisd627f682008-10-02 16:36:52 +0200432 # to the project name, %f to the project path within the filesystem,
433 # %h to the current hash (h gitweb parameter) and %b to the current
Jakub Narebski2b11e052008-10-12 00:02:32 +0200434 # hash base (hb gitweb parameter); %% expands to %.
Petr Baudisd627f682008-10-02 16:36:52 +0200435
436 # To enable system wide have in $GITWEB_CONFIG e.g.
437 # $feature{'actions'}{'default'} = [('graphiclog',
438 # '/git-browser/by-commit.html?r=%n', 'summary')];
439 # Project specific override is not supported.
440 'actions' => {
441 'override' => 0,
442 'default' => []},
Shawn O. Pearce3e3d4ee2008-10-03 07:41:25 -0700443
Jakub Narebski0368c492011-04-29 19:51:57 +0200444 # Allow gitweb scan project content tags of project repository,
445 # and display the popular Web 2.0-ish "tag cloud" near the projects
446 # list. Note that this is something COMPLETELY different from the
447 # normal Git tags.
Petr Baudisaed93de2008-10-02 17:13:02 +0200448
449 # gitweb by itself can show existing tags, but it does not handle
Jakub Narebski0368c492011-04-29 19:51:57 +0200450 # tagging itself; you need to do it externally, outside gitweb.
451 # The format is described in git_get_project_ctags() subroutine.
Petr Baudisaed93de2008-10-02 17:13:02 +0200452 # You may want to install the HTML::TagCloud Perl module to get
453 # a pretty tag cloud instead of just a list of tags.
454
455 # To enable system wide have in $GITWEB_CONFIG
Jakub Narebski0368c492011-04-29 19:51:57 +0200456 # $feature{'ctags'}{'default'} = [1];
Petr Baudisaed93de2008-10-02 17:13:02 +0200457 # Project specific override is not supported.
Jakub Narebski0368c492011-04-29 19:51:57 +0200458
459 # In the future whether ctags editing is enabled might depend
460 # on the value, but using 1 should always mean no editing of ctags.
Petr Baudisaed93de2008-10-02 17:13:02 +0200461 'ctags' => {
462 'override' => 0,
463 'default' => [0]},
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +0100464
465 # The maximum number of patches in a patchset generated in patch
466 # view. Set this to 0 or undef to disable patch view, or to a
467 # negative number to remove any limit.
468
469 # To disable system wide have in $GITWEB_CONFIG
470 # $feature{'patches'}{'default'} = [0];
471 # To have project specific config enable override in $GITWEB_CONFIG
472 # $feature{'patches'}{'override'} = 1;
473 # and in project config gitweb.patches = 0|n;
474 # where n is the maximum number of patches allowed in a patchset.
475 'patches' => {
476 'sub' => \&feature_patches,
477 'override' => 0,
478 'default' => [16]},
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200479
480 # Avatar support. When this feature is enabled, views such as
481 # shortlog or commit will display an avatar associated with
482 # the email of the committer(s) and/or author(s).
483
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +0200484 # Currently available providers are gravatar and picon.
485 # If an unknown provider is specified, the feature is disabled.
486
487 # Gravatar depends on Digest::MD5.
488 # Picon currently relies on the indiana.edu database.
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200489
490 # To enable system wide have in $GITWEB_CONFIG
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +0200491 # $feature{'avatar'}{'default'} = ['<provider>'];
492 # where <provider> is either gravatar or picon.
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200493 # To have project specific config enable override in $GITWEB_CONFIG
494 # $feature{'avatar'}{'override'} = 1;
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +0200495 # and in project config gitweb.avatar = <provider>;
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200496 'avatar' => {
497 'sub' => \&feature_avatar,
498 'override' => 0,
499 'default' => ['']},
Jakub Narebskiaa7dd052009-09-01 13:39:16 +0200500
501 # Enable displaying how much time and how many git commands
502 # it took to generate and display page. Disabled by default.
503 # Project specific override is not supported.
504 'timed' => {
505 'override' => 0,
506 'default' => [0]},
Jakub Narebskie627e502009-11-26 21:12:15 +0100507
508 # Enable turning some links into links to actions which require
509 # JavaScript to run (like 'blame_incremental'). Not enabled by
510 # default. Project specific override is currently not supported.
511 'javascript-actions' => {
512 'override' => 0,
513 'default' => [0]},
Johannes Schindelinb331fe52010-04-27 21:34:44 +0200514
Jakub Narebski2e987f92011-04-28 21:04:11 +0200515 # Enable and configure ability to change common timezone for dates
516 # in gitweb output via JavaScript. Enabled by default.
517 # Project specific override is not supported.
518 'javascript-timezone' => {
519 'override' => 0,
520 'default' => [
521 'local', # default timezone: 'utc', 'local', or '(-|+)HHMM' format,
522 # or undef to turn off this feature
523 'gitweb_tz', # name of cookie where to store selected timezone
524 'datetime', # CSS class used to mark up dates for manipulation
525 ]},
526
Johannes Schindelinb331fe52010-04-27 21:34:44 +0200527 # Syntax highlighting support. This is based on Daniel Svensson's
528 # and Sham Chukoury's work in gitweb-xmms2.git.
Jakub Narebski592ea412010-04-27 21:34:45 +0200529 # It requires the 'highlight' program present in $PATH,
530 # and therefore is disabled by default.
Johannes Schindelinb331fe52010-04-27 21:34:44 +0200531
532 # To enable system wide have in $GITWEB_CONFIG
533 # $feature{'highlight'}{'default'} = [1];
534
535 'highlight' => {
536 'sub' => sub { feature_bool('highlight', @_) },
537 'override' => 0,
538 'default' => [0]},
Giuseppe Bilotta60efa242010-11-11 13:26:09 +0100539
540 # Enable displaying of remote heads in the heads list
541
542 # To enable system wide have in $GITWEB_CONFIG
543 # $feature{'remote_heads'}{'default'} = [1];
544 # To have project specific config enable override in $GITWEB_CONFIG
545 # $feature{'remote_heads'}{'override'} = 1;
Phil Pennockaf507942012-11-05 18:50:47 -0500546 # and in project config gitweb.remoteheads = 0|1;
Giuseppe Bilotta60efa242010-11-11 13:26:09 +0100547 'remote_heads' => {
548 'sub' => sub { feature_bool('remote_heads', @_) },
549 'override' => 0,
550 'default' => [0]},
Krzesimir Nowak8d646a92013-12-11 12:54:43 +0100551
552 # Enable showing branches under other refs in addition to heads
553
554 # To set system wide extra branch refs have in $GITWEB_CONFIG
555 # $feature{'extra-branch-refs'}{'default'} = ['dirs', 'of', 'choice'];
556 # To have project specific config enable override in $GITWEB_CONFIG
557 # $feature{'extra-branch-refs'}{'override'} = 1;
558 # and in project config gitweb.extrabranchrefs = dirs of choice
559 # Every directory is separated with whitespace.
560
561 'extra-branch-refs' => {
562 'sub' => \&feature_extra_branch_refs,
563 'override' => 0,
564 'default' => []},
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530565);
566
Junio C Hamanoa7c5a282008-11-29 13:02:08 -0800567sub gitweb_get_feature {
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530568 my ($name) = @_;
Jakub Narebskidd1ad5f2006-09-26 01:56:17 +0200569 return unless exists $feature{$name};
Jakub Narebski952c65f2006-08-22 16:52:50 +0200570 my ($sub, $override, @defaults) = (
571 $feature{$name}{'sub'},
572 $feature{$name}{'override'},
573 @{$feature{$name}{'default'}});
Jakub Narebski9be36142010-03-01 22:51:34 +0100574 # project specific override is possible only if we have project
575 our $git_dir; # global variable, declared later
576 if (!$override || !defined $git_dir) {
577 return @defaults;
578 }
Martin Waitza9455912006-10-03 20:07:43 +0200579 if (!defined $sub) {
Nanako Shiraishi93197892009-08-28 12:18:49 +0900580 warn "feature $name is not overridable";
Martin Waitza9455912006-10-03 20:07:43 +0200581 return @defaults;
582 }
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530583 return $sub->(@defaults);
584}
585
Giuseppe Bilotta25b27902008-11-29 13:07:29 -0800586# A wrapper to check if a given feature is enabled.
587# With this, you can say
588#
589# my $bool_feat = gitweb_check_feature('bool_feat');
590# gitweb_check_feature('bool_feat') or somecode;
591#
592# instead of
593#
594# my ($bool_feat) = gitweb_get_feature('bool_feat');
595# (gitweb_get_feature('bool_feat'))[0] or somecode;
596#
597sub gitweb_check_feature {
598 return (gitweb_get_feature(@_))[0];
599}
600
601
Matt Kraaicdad8172008-12-15 22:16:19 -0800602sub feature_bool {
603 my $key = shift;
604 my ($val) = git_get_project_config($key, '--bool');
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530605
Marcel M. Carydf5d10a2009-02-18 14:09:41 +0100606 if (!defined $val) {
607 return ($_[0]);
608 } elsif ($val eq 'true') {
Matt Kraaicdad8172008-12-15 22:16:19 -0800609 return (1);
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530610 } elsif ($val eq 'false') {
Matt Kraaicdad8172008-12-15 22:16:19 -0800611 return (0);
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530612 }
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530613}
614
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530615sub feature_snapshot {
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200616 my (@fmts) = @_;
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530617
618 my ($val) = git_get_project_config('snapshot');
619
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200620 if ($val) {
621 @fmts = ($val eq 'none' ? () : split /\s*[,\s]\s*/, $val);
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530622 }
623
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200624 return @fmts;
Luben Tuikovde9272f2006-09-28 16:49:21 -0700625}
626
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +0100627sub feature_patches {
628 my @val = (git_get_project_config('patches', '--int'));
629
630 if (@val) {
631 return @val;
632 }
633
634 return ($_[0]);
635}
636
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200637sub feature_avatar {
638 my @val = (git_get_project_config('avatar'));
639
640 return @val ? @val : @_;
641}
642
Krzesimir Nowak8d646a92013-12-11 12:54:43 +0100643sub feature_extra_branch_refs {
644 my (@branch_refs) = @_;
645 my $values = git_get_project_config('extrabranchrefs');
646
647 if ($values) {
648 $values = config_to_multi ($values);
649 @branch_refs = ();
650 foreach my $value (@{$values}) {
651 push @branch_refs, split /\s+/, $value;
652 }
653 }
654
655 return @branch_refs;
656}
657
Junio C Hamano2172ce42006-10-03 02:30:47 -0700658# checking HEAD file with -e is fragile if the repository was
659# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
660# and then pruned.
661sub check_head_link {
662 my ($dir) = @_;
663 my $headfile = "$dir/HEAD";
664 return ((-e $headfile) ||
665 (-l $headfile && readlink($headfile) =~ /^refs\/heads\//));
666}
667
668sub check_export_ok {
669 my ($dir) = @_;
670 return (check_head_link($dir) &&
Alexander Gavrilovdd7f5f12008-11-06 01:36:23 +0300671 (!$export_ok || -e "$dir/$export_ok") &&
672 (!$export_auth_hook || $export_auth_hook->($dir)));
Junio C Hamano2172ce42006-10-03 02:30:47 -0700673}
674
Jakub Narebskia7817852007-07-22 23:41:20 +0200675# process alternate names for backward compatibility
676# filter out unsupported (unknown) snapshot formats
677sub filter_snapshot_fmts {
678 my @fmts = @_;
679
680 @fmts = map {
681 exists $known_snapshot_format_aliases{$_} ?
682 $known_snapshot_format_aliases{$_} : $_} @fmts;
Jakub Narebski68cedb12009-05-10 02:40:37 +0200683 @fmts = grep {
Mark A Rada1bfd3632009-08-06 10:25:39 -0400684 exists $known_snapshot_formats{$_} &&
685 !$known_snapshot_formats{$_}{'disabled'}} @fmts;
Jakub Narebskia7817852007-07-22 23:41:20 +0200686}
687
Krzesimir Nowak8d646a92013-12-11 12:54:43 +0100688sub filter_and_validate_refs {
689 my @refs = @_;
690 my %unique_refs = ();
691
692 foreach my $ref (@refs) {
693 die_error(500, "Invalid ref '$ref' in 'extra-branch-refs' feature") unless (is_valid_ref_format($ref));
694 # 'heads' are added implicitly in get_branch_refs().
695 $unique_refs{$ref} = 1 if ($ref ne 'heads');
696 }
697 return sort keys %unique_refs;
698}
699
Jakub Narebskida4b2432010-11-25 19:43:59 +0100700# If it is set to code reference, it is code that it is to be run once per
701# request, allowing updating configurations that change with each request,
702# while running other code in config file only once.
703#
704# Otherwise, if it is false then gitweb would process config file only once;
705# if it is true then gitweb config would be run for each request.
706our $per_request_config = 1;
707
Jakub Narebskif612a712011-05-25 18:35:26 +0200708# read and parse gitweb config file given by its parameter.
709# returns true on success, false on recoverable error, allowing
710# to chain this subroutine, using first file that exists.
711# dies on errors during parsing config file, as it is unrecoverable.
712sub read_config_file {
713 my $filename = shift;
714 return unless defined $filename;
715 # die if there are errors parsing config file
716 if (-e $filename) {
717 do $filename;
718 die $@ if $@;
719 return 1;
720 }
721 return;
722}
723
Jakub Narebski131d6af2011-07-25 00:29:18 +0200724our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM, $GITWEB_CONFIG_COMMON);
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200725sub evaluate_gitweb_config {
726 our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
727 our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
Jakub Narebski131d6af2011-07-25 00:29:18 +0200728 our $GITWEB_CONFIG_COMMON = $ENV{'GITWEB_CONFIG_COMMON'} || "++GITWEB_CONFIG_COMMON++";
Jakub Narebskif612a712011-05-25 18:35:26 +0200729
Stefano Lattarini41ccfdd2013-04-12 00:36:10 +0200730 # Protect against duplications of file names, to not read config twice.
Jakub Narebski131d6af2011-07-25 00:29:18 +0200731 # Only one of $GITWEB_CONFIG and $GITWEB_CONFIG_SYSTEM is used, so
732 # there possibility of duplication of filename there doesn't matter.
733 $GITWEB_CONFIG = "" if ($GITWEB_CONFIG eq $GITWEB_CONFIG_COMMON);
734 $GITWEB_CONFIG_SYSTEM = "" if ($GITWEB_CONFIG_SYSTEM eq $GITWEB_CONFIG_COMMON);
735
736 # Common system-wide settings for convenience.
737 # Those settings can be ovverriden by GITWEB_CONFIG or GITWEB_CONFIG_SYSTEM.
738 read_config_file($GITWEB_CONFIG_COMMON);
739
740 # Use first config file that exists. This means use the per-instance
741 # GITWEB_CONFIG if exists, otherwise use GITWEB_SYSTEM_CONFIG.
742 read_config_file($GITWEB_CONFIG) and return;
Jakub Narebskif612a712011-05-25 18:35:26 +0200743 read_config_file($GITWEB_CONFIG_SYSTEM);
Gerrit Pape17a8b252008-03-26 18:11:19 +0000744}
Jeff Kingc8d138a2006-08-02 15:23:34 -0400745
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +0100746# Get loadavg of system, to compare against $maxload.
747# Currently it requires '/proc/loadavg' present to get loadavg;
748# if it is not present it returns 0, which means no load checking.
749sub get_loadavg {
750 if( -e '/proc/loadavg' ){
751 open my $fd, '<', '/proc/loadavg'
752 or return 0;
753 my @load = split(/\s+/, scalar <$fd>);
754 close $fd;
755
756 # The first three columns measure CPU and IO utilization of the last one,
757 # five, and 10 minute periods. The fourth column shows the number of
758 # currently running processes and the total number of processes in the m/n
759 # format. The last column displays the last process ID used.
760 return $load[0] || 0;
761 }
762 # additional checks for load average should go here for things that don't export
763 # /proc/loadavg
764
765 return 0;
766}
767
Jeff Kingc8d138a2006-08-02 15:23:34 -0400768# version of the core git binary
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200769our $git_version;
770sub evaluate_git_version {
771 our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
772 $number_of_git_cmds++;
773}
Jeff Kingc8d138a2006-08-02 15:23:34 -0400774
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200775sub check_loadavg {
776 if (defined $maxload && get_loadavg() > $maxload) {
777 die_error(503, "The load average on the server is too high");
778 }
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +0100779}
780
Jakub Narebski154b4d72006-08-05 12:55:20 +0200781# ======================================================================
Kay Sievers09bd7892005-08-07 20:21:23 +0200782# input validation and dispatch
Kay Sieversb87d78d2005-08-07 20:21:04 +0200783
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200784# input parameters can be collected from a variety of sources (presently, CGI
785# and PATH_INFO), so we define an %input_params hash that collects them all
786# together during validation: this allows subsequent uses (e.g. href()) to be
787# agnostic of the parameter origin
Kay Sievers6191f8e2005-08-07 20:19:56 +0200788
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300789our %input_params = ();
Martin Waitz5c95fab2006-08-17 00:28:38 +0200790
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200791# input parameters are stored with the long parameter name as key. This will
792# also be used in the href subroutine to convert parameters to their CGI
793# equivalent, and since the href() usage is the most frequent one, we store
794# the name -> CGI key mapping here, instead of the reverse.
795#
796# XXX: Warning: If you touch this, check the search form for updating,
797# too.
Jakub Narebski24d06932006-09-26 01:57:02 +0200798
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300799our @cgi_param_mapping = (
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200800 project => "p",
801 action => "a",
802 file_name => "f",
803 file_parent => "fp",
804 hash => "h",
805 hash_parent => "hp",
806 hash_base => "hb",
807 hash_parent_base => "hpb",
808 page => "pg",
809 order => "o",
810 searchtext => "s",
811 searchtype => "st",
812 snapshot_format => "sf",
813 extra_options => "opt",
814 search_use_regexp => "sr",
Jakub Narebski0368c492011-04-29 19:51:57 +0200815 ctag => "by_tag",
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +0100816 diff_style => "ds",
Bernhard R. Link19d2d232012-01-30 21:07:37 +0100817 project_filter => "pf",
Jakub Narebskic4ccf612009-09-01 13:39:19 +0200818 # this must be last entry (for manipulation from JavaScript)
819 javascript => "js"
Miklos Vajna868bc062007-07-12 20:39:27 +0200820);
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300821our %cgi_param_mapping = @cgi_param_mapping;
Miklos Vajna868bc062007-07-12 20:39:27 +0200822
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200823# we will also need to know the possible actions, for validation
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300824our %actions = (
Rafael Garcia-Suarez3a5b9192008-06-06 09:53:32 +0200825 "blame" => \&git_blame,
Jakub Narebski4af819d2009-09-01 13:39:17 +0200826 "blame_incremental" => \&git_blame_incremental,
827 "blame_data" => \&git_blame_data,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200828 "blobdiff" => \&git_blobdiff,
829 "blobdiff_plain" => \&git_blobdiff_plain,
830 "blob" => \&git_blob,
831 "blob_plain" => \&git_blob_plain,
832 "commitdiff" => \&git_commitdiff,
833 "commitdiff_plain" => \&git_commitdiff_plain,
834 "commit" => \&git_commit,
Petr Baudise30496d2006-10-24 05:33:17 +0200835 "forks" => \&git_forks,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200836 "heads" => \&git_heads,
837 "history" => \&git_history,
838 "log" => \&git_log,
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +0100839 "patch" => \&git_patch,
Giuseppe Bilottaa3411f82008-12-18 08:13:18 +0100840 "patches" => \&git_patches,
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +0100841 "remotes" => \&git_remotes,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200842 "rss" => \&git_rss,
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +0100843 "atom" => \&git_atom,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200844 "search" => \&git_search,
Petr Baudis88ad7292006-10-24 05:15:46 +0200845 "search_help" => \&git_search_help,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200846 "shortlog" => \&git_shortlog,
847 "summary" => \&git_summary,
848 "tag" => \&git_tag,
849 "tags" => \&git_tags,
850 "tree" => \&git_tree,
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +0530851 "snapshot" => \&git_snapshot,
Jakub Narebskica946012006-12-10 13:25:47 +0100852 "object" => \&git_object,
Jakub Narebski77a153f2006-08-22 16:59:20 +0200853 # those below don't need $project
854 "opml" => \&git_opml,
855 "project_list" => \&git_project_list,
Jakub Narebskifc2b2be2006-09-15 04:56:03 +0200856 "project_index" => \&git_project_index,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200857);
858
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200859# finally, we have the hash of allowed extra_options for the commands that
860# allow them
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300861our %allowed_options = (
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200862 "--no-merges" => [ qw(rss atom log shortlog history) ],
863);
864
865# fill %input_params with the CGI parameters. All values except for 'opt'
866# should be single values, but opt can be an array. We should probably
867# build an array of parameters that can be multi-valued, but since for the time
868# being it's only this one, we just single it out
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200869sub evaluate_query_params {
870 our $cgi;
871
872 while (my ($name, $symbol) = each %cgi_param_mapping) {
873 if ($symbol eq 'opt') {
Jakub Narebski84d9e2d2012-02-03 13:44:54 +0100874 $input_params{$name} = [ map { decode_utf8($_) } $cgi->param($symbol) ];
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200875 } else {
Jakub Narebski84d9e2d2012-02-03 13:44:54 +0100876 $input_params{$name} = decode_utf8($cgi->param($symbol));
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200877 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200878 }
879}
880
881# now read PATH_INFO and update the parameter list for missing parameters
882sub evaluate_path_info {
883 return if defined $input_params{'project'};
884 return if !$path_info;
885 $path_info =~ s,^/+,,;
886 return if !$path_info;
887
888 # find which part of PATH_INFO is project
889 my $project = $path_info;
890 $project =~ s,/+$,,;
891 while ($project && !check_head_link("$projectroot/$project")) {
892 $project =~ s,/*[^/]*$,,;
893 }
894 return unless $project;
895 $input_params{'project'} = $project;
896
897 # do not change any parameters if an action is given using the query string
898 return if $input_params{'action'};
899 $path_info =~ s,^\Q$project\E/*,,;
900
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200901 # next, check if we have an action
902 my $action = $path_info;
903 $action =~ s,/.*$,,;
904 if (exists $actions{$action}) {
905 $path_info =~ s,^$action/*,,;
906 $input_params{'action'} = $action;
907 }
908
909 # list of actions that want hash_base instead of hash, but can have no
910 # pathname (f) parameter
911 my @wants_base = (
912 'tree',
913 'history',
914 );
915
Jakub Narebski7e00dc52010-10-13 13:33:48 +0200916 # we want to catch, among others
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200917 # [$hash_parent_base[:$file_parent]..]$hash_parent[:$file_name]
918 my ($parentrefname, $parentpathname, $refname, $pathname) =
Jakub Narebski7e00dc52010-10-13 13:33:48 +0200919 ($path_info =~ /^(?:(.+?)(?::(.+))?\.\.)?([^:]+?)?(?::(.+))?$/);
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200920
921 # first, analyze the 'current' part
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200922 if (defined $pathname) {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200923 # we got "branch:filename" or "branch:dir/"
924 # we could use git_get_type(branch:pathname), but:
925 # - it needs $git_dir
926 # - it does a git() call
927 # - the convention of terminating directories with a slash
928 # makes it superfluous
929 # - embedding the action in the PATH_INFO would make it even
930 # more superfluous
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200931 $pathname =~ s,^/+,,;
932 if (!$pathname || substr($pathname, -1) eq "/") {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200933 $input_params{'action'} ||= "tree";
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200934 $pathname =~ s,/$,,;
935 } else {
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200936 # the default action depends on whether we had parent info
937 # or not
938 if ($parentrefname) {
939 $input_params{'action'} ||= "blobdiff_plain";
940 } else {
941 $input_params{'action'} ||= "blob_plain";
942 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200943 }
944 $input_params{'hash_base'} ||= $refname;
945 $input_params{'file_name'} ||= $pathname;
946 } elsif (defined $refname) {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200947 # we got "branch". In this case we have to choose if we have to
948 # set hash or hash_base.
949 #
950 # Most of the actions without a pathname only want hash to be
951 # set, except for the ones specified in @wants_base that want
952 # hash_base instead. It should also be noted that hand-crafted
953 # links having 'history' as an action and no pathname or hash
954 # set will fail, but that happens regardless of PATH_INFO.
Jakub Narebskid0af3732010-10-13 13:35:20 +0200955 if (defined $parentrefname) {
956 # if there is parent let the default be 'shortlog' action
957 # (for http://git.example.com/repo.git/A..B links); if there
958 # is no parent, dispatch will detect type of object and set
959 # action appropriately if required (if action is not set)
960 $input_params{'action'} ||= "shortlog";
961 }
962 if ($input_params{'action'} &&
963 grep { $_ eq $input_params{'action'} } @wants_base) {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200964 $input_params{'hash_base'} ||= $refname;
965 } else {
966 $input_params{'hash'} ||= $refname;
967 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200968 }
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200969
970 # next, handle the 'parent' part, if present
971 if (defined $parentrefname) {
972 # a missing pathspec defaults to the 'current' filename, allowing e.g.
973 # someproject/blobdiff/oldrev..newrev:/filename
974 if ($parentpathname) {
975 $parentpathname =~ s,^/+,,;
976 $parentpathname =~ s,/$,,;
977 $input_params{'file_parent'} ||= $parentpathname;
978 } else {
979 $input_params{'file_parent'} ||= $input_params{'file_name'};
980 }
981 # we assume that hash_parent_base is wanted if a path was specified,
982 # or if the action wants hash_base instead of hash
983 if (defined $input_params{'file_parent'} ||
984 grep { $_ eq $input_params{'action'} } @wants_base) {
985 $input_params{'hash_parent_base'} ||= $parentrefname;
986 } else {
987 $input_params{'hash_parent'} ||= $parentrefname;
988 }
989 }
Giuseppe Bilotta1ec2fb52008-11-02 10:21:38 +0100990
991 # for the snapshot action, we allow URLs in the form
992 # $project/snapshot/$hash.ext
993 # where .ext determines the snapshot and gets removed from the
994 # passed $refname to provide the $hash.
995 #
996 # To be able to tell that $refname includes the format extension, we
997 # require the following two conditions to be satisfied:
998 # - the hash input parameter MUST have been set from the $refname part
999 # of the URL (i.e. they must be equal)
1000 # - the snapshot format MUST NOT have been defined already (e.g. from
1001 # CGI parameter sf)
1002 # It's also useless to try any matching unless $refname has a dot,
1003 # so we check for that too
1004 if (defined $input_params{'action'} &&
1005 $input_params{'action'} eq 'snapshot' &&
1006 defined $refname && index($refname, '.') != -1 &&
1007 $refname eq $input_params{'hash'} &&
1008 !defined $input_params{'snapshot_format'}) {
1009 # We loop over the known snapshot formats, checking for
1010 # extensions. Allowed extensions are both the defined suffix
1011 # (which includes the initial dot already) and the snapshot
1012 # format key itself, with a prepended dot
Holger Weißccb4b532009-03-31 18:16:36 +02001013 while (my ($fmt, $opt) = each %known_snapshot_formats) {
Giuseppe Bilotta1ec2fb52008-11-02 10:21:38 +01001014 my $hash = $refname;
Jakub Narebski095e9142009-05-11 19:42:47 +02001015 unless ($hash =~ s/(\Q$opt->{'suffix'}\E|\Q.$fmt\E)$//) {
1016 next;
1017 }
1018 my $sfx = $1;
Giuseppe Bilotta1ec2fb52008-11-02 10:21:38 +01001019 # a valid suffix was found, so set the snapshot format
1020 # and reset the hash parameter
1021 $input_params{'snapshot_format'} = $fmt;
1022 $input_params{'hash'} = $hash;
1023 # we also set the format suffix to the one requested
1024 # in the URL: this way a request for e.g. .tgz returns
1025 # a .tgz instead of a .tar.gz
1026 $known_snapshot_formats{$fmt}{'suffix'} = $sfx;
1027 last;
1028 }
1029 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001030}
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001031
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001032our ($action, $project, $file_name, $file_parent, $hash, $hash_parent, $hash_base,
1033 $hash_parent_base, @extra_options, $page, $searchtype, $search_use_regexp,
Bernhard R. Link19d2d232012-01-30 21:07:37 +01001034 $searchtext, $search_regexp, $project_filter);
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001035sub evaluate_and_validate_params {
1036 our $action = $input_params{'action'};
1037 if (defined $action) {
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001038 if (!is_valid_action($action)) {
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001039 die_error(400, "Invalid action parameter");
1040 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001041 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001042
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001043 # parameters which are pathnames
1044 our $project = $input_params{'project'};
1045 if (defined $project) {
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001046 if (!is_valid_project($project)) {
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001047 undef $project;
1048 die_error(404, "No such project");
1049 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001050 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001051
Bernhard R. Link19d2d232012-01-30 21:07:37 +01001052 our $project_filter = $input_params{'project_filter'};
1053 if (defined $project_filter) {
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001054 if (!is_valid_pathname($project_filter)) {
Bernhard R. Link19d2d232012-01-30 21:07:37 +01001055 die_error(404, "Invalid project_filter parameter");
1056 }
1057 }
1058
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001059 our $file_name = $input_params{'file_name'};
1060 if (defined $file_name) {
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001061 if (!is_valid_pathname($file_name)) {
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001062 die_error(400, "Invalid file parameter");
1063 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001064 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001065
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001066 our $file_parent = $input_params{'file_parent'};
1067 if (defined $file_parent) {
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001068 if (!is_valid_pathname($file_parent)) {
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001069 die_error(400, "Invalid file parent parameter");
1070 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001071 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001072
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001073 # parameters which are refnames
1074 our $hash = $input_params{'hash'};
1075 if (defined $hash) {
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001076 if (!is_valid_refname($hash)) {
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001077 die_error(400, "Invalid hash parameter");
1078 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001079 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001080
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001081 our $hash_parent = $input_params{'hash_parent'};
1082 if (defined $hash_parent) {
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001083 if (!is_valid_refname($hash_parent)) {
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001084 die_error(400, "Invalid hash parent parameter");
1085 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001086 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001087
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001088 our $hash_base = $input_params{'hash_base'};
1089 if (defined $hash_base) {
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001090 if (!is_valid_refname($hash_base)) {
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001091 die_error(400, "Invalid hash base parameter");
1092 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001093 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001094
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001095 our @extra_options = @{$input_params{'extra_options'}};
1096 # @extra_options is always defined, since it can only be (currently) set from
1097 # CGI, and $cgi->param() returns the empty array in array context if the param
1098 # is not set
1099 foreach my $opt (@extra_options) {
1100 if (not exists $allowed_options{$opt}) {
1101 die_error(400, "Invalid option parameter");
1102 }
1103 if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
1104 die_error(400, "Invalid option parameter for this action");
1105 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001106 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001107
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001108 our $hash_parent_base = $input_params{'hash_parent_base'};
1109 if (defined $hash_parent_base) {
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001110 if (!is_valid_refname($hash_parent_base)) {
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001111 die_error(400, "Invalid hash parent base parameter");
1112 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001113 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001114
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001115 # other parameters
1116 our $page = $input_params{'page'};
1117 if (defined $page) {
1118 if ($page =~ m/[^0-9]/) {
1119 die_error(400, "Invalid page parameter");
1120 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001121 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001122
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001123 our $searchtype = $input_params{'searchtype'};
1124 if (defined $searchtype) {
1125 if ($searchtype =~ m/[^a-z]/) {
1126 die_error(400, "Invalid searchtype parameter");
1127 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001128 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001129
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001130 our $search_use_regexp = $input_params{'search_use_regexp'};
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001131
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001132 our $searchtext = $input_params{'searchtext'};
Charles McGarveyca7a5dc2013-06-04 22:44:28 -06001133 our $search_regexp = undef;
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001134 if (defined $searchtext) {
1135 if (length($searchtext) < 2) {
1136 die_error(403, "At least two characters are required for search parameter");
1137 }
Jakub Narebski36612e42012-02-28 19:41:47 +01001138 if ($search_use_regexp) {
1139 $search_regexp = $searchtext;
1140 if (!eval { qr/$search_regexp/; 1; }) {
1141 (my $error = $@) =~ s/ at \S+ line \d+.*\n?//;
1142 die_error(400, "Invalid search regexp '$search_regexp'",
1143 esc_html($error));
1144 }
1145 } else {
1146 $search_regexp = quotemeta $searchtext;
1147 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001148 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001149}
1150
1151# path to the current git repository
1152our $git_dir;
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001153sub evaluate_git_dir {
1154 our $git_dir = "$projectroot/$project" if $project;
1155}
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001156
Krzesimir Nowak8d646a92013-12-11 12:54:43 +01001157our (@snapshot_fmts, $git_avatar, @extra_branch_refs);
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001158sub configure_gitweb_features {
1159 # list of supported snapshot formats
1160 our @snapshot_fmts = gitweb_get_feature('snapshot');
1161 @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
Giuseppe Bilotta5e166842008-11-02 10:21:37 +01001162
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001163 # check that the avatar feature is set to a known provider name,
1164 # and for each provider check if the dependencies are satisfied.
1165 # if the provider name is invalid or the dependencies are not met,
1166 # reset $git_avatar to the empty string.
1167 our ($git_avatar) = gitweb_get_feature('avatar');
1168 if ($git_avatar eq 'gravatar') {
1169 $git_avatar = '' unless (eval { require Digest::MD5; 1; });
1170 } elsif ($git_avatar eq 'picon') {
1171 # no dependencies
1172 } else {
1173 $git_avatar = '';
1174 }
Krzesimir Nowak8d646a92013-12-11 12:54:43 +01001175
1176 our @extra_branch_refs = gitweb_get_feature('extra-branch-refs');
1177 @extra_branch_refs = filter_and_validate_refs (@extra_branch_refs);
1178}
1179
1180sub get_branch_refs {
1181 return ('heads', @extra_branch_refs);
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02001182}
1183
Jakub Narebski7a597452010-04-24 16:00:04 +02001184# custom error handler: 'die <message>' is Internal Server Error
1185sub handle_errors_html {
1186 my $msg = shift; # it is already HTML escaped
1187
1188 # to avoid infinite loop where error occurs in die_error,
1189 # change handler to default handler, disabling handle_errors_html
Stefano Lattarini41ccfdd2013-04-12 00:36:10 +02001190 set_message("Error occurred when inside die_error:\n$msg");
Jakub Narebski7a597452010-04-24 16:00:04 +02001191
1192 # you cannot jump out of die_error when called as error handler;
1193 # the subroutine set via CGI::Carp::set_message is called _after_
1194 # HTTP headers are already written, so it cannot write them itself
1195 die_error(undef, undef, $msg, -error_handler => 1, -no_http_header => 1);
1196}
1197set_message(\&handle_errors_html);
1198
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001199# dispatch
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001200sub dispatch {
1201 if (!defined $action) {
1202 if (defined $hash) {
1203 $action = git_get_type($hash);
Jakub Narebski18ab83e2012-01-07 11:47:38 +01001204 $action or die_error(404, "Object does not exist");
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001205 } elsif (defined $hash_base && defined $file_name) {
1206 $action = git_get_type("$hash_base:$file_name");
Jakub Narebski18ab83e2012-01-07 11:47:38 +01001207 $action or die_error(404, "File or directory does not exist");
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001208 } elsif (defined $project) {
1209 $action = 'summary';
1210 } else {
1211 $action = 'project_list';
1212 }
Gerrit Pape7f9778b2007-05-10 07:32:07 +00001213 }
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001214 if (!defined($actions{$action})) {
1215 die_error(400, "Unknown action");
1216 }
1217 if ($action !~ m/^(?:opml|project_list|project_index)$/ &&
1218 !$project) {
1219 die_error(400, "Project needed");
1220 }
1221 $actions{$action}->();
Jakub Narebski77a153f2006-08-22 16:59:20 +02001222}
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001223
Jakub Narebski869d5882010-07-05 20:52:43 +02001224sub reset_timer {
Jakub Narebski3962f1d72010-11-09 19:27:54 +01001225 our $t0 = [ gettimeofday() ]
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001226 if defined $t0;
Jakub Narebski869d5882010-07-05 20:52:43 +02001227 our $number_of_git_cmds = 0;
1228}
1229
Jakub Narebskida4b2432010-11-25 19:43:59 +01001230our $first_request = 1;
Jakub Narebski869d5882010-07-05 20:52:43 +02001231sub run_request {
1232 reset_timer();
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001233
1234 evaluate_uri();
Jakub Narebskida4b2432010-11-25 19:43:59 +01001235 if ($first_request) {
1236 evaluate_gitweb_config();
1237 evaluate_git_version();
1238 }
1239 if ($per_request_config) {
1240 if (ref($per_request_config) eq 'CODE') {
1241 $per_request_config->();
1242 } elsif (!$first_request) {
1243 evaluate_gitweb_config();
1244 }
1245 }
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001246 check_loadavg();
1247
Jonathan Nieder7f425db2010-07-30 22:01:59 -05001248 # $projectroot and $projects_list might be set in gitweb config file
1249 $projects_list ||= $projectroot;
1250
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001251 evaluate_query_params();
1252 evaluate_path_info();
1253 evaluate_and_validate_params();
1254 evaluate_git_dir();
1255
1256 configure_gitweb_features();
1257
1258 dispatch();
Kay Sievers09bd7892005-08-07 20:21:23 +02001259}
Sam Vilaina0446e72010-05-07 14:54:05 +02001260
1261our $is_last_request = sub { 1 };
1262our ($pre_dispatch_hook, $post_dispatch_hook, $pre_listen_hook);
1263our $CGI = 'CGI';
1264our $cgi;
Jakub Narebski45aa9892010-06-05 23:11:18 +02001265sub configure_as_fcgi {
1266 require CGI::Fast;
1267 our $CGI = 'CGI::Fast';
1268
1269 my $request_number = 0;
1270 # let each child service 100 requests
1271 our $is_last_request = sub { ++$request_number > 100 };
Jakub Narebskid04d3d42006-09-19 21:53:22 +02001272}
Sam Vilaina0446e72010-05-07 14:54:05 +02001273sub evaluate_argv {
Jakub Narebski45aa9892010-06-05 23:11:18 +02001274 my $script_name = $ENV{'SCRIPT_NAME'} || $ENV{'SCRIPT_FILENAME'} || __FILE__;
1275 configure_as_fcgi()
1276 if $script_name =~ /\.fcgi$/;
1277
Sam Vilaina0446e72010-05-07 14:54:05 +02001278 return unless (@ARGV);
1279
1280 require Getopt::Long;
1281 Getopt::Long::GetOptions(
Jakub Narebski45aa9892010-06-05 23:11:18 +02001282 'fastcgi|fcgi|f' => \&configure_as_fcgi,
Sam Vilaina0446e72010-05-07 14:54:05 +02001283 'nproc|n=i' => sub {
1284 my ($arg, $val) = @_;
1285 return unless eval { require FCGI::ProcManager; 1; };
1286 my $proc_manager = FCGI::ProcManager->new({
1287 n_processes => $val,
1288 });
1289 our $pre_listen_hook = sub { $proc_manager->pm_manage() };
1290 our $pre_dispatch_hook = sub { $proc_manager->pm_pre_dispatch() };
1291 our $post_dispatch_hook = sub { $proc_manager->pm_post_dispatch() };
1292 },
1293 );
1294}
1295
1296sub run {
1297 evaluate_argv();
Jakub Narebski869d5882010-07-05 20:52:43 +02001298
Jakub Narebskida4b2432010-11-25 19:43:59 +01001299 $first_request = 1;
Sam Vilaina0446e72010-05-07 14:54:05 +02001300 $pre_listen_hook->()
1301 if $pre_listen_hook;
1302
1303 REQUEST:
1304 while ($cgi = $CGI->new()) {
1305 $pre_dispatch_hook->()
1306 if $pre_dispatch_hook;
1307
1308 run_request();
1309
Jakub Narebski0b450102010-08-02 22:21:47 +02001310 $post_dispatch_hook->()
Sam Vilaina0446e72010-05-07 14:54:05 +02001311 if $post_dispatch_hook;
Jakub Narebskida4b2432010-11-25 19:43:59 +01001312 $first_request = 0;
Sam Vilaina0446e72010-05-07 14:54:05 +02001313
1314 last REQUEST if ($is_last_request->());
1315 }
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001316
1317 DONE_GITWEB:
1318 1;
Kay Sievers09bd7892005-08-07 20:21:23 +02001319}
Sam Vilaina0446e72010-05-07 14:54:05 +02001320
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001321run();
Kay Sievers09bd7892005-08-07 20:21:23 +02001322
Jakub Narebski5ed2ec12010-06-13 12:09:32 +02001323if (defined caller) {
1324 # wrapped in a subroutine processing requests,
1325 # e.g. mod_perl with ModPerl::Registry, or PSGI with Plack::App::WrapCGI
1326 return;
1327} else {
1328 # pure CGI script, serving single request
1329 exit;
1330}
Kay Sievers823d5dc2005-08-07 19:57:58 +02001331
Jakub Narebski717b8312006-07-31 21:22:15 +02001332## ======================================================================
Martin Waitz06a9d862006-08-16 00:23:50 +02001333## action links
1334
Jakub Narebski377bee32010-04-24 15:53:19 +02001335# possible values of extra options
1336# -full => 0|1 - use absolute/full URL ($my_uri/$my_url as base)
1337# -replay => 1 - start from a current view (replay with modifications)
1338# -path_info => 0|1 - don't use/use path_info URL (if possible)
Kevin Cernekee5e96a842011-03-18 17:00:16 +01001339# -anchor => ANCHOR - add #ANCHOR to end of URL, implies -replay if used alone
Jakub Narebski74fd8722009-05-07 19:11:29 +02001340sub href {
Jakub Narebski498fe002006-08-22 19:05:25 +02001341 my %params = @_;
Jakub Narebskibd5d1e42006-11-19 15:05:21 +01001342 # default is to use -absolute url() i.e. $my_uri
1343 my $href = $params{-full} ? $my_url : $my_uri;
Jakub Narebski498fe002006-08-22 19:05:25 +02001344
Kevin Cernekee5e96a842011-03-18 17:00:16 +01001345 # implicit -replay, must be first of implicit params
1346 $params{-replay} = 1 if (keys %params == 1 && $params{-anchor});
1347
Jakub Narebskiafa9b622008-02-14 09:22:30 +01001348 $params{'project'} = $project unless exists $params{'project'};
1349
Jakub Narebski1cad2832007-11-01 13:06:27 +01001350 if ($params{-replay}) {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001351 while (my ($name, $symbol) = each %cgi_param_mapping) {
Jakub Narebski1cad2832007-11-01 13:06:27 +01001352 if (!exists $params{$name}) {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001353 $params{$name} = $input_params{$name};
Jakub Narebski1cad2832007-11-01 13:06:27 +01001354 }
1355 }
1356 }
1357
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08001358 my $use_pathinfo = gitweb_check_feature('pathinfo');
Jakub Narebski377bee32010-04-24 15:53:19 +02001359 if (defined $params{'project'} &&
1360 (exists $params{-path_info} ? $params{-path_info} : $use_pathinfo)) {
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001361 # try to put as many parameters as possible in PATH_INFO:
1362 # - project name
1363 # - action
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001364 # - hash_parent or hash_parent_base:/file_parent
Giuseppe Bilotta3550ea72008-10-21 21:34:52 +02001365 # - hash or hash_base:/filename
Giuseppe Bilottac752a0e2008-11-02 10:21:39 +01001366 # - the snapshot_format as an appropriate suffix
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001367
1368 # When the script is the root DirectoryIndex for the domain,
1369 # $href here would be something like http://gitweb.example.com/
1370 # Thus, we strip any trailing / from $href, to spare us double
1371 # slashes in the final URL
1372 $href =~ s,/$,,;
1373
1374 # Then add the project name, if present
Jakub Narebski67976c62010-12-14 16:54:31 +01001375 $href .= "/".esc_path_info($params{'project'});
Martin Waitz9e756902006-10-01 23:57:48 +02001376 delete $params{'project'};
1377
Giuseppe Bilottac752a0e2008-11-02 10:21:39 +01001378 # since we destructively absorb parameters, we keep this
1379 # boolean that remembers if we're handling a snapshot
1380 my $is_snapshot = $params{'action'} eq 'snapshot';
1381
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001382 # Summary just uses the project path URL, any other action is
1383 # added to the URL
1384 if (defined $params{'action'}) {
Jakub Narebski67976c62010-12-14 16:54:31 +01001385 $href .= "/".esc_path_info($params{'action'})
1386 unless $params{'action'} eq 'summary';
Martin Waitz9e756902006-10-01 23:57:48 +02001387 delete $params{'action'};
1388 }
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001389
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001390 # Next, we put hash_parent_base:/file_parent..hash_base:/file_name,
1391 # stripping nonexistent or useless pieces
1392 $href .= "/" if ($params{'hash_base'} || $params{'hash_parent_base'}
1393 || $params{'hash_parent'} || $params{'hash'});
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001394 if (defined $params{'hash_base'}) {
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001395 if (defined $params{'hash_parent_base'}) {
Jakub Narebski67976c62010-12-14 16:54:31 +01001396 $href .= esc_path_info($params{'hash_parent_base'});
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001397 # skip the file_parent if it's the same as the file_name
Giuseppe Bilottab7da7212009-07-31 08:48:49 +02001398 if (defined $params{'file_parent'}) {
1399 if (defined $params{'file_name'} && $params{'file_parent'} eq $params{'file_name'}) {
1400 delete $params{'file_parent'};
1401 } elsif ($params{'file_parent'} !~ /\.\./) {
Jakub Narebski67976c62010-12-14 16:54:31 +01001402 $href .= ":/".esc_path_info($params{'file_parent'});
Giuseppe Bilottab7da7212009-07-31 08:48:49 +02001403 delete $params{'file_parent'};
1404 }
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001405 }
1406 $href .= "..";
1407 delete $params{'hash_parent'};
1408 delete $params{'hash_parent_base'};
1409 } elsif (defined $params{'hash_parent'}) {
Jakub Narebski67976c62010-12-14 16:54:31 +01001410 $href .= esc_path_info($params{'hash_parent'}). "..";
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001411 delete $params{'hash_parent'};
1412 }
1413
Jakub Narebski67976c62010-12-14 16:54:31 +01001414 $href .= esc_path_info($params{'hash_base'});
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001415 if (defined $params{'file_name'} && $params{'file_name'} !~ /\.\./) {
Jakub Narebski67976c62010-12-14 16:54:31 +01001416 $href .= ":/".esc_path_info($params{'file_name'});
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001417 delete $params{'file_name'};
1418 }
1419 delete $params{'hash'};
1420 delete $params{'hash_base'};
1421 } elsif (defined $params{'hash'}) {
Jakub Narebski67976c62010-12-14 16:54:31 +01001422 $href .= esc_path_info($params{'hash'});
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001423 delete $params{'hash'};
1424 }
Giuseppe Bilottac752a0e2008-11-02 10:21:39 +01001425
1426 # If the action was a snapshot, we can absorb the
1427 # snapshot_format parameter too
1428 if ($is_snapshot) {
1429 my $fmt = $params{'snapshot_format'};
1430 # snapshot_format should always be defined when href()
1431 # is called, but just in case some code forgets, we
1432 # fall back to the default
1433 $fmt ||= $snapshot_fmts[0];
1434 $href .= $known_snapshot_formats{$fmt}{'suffix'};
1435 delete $params{'snapshot_format'};
1436 }
Martin Waitz9e756902006-10-01 23:57:48 +02001437 }
1438
1439 # now encode the parameters explicitly
Jakub Narebski498fe002006-08-22 19:05:25 +02001440 my @result = ();
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001441 for (my $i = 0; $i < @cgi_param_mapping; $i += 2) {
1442 my ($name, $symbol) = ($cgi_param_mapping[$i], $cgi_param_mapping[$i+1]);
Jakub Narebski498fe002006-08-22 19:05:25 +02001443 if (defined $params{$name}) {
Jakub Narebskif22cca42007-07-29 01:04:09 +02001444 if (ref($params{$name}) eq "ARRAY") {
1445 foreach my $par (@{$params{$name}}) {
1446 push @result, $symbol . "=" . esc_param($par);
1447 }
1448 } else {
1449 push @result, $symbol . "=" . esc_param($params{$name});
1450 }
Jakub Narebski498fe002006-08-22 19:05:25 +02001451 }
1452 }
Martin Waitz9e756902006-10-01 23:57:48 +02001453 $href .= "?" . join(';', @result) if scalar @result;
1454
Jakub Narebski67976c62010-12-14 16:54:31 +01001455 # final transformation: trailing spaces must be escaped (URI-encoded)
1456 $href =~ s/(\s+)$/CGI::escape($1)/e;
1457
Kevin Cernekee5e96a842011-03-18 17:00:16 +01001458 if ($params{-anchor}) {
1459 $href .= "#".esc_param($params{-anchor});
1460 }
1461
Martin Waitz9e756902006-10-01 23:57:48 +02001462 return $href;
Martin Waitz06a9d862006-08-16 00:23:50 +02001463}
1464
1465
1466## ======================================================================
Jakub Narebski717b8312006-07-31 21:22:15 +02001467## validation, quoting/unquoting and escaping
1468
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001469sub is_valid_action {
1470 my $input = shift;
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001471 return undef unless exists $actions{$input};
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001472 return 1;
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001473}
1474
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001475sub is_valid_project {
1476 my $input = shift;
1477
1478 return unless defined $input;
1479 if (!is_valid_pathname($input) ||
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001480 !(-d "$projectroot/$input") ||
Alexander Gavrilovec26f092008-11-06 01:15:56 +03001481 !check_export_ok("$projectroot/$input") ||
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001482 ($strict_export && !project_in_list($input))) {
1483 return undef;
1484 } else {
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001485 return 1;
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001486 }
1487}
1488
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001489sub is_valid_pathname {
1490 my $input = shift;
Jakub Narebski717b8312006-07-31 21:22:15 +02001491
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001492 return undef unless defined $input;
Jakub Narebski24d06932006-09-26 01:57:02 +02001493 # no '.' or '..' as elements of path, i.e. no '.' nor '..'
1494 # at the beginning, at the end, and between slashes.
1495 # also this catches doubled slashes
1496 if ($input =~ m!(^|/)(|\.|\.\.)(/|$)!) {
1497 return undef;
1498 }
1499 # no null characters
1500 if ($input =~ m!\0!) {
1501 return undef;
1502 }
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001503 return 1;
Jakub Narebski24d06932006-09-26 01:57:02 +02001504}
1505
Krzesimir Nowakc0bc2262013-12-11 12:54:41 +01001506sub is_valid_ref_format {
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001507 my $input = shift;
Krzesimir Nowakc0bc2262013-12-11 12:54:41 +01001508
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001509 return undef unless defined $input;
Krzesimir Nowakc0bc2262013-12-11 12:54:41 +01001510 # restrictions on ref name according to git-check-ref-format
1511 if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) {
1512 return undef;
1513 }
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001514 return 1;
Krzesimir Nowakc0bc2262013-12-11 12:54:41 +01001515}
1516
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001517sub is_valid_refname {
1518 my $input = shift;
Jakub Narebski24d06932006-09-26 01:57:02 +02001519
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001520 return undef unless defined $input;
Jakub Narebski24d06932006-09-26 01:57:02 +02001521 # textual hashes are O.K.
Jakub Narebski717b8312006-07-31 21:22:15 +02001522 if ($input =~ m/^[0-9a-fA-F]{40}$/) {
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001523 return 1;
Jakub Narebski717b8312006-07-31 21:22:15 +02001524 }
Jakub Narebski24d06932006-09-26 01:57:02 +02001525 # it must be correct pathname
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001526 is_valid_pathname($input) or return undef;
Krzesimir Nowakc0bc2262013-12-11 12:54:41 +01001527 # check git-check-ref-format restrictions
Krzesimir Nowak23faf542013-12-11 12:54:42 +01001528 is_valid_ref_format($input) or return undef;
1529 return 1;
Jakub Narebski717b8312006-07-31 21:22:15 +02001530}
1531
Martin Koegler00f429a2007-06-03 17:42:44 +02001532# decode sequences of octets in utf8 into Perl's internal form,
1533# which is utf-8 with utf8 flag set if needed. gitweb writes out
1534# in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning
1535sub to_utf8 {
1536 my $str = shift;
Jakub Narebski1df48762010-02-07 21:52:25 +01001537 return undef unless defined $str;
Jakub Narebskib13e3ea2011-12-18 23:00:58 +01001538
1539 if (utf8::is_utf8($str) || utf8::decode($str)) {
İsmail Dönmeze5d3de52007-12-04 10:55:41 +02001540 return $str;
Martin Koegler00f429a2007-06-03 17:42:44 +02001541 } else {
1542 return decode($fallback_encoding, $str, Encode::FB_DEFAULT);
1543 }
1544}
1545
Kay Sievers232ff552005-11-24 16:56:55 +01001546# quote unsafe chars, but keep the slash, even when it's not
1547# correct, but quoted slashes look too horrible in bookmarks
1548sub esc_param {
Kay Sievers353347b2005-11-14 05:47:18 +01001549 my $str = shift;
Jakub Narebski1df48762010-02-07 21:52:25 +01001550 return undef unless defined $str;
Giuseppe Bilotta452e2252009-10-13 21:51:36 +02001551 $str =~ s/([^A-Za-z0-9\-_.~()\/:@ ]+)/CGI::escape($1)/eg;
Kay Sieversa9e60b72005-11-14 15:15:12 +01001552 $str =~ s/ /\+/g;
Kay Sievers353347b2005-11-14 05:47:18 +01001553 return $str;
1554}
1555
Jakub Narebski67976c62010-12-14 16:54:31 +01001556# the quoting rules for path_info fragment are slightly different
1557sub esc_path_info {
1558 my $str = shift;
1559 return undef unless defined $str;
1560
1561 # path_info doesn't treat '+' as space (specially), but '?' must be escaped
1562 $str =~ s/([^A-Za-z0-9\-_.~();\/;:@&= +]+)/CGI::escape($1)/eg;
1563
1564 return $str;
1565}
1566
Ralf Wildenhues22e5e582010-08-22 13:12:12 +02001567# quote unsafe chars in whole URL, so some characters cannot be quoted
Jakub Narebskif93bff82006-09-26 01:58:41 +02001568sub esc_url {
1569 my $str = shift;
Jakub Narebski1df48762010-02-07 21:52:25 +01001570 return undef unless defined $str;
Pavan Kumar Sunkara109988f2010-07-15 12:59:01 +05301571 $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&= ]+)/CGI::escape($1)/eg;
Jakub Narebskif93bff82006-09-26 01:58:41 +02001572 $str =~ s/ /\+/g;
1573 return $str;
1574}
1575
Jakub Narebski3017ed62010-12-15 00:34:01 +01001576# quote unsafe characters in HTML attributes
1577sub esc_attr {
1578
1579 # for XHTML conformance escaping '"' to '&quot;' is not enough
1580 return esc_html(@_);
1581}
1582
Kay Sievers232ff552005-11-24 16:56:55 +01001583# replace invalid utf8 character with SUBSTITUTION sequence
Jakub Narebski74fd8722009-05-07 19:11:29 +02001584sub esc_html {
Kay Sievers40c13812005-11-19 17:41:29 +01001585 my $str = shift;
Jakub Narebski6255ef02006-11-01 14:33:21 +01001586 my %opts = @_;
1587
Jakub Narebski1df48762010-02-07 21:52:25 +01001588 return undef unless defined $str;
1589
Martin Koegler00f429a2007-06-03 17:42:44 +02001590 $str = to_utf8($str);
Li Yangc390ae92007-03-06 11:58:56 +08001591 $str = $cgi->escapeHTML($str);
Jakub Narebski6255ef02006-11-01 14:33:21 +01001592 if ($opts{'-nbsp'}) {
1593 $str =~ s/ /&nbsp;/g;
1594 }
Junio C Hamano25ffbb22006-11-08 15:11:10 -08001595 $str =~ s|([[:cntrl:]])|(($1 ne "\t") ? quot_cec($1) : $1)|eg;
Kay Sievers40c13812005-11-19 17:41:29 +01001596 return $str;
1597}
1598
Jakub Narebski391862e2006-11-25 09:43:59 +01001599# quote control characters and escape filename to HTML
1600sub esc_path {
1601 my $str = shift;
1602 my %opts = @_;
1603
Jakub Narebski1df48762010-02-07 21:52:25 +01001604 return undef unless defined $str;
1605
Martin Koegler00f429a2007-06-03 17:42:44 +02001606 $str = to_utf8($str);
Li Yangc390ae92007-03-06 11:58:56 +08001607 $str = $cgi->escapeHTML($str);
Jakub Narebski391862e2006-11-25 09:43:59 +01001608 if ($opts{'-nbsp'}) {
1609 $str =~ s/ /&nbsp;/g;
1610 }
1611 $str =~ s|([[:cntrl:]])|quot_cec($1)|eg;
1612 return $str;
1613}
1614
Jakub Narebski08667862011-09-16 14:41:57 +02001615# Sanitize for use in XHTML + application/xml+xhtm (valid XML 1.0)
1616sub sanitize {
1617 my $str = shift;
1618
1619 return undef unless defined $str;
1620
1621 $str = to_utf8($str);
Orgad Shaneh0e901d22012-12-30 13:52:53 +02001622 $str =~ s|([[:cntrl:]])|(index("\t\n\r", $1) != -1 ? $1 : quot_cec($1))|eg;
Jakub Narebski08667862011-09-16 14:41:57 +02001623 return $str;
1624}
1625
Jakub Narebski391862e2006-11-25 09:43:59 +01001626# Make control characters "printable", using character escape codes (CEC)
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001627sub quot_cec {
1628 my $cntrl = shift;
Jakub Narebskic84c4832008-02-17 18:48:13 +01001629 my %opts = @_;
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001630 my %es = ( # character escape codes, aka escape sequences
Jakub Narebskic84c4832008-02-17 18:48:13 +01001631 "\t" => '\t', # tab (HT)
1632 "\n" => '\n', # line feed (LF)
1633 "\r" => '\r', # carrige return (CR)
1634 "\f" => '\f', # form feed (FF)
1635 "\b" => '\b', # backspace (BS)
1636 "\a" => '\a', # alarm (bell) (BEL)
1637 "\e" => '\e', # escape (ESC)
1638 "\013" => '\v', # vertical tab (VT)
1639 "\000" => '\0', # nul character (NUL)
1640 );
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001641 my $chr = ( (exists $es{$cntrl})
1642 ? $es{$cntrl}
Petr Baudis25dfd172008-10-01 22:11:54 +02001643 : sprintf('\%2x', ord($cntrl)) );
Jakub Narebskic84c4832008-02-17 18:48:13 +01001644 if ($opts{-nohtml}) {
1645 return $chr;
1646 } else {
1647 return "<span class=\"cntrl\">$chr</span>";
1648 }
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001649}
1650
Jakub Narebski391862e2006-11-25 09:43:59 +01001651# Alternatively use unicode control pictures codepoints,
1652# Unicode "printable representation" (PR)
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001653sub quot_upr {
1654 my $cntrl = shift;
Jakub Narebskic84c4832008-02-17 18:48:13 +01001655 my %opts = @_;
1656
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001657 my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
Jakub Narebskic84c4832008-02-17 18:48:13 +01001658 if ($opts{-nohtml}) {
1659 return $chr;
1660 } else {
1661 return "<span class=\"cntrl\">$chr</span>";
1662 }
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001663}
1664
Kay Sievers232ff552005-11-24 16:56:55 +01001665# git may return quoted and escaped filenames
1666sub unquote {
1667 my $str = shift;
Jakub Narebski403d0902006-11-08 11:48:56 +01001668
1669 sub unq {
1670 my $seq = shift;
1671 my %es = ( # character escape codes, aka escape sequences
1672 't' => "\t", # tab (HT, TAB)
1673 'n' => "\n", # newline (NL)
1674 'r' => "\r", # return (CR)
1675 'f' => "\f", # form feed (FF)
1676 'b' => "\b", # backspace (BS)
1677 'a' => "\a", # alarm (bell) (BEL)
1678 'e' => "\e", # escape (ESC)
1679 'v' => "\013", # vertical tab (VT)
1680 );
1681
1682 if ($seq =~ m/^[0-7]{1,3}$/) {
1683 # octal char sequence
1684 return chr(oct($seq));
1685 } elsif (exists $es{$seq}) {
1686 # C escape sequence, aka character escape code
Jakub Narebskic84c4832008-02-17 18:48:13 +01001687 return $es{$seq};
Jakub Narebski403d0902006-11-08 11:48:56 +01001688 }
1689 # quoted ordinary character
1690 return $seq;
1691 }
1692
Kay Sievers232ff552005-11-24 16:56:55 +01001693 if ($str =~ m/^"(.*)"$/) {
Jakub Narebski403d0902006-11-08 11:48:56 +01001694 # needs unquoting
Kay Sievers232ff552005-11-24 16:56:55 +01001695 $str = $1;
Jakub Narebski403d0902006-11-08 11:48:56 +01001696 $str =~ s/\\([^0-7]|[0-7]{1,3})/unq($1)/eg;
Kay Sievers232ff552005-11-24 16:56:55 +01001697 }
1698 return $str;
1699}
1700
Jakub Narebskif16db172006-08-06 02:08:31 +02001701# escape tabs (convert tabs to spaces)
1702sub untabify {
1703 my $line = shift;
1704
1705 while ((my $pos = index($line, "\t")) != -1) {
1706 if (my $count = (8 - ($pos % 8))) {
1707 my $spaces = ' ' x $count;
1708 $line =~ s/\t/$spaces/;
1709 }
1710 }
1711
1712 return $line;
1713}
1714
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +02001715sub project_in_list {
1716 my $project = shift;
1717 my @list = git_get_projects_list();
1718 return @list && scalar(grep { $_->{'path'} eq $project } @list);
1719}
1720
Jakub Narebski717b8312006-07-31 21:22:15 +02001721## ----------------------------------------------------------------------
1722## HTML aware string manipulation
1723
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001724# Try to chop given string on a word boundary between position
1725# $len and $len+$add_len. If there is no word boundary there,
1726# chop at $len+$add_len. Do not chop if chopped part plus ellipsis
1727# (marking chopped part) would be longer than given string.
Jakub Narebski717b8312006-07-31 21:22:15 +02001728sub chop_str {
1729 my $str = shift;
1730 my $len = shift;
1731 my $add_len = shift || 10;
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001732 my $where = shift || 'right'; # 'left' | 'center' | 'right'
Jakub Narebski717b8312006-07-31 21:22:15 +02001733
Anders Waldenborgdee27752008-05-21 13:44:43 +02001734 # Make sure perl knows it is utf8 encoded so we don't
1735 # cut in the middle of a utf8 multibyte char.
1736 $str = to_utf8($str);
1737
Jakub Narebski717b8312006-07-31 21:22:15 +02001738 # allow only $len chars, but don't cut a word if it would fit in $add_len
1739 # 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 +01001740 # remove chopped character entities entirely
1741
1742 # when chopping in the middle, distribute $len into left and right part
1743 # return early if chopping wouldn't make string shorter
1744 if ($where eq 'center') {
1745 return $str if ($len + 5 >= length($str)); # filler is length 5
1746 $len = int($len/2);
1747 } else {
1748 return $str if ($len + 4 >= length($str)); # filler is length 4
Jakub Narebski717b8312006-07-31 21:22:15 +02001749 }
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001750
1751 # regexps: ending and beginning with word part up to $add_len
1752 my $endre = qr/.{$len}\w{0,$add_len}/;
1753 my $begre = qr/\w{0,$add_len}.{$len}/;
1754
1755 if ($where eq 'left') {
1756 $str =~ m/^(.*?)($begre)$/;
1757 my ($lead, $body) = ($1, $2);
1758 if (length($lead) > 4) {
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001759 $lead = " ...";
1760 }
1761 return "$lead$body";
1762
1763 } elsif ($where eq 'center') {
1764 $str =~ m/^($endre)(.*)$/;
1765 my ($left, $str) = ($1, $2);
1766 $str =~ m/^(.*?)($begre)$/;
1767 my ($mid, $right) = ($1, $2);
1768 if (length($mid) > 5) {
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001769 $mid = " ... ";
1770 }
1771 return "$left$mid$right";
1772
1773 } else {
1774 $str =~ m/^($endre)(.*)$/;
1775 my $body = $1;
1776 my $tail = $2;
1777 if (length($tail) > 4) {
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001778 $tail = "... ";
1779 }
1780 return "$body$tail";
1781 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001782}
1783
David Symondsce58ec92007-10-23 11:31:22 +10001784# takes the same arguments as chop_str, but also wraps a <span> around the
1785# result with a title attribute if it does get chopped. Additionally, the
1786# string is HTML-escaped.
1787sub chop_and_escape_str {
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001788 my ($str) = @_;
David Symondsce58ec92007-10-23 11:31:22 +10001789
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001790 my $chopped = chop_str(@_);
Jürgen Kreileder168c1e02011-12-17 10:22:21 +01001791 $str = to_utf8($str);
David Symondsce58ec92007-10-23 11:31:22 +10001792 if ($chopped eq $str) {
1793 return esc_html($chopped);
1794 } else {
Jakub Narebski14afe772009-05-22 17:35:46 +02001795 $str =~ s/[[:cntrl:]]/?/g;
Jakub Narebski850b90a2008-02-16 23:07:46 +01001796 return $cgi->span({-title=>$str}, esc_html($chopped));
David Symondsce58ec92007-10-23 11:31:22 +10001797 }
1798}
1799
Jakub Narebski337da8d2012-02-27 02:55:19 +01001800# Highlight selected fragments of string, using given CSS class,
1801# and escape HTML. It is assumed that fragments do not overlap.
1802# Regions are passed as list of pairs (array references).
1803#
1804# Example: esc_html_hl_regions("foobar", "mark", [ 0, 3 ]) returns
1805# '<span class="mark">foo</span>bar'
1806sub esc_html_hl_regions {
1807 my ($str, $css_class, @sel) = @_;
Jakub Narębski9768a9d2012-04-11 23:18:39 +02001808 my %opts = grep { ref($_) ne 'ARRAY' } @sel;
1809 @sel = grep { ref($_) eq 'ARRAY' } @sel;
1810 return esc_html($str, %opts) unless @sel;
Jakub Narebski337da8d2012-02-27 02:55:19 +01001811
1812 my $out = '';
1813 my $pos = 0;
1814
1815 for my $s (@sel) {
Michał Kiedrowiczce61fb92012-04-11 23:18:37 +02001816 my ($begin, $end) = @$s;
Jakub Narebski337da8d2012-02-27 02:55:19 +01001817
Michał Kiedrowiczcbbea3d2012-04-11 23:18:38 +02001818 # Don't create empty <span> elements.
1819 next if $end <= $begin;
1820
Jakub Narębski9768a9d2012-04-11 23:18:39 +02001821 my $escaped = esc_html(substr($str, $begin, $end - $begin),
1822 %opts);
Michał Kiedrowiczce61fb92012-04-11 23:18:37 +02001823
Jakub Narębski9768a9d2012-04-11 23:18:39 +02001824 $out .= esc_html(substr($str, $pos, $begin - $pos), %opts)
Michał Kiedrowiczce61fb92012-04-11 23:18:37 +02001825 if ($begin - $pos > 0);
1826 $out .= $cgi->span({-class => $css_class}, $escaped);
1827
1828 $pos = $end;
Jakub Narebski337da8d2012-02-27 02:55:19 +01001829 }
Jakub Narębski9768a9d2012-04-11 23:18:39 +02001830 $out .= esc_html(substr($str, $pos), %opts)
Jakub Narebski337da8d2012-02-27 02:55:19 +01001831 if ($pos < length($str));
1832
1833 return $out;
1834}
1835
Jakub Narebskie607b792012-02-27 02:55:22 +01001836# return positions of beginning and end of each match
1837sub matchpos_list {
Jakub Narebski337da8d2012-02-27 02:55:19 +01001838 my ($str, $regexp) = @_;
Jakub Narebskie607b792012-02-27 02:55:22 +01001839 return unless (defined $str && defined $regexp);
Jakub Narebski337da8d2012-02-27 02:55:19 +01001840
1841 my @matches;
1842 while ($str =~ /$regexp/g) {
1843 push @matches, [$-[0], $+[0]];
1844 }
Jakub Narebskie607b792012-02-27 02:55:22 +01001845 return @matches;
1846}
1847
1848# highlight match (if any), and escape HTML
1849sub esc_html_match_hl {
1850 my ($str, $regexp) = @_;
1851 return esc_html($str) unless defined $regexp;
1852
1853 my @matches = matchpos_list($str, $regexp);
Jakub Narebski337da8d2012-02-27 02:55:19 +01001854 return esc_html($str) unless @matches;
1855
1856 return esc_html_hl_regions($str, 'match', @matches);
1857}
1858
Jakub Narebskie607b792012-02-27 02:55:22 +01001859
1860# highlight match (if any) of shortened string, and escape HTML
1861sub esc_html_match_hl_chopped {
1862 my ($str, $chopped, $regexp) = @_;
1863 return esc_html_match_hl($str, $regexp) unless defined $chopped;
1864
1865 my @matches = matchpos_list($str, $regexp);
1866 return esc_html($chopped) unless @matches;
1867
1868 # filter matches so that we mark chopped string
1869 my $tail = "... "; # see chop_str
1870 unless ($chopped =~ s/\Q$tail\E$//) {
1871 $tail = '';
1872 }
1873 my $chop_len = length($chopped);
1874 my $tail_len = length($tail);
1875 my @filtered;
1876
1877 for my $m (@matches) {
1878 if ($m->[0] > $chop_len) {
1879 push @filtered, [ $chop_len, $chop_len + $tail_len ] if ($tail_len > 0);
1880 last;
1881 } elsif ($m->[1] > $chop_len) {
1882 push @filtered, [ $m->[0], $chop_len + $tail_len ];
1883 last;
1884 }
1885 push @filtered, $m;
1886 }
1887
1888 return esc_html_hl_regions($chopped . $tail, 'match', @filtered);
1889}
1890
Jakub Narebski717b8312006-07-31 21:22:15 +02001891## ----------------------------------------------------------------------
1892## functions returning short strings
1893
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00001894# CSS class for given age value (in seconds)
1895sub age_class {
1896 my $age = shift;
1897
Jakub Narebski785cdea2007-05-13 12:39:22 +02001898 if (!defined $age) {
1899 return "noage";
1900 } elsif ($age < 60*60*2) {
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00001901 return "age0";
1902 } elsif ($age < 60*60*24*2) {
1903 return "age1";
1904 } else {
1905 return "age2";
1906 }
1907}
1908
Jakub Narebski717b8312006-07-31 21:22:15 +02001909# convert age in seconds to "nn units ago" string
1910sub age_string {
1911 my $age = shift;
1912 my $age_str;
1913
1914 if ($age > 60*60*24*365*2) {
1915 $age_str = (int $age/60/60/24/365);
1916 $age_str .= " years ago";
1917 } elsif ($age > 60*60*24*(365/12)*2) {
1918 $age_str = int $age/60/60/24/(365/12);
1919 $age_str .= " months ago";
1920 } elsif ($age > 60*60*24*7*2) {
1921 $age_str = int $age/60/60/24/7;
1922 $age_str .= " weeks ago";
1923 } elsif ($age > 60*60*24*2) {
1924 $age_str = int $age/60/60/24;
1925 $age_str .= " days ago";
1926 } elsif ($age > 60*60*2) {
1927 $age_str = int $age/60/60;
1928 $age_str .= " hours ago";
1929 } elsif ($age > 60*2) {
1930 $age_str = int $age/60;
1931 $age_str .= " min ago";
1932 } elsif ($age > 2) {
1933 $age_str = int $age;
1934 $age_str .= " sec ago";
1935 } else {
1936 $age_str .= " right now";
1937 }
1938 return $age_str;
1939}
1940
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001941use constant {
1942 S_IFINVALID => 0030000,
1943 S_IFGITLINK => 0160000,
1944};
1945
1946# submodule/subproject, a commit object reference
Jakub Narebski74fd8722009-05-07 19:11:29 +02001947sub S_ISGITLINK {
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001948 my $mode = shift;
1949
1950 return (($mode & S_IFMT) == S_IFGITLINK)
1951}
1952
Jakub Narebski717b8312006-07-31 21:22:15 +02001953# convert file mode in octal to symbolic file mode string
1954sub mode_str {
1955 my $mode = oct shift;
1956
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001957 if (S_ISGITLINK($mode)) {
1958 return 'm---------';
1959 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001960 return 'drwxr-xr-x';
1961 } elsif (S_ISLNK($mode)) {
1962 return 'lrwxrwxrwx';
1963 } elsif (S_ISREG($mode)) {
1964 # git cares only about the executable bit
1965 if ($mode & S_IXUSR) {
1966 return '-rwxr-xr-x';
1967 } else {
1968 return '-rw-r--r--';
1969 };
1970 } else {
1971 return '----------';
1972 }
1973}
1974
1975# convert file mode in octal to file type string
1976sub file_type {
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02001977 my $mode = shift;
1978
1979 if ($mode !~ m/^[0-7]+$/) {
1980 return $mode;
1981 } else {
1982 $mode = oct $mode;
1983 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001984
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001985 if (S_ISGITLINK($mode)) {
1986 return "submodule";
1987 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001988 return "directory";
1989 } elsif (S_ISLNK($mode)) {
1990 return "symlink";
1991 } elsif (S_ISREG($mode)) {
1992 return "file";
1993 } else {
1994 return "unknown";
1995 }
1996}
1997
Jakub Narebski744d0ac2006-11-08 17:59:41 +01001998# convert file mode in octal to file type description string
1999sub file_type_long {
2000 my $mode = shift;
2001
2002 if ($mode !~ m/^[0-7]+$/) {
2003 return $mode;
2004 } else {
2005 $mode = oct $mode;
2006 }
2007
Jakub Narebski01ac1e32007-07-28 16:27:31 +02002008 if (S_ISGITLINK($mode)) {
2009 return "submodule";
2010 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski744d0ac2006-11-08 17:59:41 +01002011 return "directory";
2012 } elsif (S_ISLNK($mode)) {
2013 return "symlink";
2014 } elsif (S_ISREG($mode)) {
2015 if ($mode & S_IXUSR) {
2016 return "executable";
2017 } else {
2018 return "file";
2019 };
2020 } else {
2021 return "unknown";
2022 }
2023}
2024
2025
Jakub Narebski717b8312006-07-31 21:22:15 +02002026## ----------------------------------------------------------------------
2027## functions returning short HTML fragments, or transforming HTML fragments
Pavel Roskin3dff5372007-02-03 23:49:16 -05002028## which don't belong to other sections
Jakub Narebski717b8312006-07-31 21:22:15 +02002029
Junio C Hamano225932e2006-11-09 00:57:13 -08002030# format line of commit message.
Jakub Narebski717b8312006-07-31 21:22:15 +02002031sub format_log_line_html {
2032 my $line = shift;
2033
Junio C Hamano225932e2006-11-09 00:57:13 -08002034 $line = esc_html($line, -nbsp=>1);
Marcel M. Cary7d233de2009-02-17 19:00:43 -08002035 $line =~ s{\b([0-9a-fA-F]{8,40})\b}{
2036 $cgi->a({-href => href(action=>"object", hash=>$1),
2037 -class => "text"}, $1);
2038 }eg;
2039
Jakub Narebski717b8312006-07-31 21:22:15 +02002040 return $line;
2041}
2042
2043# format marker of refs pointing to given object
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02002044
2045# the destination action is chosen based on object type and current context:
2046# - for annotated tags, we choose the tag view unless it's the current view
2047# already, in which case we go to shortlog view
2048# - for other refs, we keep the current view if we're in history, shortlog or
2049# log view, and select shortlog otherwise
Jakub Narebski847e01f2006-08-14 02:05:47 +02002050sub format_ref_marker {
Jakub Narebski717b8312006-07-31 21:22:15 +02002051 my ($refs, $id) = @_;
Jakub Narebskid294e1c2006-08-14 02:14:20 +02002052 my $markers = '';
Jakub Narebski717b8312006-07-31 21:22:15 +02002053
2054 if (defined $refs->{$id}) {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02002055 foreach my $ref (@{$refs->{$id}}) {
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02002056 # this code exploits the fact that non-lightweight tags are the
2057 # only indirect objects, and that they are the only objects for which
2058 # we want to use tag instead of shortlog as action
Jakub Narebskid294e1c2006-08-14 02:14:20 +02002059 my ($type, $name) = qw();
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02002060 my $indirect = ($ref =~ s/\^\{\}$//);
Jakub Narebskid294e1c2006-08-14 02:14:20 +02002061 # e.g. tags/v2.6.11 or heads/next
2062 if ($ref =~ m!^(.*?)s?/(.*)$!) {
2063 $type = $1;
2064 $name = $2;
2065 } else {
2066 $type = "ref";
2067 $name = $ref;
2068 }
2069
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02002070 my $class = $type;
2071 $class .= " indirect" if $indirect;
2072
2073 my $dest_action = "shortlog";
2074
2075 if ($indirect) {
2076 $dest_action = "tag" unless $action eq "tag";
2077 } elsif ($action =~ /^(history|(short)?log)$/) {
2078 $dest_action = $action;
2079 }
2080
2081 my $dest = "";
2082 $dest .= "refs/" unless $ref =~ m!^refs/!;
2083 $dest .= $ref;
2084
2085 my $link = $cgi->a({
2086 -href => href(
2087 action=>$dest_action,
2088 hash=>$dest
2089 )}, $name);
2090
Jakub Narebski3017ed62010-12-15 00:34:01 +01002091 $markers .= " <span class=\"".esc_attr($class)."\" title=\"".esc_attr($ref)."\">" .
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02002092 $link . "</span>";
Jakub Narebskid294e1c2006-08-14 02:14:20 +02002093 }
2094 }
2095
2096 if ($markers) {
2097 return ' <span class="refs">'. $markers . '</span>';
Jakub Narebski717b8312006-07-31 21:22:15 +02002098 } else {
2099 return "";
2100 }
2101}
2102
Jakub Narebski17d07442006-08-14 02:08:27 +02002103# format, perhaps shortened and with markers, title line
2104sub format_subject_html {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02002105 my ($long, $short, $href, $extra) = @_;
Jakub Narebski17d07442006-08-14 02:08:27 +02002106 $extra = '' unless defined($extra);
2107
2108 if (length($short) < length($long)) {
Jakub Narebski14afe772009-05-22 17:35:46 +02002109 $long =~ s/[[:cntrl:]]/?/g;
Jakub Narebski7c278012006-08-22 12:02:48 +02002110 return $cgi->a({-href => $href, -class => "list subject",
Martin Koegler00f429a2007-06-03 17:42:44 +02002111 -title => to_utf8($long)},
Giuseppe Bilotta01b89f02009-08-23 10:28:09 +02002112 esc_html($short)) . $extra;
Jakub Narebski17d07442006-08-14 02:08:27 +02002113 } else {
Jakub Narebski7c278012006-08-22 12:02:48 +02002114 return $cgi->a({-href => $href, -class => "list subject"},
Giuseppe Bilotta01b89f02009-08-23 10:28:09 +02002115 esc_html($long)) . $extra;
Jakub Narebski17d07442006-08-14 02:08:27 +02002116 }
2117}
2118
Giuseppe Bilotta5a371b72009-06-30 00:00:52 +02002119# Rather than recomputing the url for an email multiple times, we cache it
2120# after the first hit. This gives a visible benefit in views where the avatar
2121# for the same email is used repeatedly (e.g. shortlog).
2122# The cache is shared by all avatar engines (currently gravatar only), which
2123# are free to use it as preferred. Since only one avatar engine is used for any
2124# given page, there's no risk for cache conflicts.
2125our %avatar_cache = ();
2126
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +02002127# Compute the picon url for a given email, by using the picon search service over at
2128# http://www.cs.indiana.edu/picons/search.html
2129sub picon_url {
2130 my $email = lc shift;
2131 if (!$avatar_cache{$email}) {
2132 my ($user, $domain) = split('@', $email);
2133 $avatar_cache{$email} =
Andrej E Baranov57485582013-01-29 00:41:32 +01002134 "//www.cs.indiana.edu/cgi-pub/kinzler/piconsearch.cgi/" .
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +02002135 "$domain/$user/" .
2136 "users+domains+unknown/up/single";
2137 }
2138 return $avatar_cache{$email};
2139}
2140
Giuseppe Bilotta5a371b72009-06-30 00:00:52 +02002141# Compute the gravatar url for a given email, if it's not in the cache already.
2142# Gravatar stores only the part of the URL before the size, since that's the
2143# one computationally more expensive. This also allows reuse of the cache for
2144# different sizes (for this particular engine).
2145sub gravatar_url {
2146 my $email = lc shift;
2147 my $size = shift;
2148 $avatar_cache{$email} ||=
Andrej E Baranov57485582013-01-29 00:41:32 +01002149 "//www.gravatar.com/avatar/" .
Giuseppe Bilotta5a371b72009-06-30 00:00:52 +02002150 Digest::MD5::md5_hex($email) . "?s=";
2151 return $avatar_cache{$email} . $size;
2152}
2153
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02002154# Insert an avatar for the given $email at the given $size if the feature
2155# is enabled.
2156sub git_get_avatar {
2157 my ($email, %opts) = @_;
2158 my $pre_white = ($opts{-pad_before} ? "&nbsp;" : "");
2159 my $post_white = ($opts{-pad_after} ? "&nbsp;" : "");
2160 $opts{-size} ||= 'default';
2161 my $size = $avatar_size{$opts{-size}} || $avatar_size{'default'};
2162 my $url = "";
2163 if ($git_avatar eq 'gravatar') {
Giuseppe Bilotta5a371b72009-06-30 00:00:52 +02002164 $url = gravatar_url($email, $size);
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +02002165 } elsif ($git_avatar eq 'picon') {
2166 $url = picon_url($email);
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02002167 }
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +02002168 # Other providers can be added by extending the if chain, defining $url
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02002169 # as needed. If no variant puts something in $url, we assume avatars
2170 # are completely disabled/unavailable.
2171 if ($url) {
2172 return $pre_white .
2173 "<img width=\"$size\" " .
2174 "class=\"avatar\" " .
Jakub Narebski3017ed62010-12-15 00:34:01 +01002175 "src=\"".esc_url($url)."\" " .
Giuseppe Bilotta7d25ef42009-06-30 00:00:54 +02002176 "alt=\"\" " .
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02002177 "/>" . $post_white;
2178 } else {
2179 return "";
2180 }
2181}
2182
Stephen Boyde133d652009-10-15 21:14:59 -07002183sub format_search_author {
2184 my ($author, $searchtype, $displaytext) = @_;
2185 my $have_search = gitweb_check_feature('search');
2186
2187 if ($have_search) {
2188 my $performed = "";
2189 if ($searchtype eq 'author') {
2190 $performed = "authored";
2191 } elsif ($searchtype eq 'committer') {
2192 $performed = "committed";
2193 }
2194
2195 return $cgi->a({-href => href(action=>"search", hash=>$hash,
2196 searchtext=>$author,
2197 searchtype=>$searchtype), class=>"list",
2198 title=>"Search for commits $performed by $author"},
2199 $displaytext);
2200
2201 } else {
2202 return $displaytext;
2203 }
2204}
2205
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02002206# format the author name of the given commit with the given tag
2207# the author name is chopped and escaped according to the other
2208# optional parameters (see chop_str).
2209sub format_author_html {
2210 my $tag = shift;
2211 my $co = shift;
2212 my $author = chop_and_escape_str($co->{'author_name'}, @_);
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02002213 return "<$tag class=\"author\">" .
Stephen Boyde133d652009-10-15 21:14:59 -07002214 format_search_author($co->{'author_name'}, "author",
2215 git_get_avatar($co->{'author_email'}, -pad_after => 1) .
2216 $author) .
2217 "</$tag>";
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02002218}
2219
Jakub Narebski90921742007-06-08 13:27:42 +02002220# format git diff header line, i.e. "diff --(git|combined|cc) ..."
2221sub format_git_diff_header_line {
2222 my $line = shift;
2223 my $diffinfo = shift;
2224 my ($from, $to) = @_;
2225
2226 if ($diffinfo->{'nparents'}) {
2227 # combined diff
2228 $line =~ s!^(diff (.*?) )"?.*$!$1!;
2229 if ($to->{'href'}) {
2230 $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
2231 esc_path($to->{'file'}));
2232 } else { # file was deleted (no href)
2233 $line .= esc_path($to->{'file'});
2234 }
2235 } else {
2236 # "ordinary" diff
2237 $line =~ s!^(diff (.*?) )"?a/.*$!$1!;
2238 if ($from->{'href'}) {
2239 $line .= $cgi->a({-href => $from->{'href'}, -class => "path"},
2240 'a/' . esc_path($from->{'file'}));
2241 } else { # file was added (no href)
2242 $line .= 'a/' . esc_path($from->{'file'});
2243 }
2244 $line .= ' ';
2245 if ($to->{'href'}) {
2246 $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
2247 'b/' . esc_path($to->{'file'}));
2248 } else { # file was deleted
2249 $line .= 'b/' . esc_path($to->{'file'});
2250 }
2251 }
2252
2253 return "<div class=\"diff header\">$line</div>\n";
2254}
2255
2256# format extended diff header line, before patch itself
2257sub format_extended_diff_header_line {
2258 my $line = shift;
2259 my $diffinfo = shift;
2260 my ($from, $to) = @_;
2261
2262 # match <path>
2263 if ($line =~ s!^((copy|rename) from ).*$!$1! && $from->{'href'}) {
2264 $line .= $cgi->a({-href=>$from->{'href'}, -class=>"path"},
2265 esc_path($from->{'file'}));
2266 }
2267 if ($line =~ s!^((copy|rename) to ).*$!$1! && $to->{'href'}) {
2268 $line .= $cgi->a({-href=>$to->{'href'}, -class=>"path"},
2269 esc_path($to->{'file'}));
2270 }
2271 # match single <mode>
2272 if ($line =~ m/\s(\d{6})$/) {
2273 $line .= '<span class="info"> (' .
2274 file_type_long($1) .
2275 ')</span>';
2276 }
2277 # match <hash>
2278 if ($line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
2279 # can match only for combined diff
2280 $line = 'index ';
2281 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
2282 if ($from->{'href'}[$i]) {
2283 $line .= $cgi->a({-href=>$from->{'href'}[$i],
2284 -class=>"hash"},
2285 substr($diffinfo->{'from_id'}[$i],0,7));
2286 } else {
2287 $line .= '0' x 7;
2288 }
2289 # separator
2290 $line .= ',' if ($i < $diffinfo->{'nparents'} - 1);
2291 }
2292 $line .= '..';
2293 if ($to->{'href'}) {
2294 $line .= $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
2295 substr($diffinfo->{'to_id'},0,7));
2296 } else {
2297 $line .= '0' x 7;
2298 }
2299
2300 } elsif ($line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
2301 # can match only for ordinary diff
2302 my ($from_link, $to_link);
2303 if ($from->{'href'}) {
2304 $from_link = $cgi->a({-href=>$from->{'href'}, -class=>"hash"},
2305 substr($diffinfo->{'from_id'},0,7));
2306 } else {
2307 $from_link = '0' x 7;
2308 }
2309 if ($to->{'href'}) {
2310 $to_link = $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
2311 substr($diffinfo->{'to_id'},0,7));
2312 } else {
2313 $to_link = '0' x 7;
2314 }
2315 my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
2316 $line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
2317 }
2318
2319 return $line . "<br/>\n";
2320}
2321
2322# format from-file/to-file diff header
2323sub format_diff_from_to_header {
Jakub Narebski91af4ce2007-06-08 13:32:44 +02002324 my ($from_line, $to_line, $diffinfo, $from, $to, @parents) = @_;
Jakub Narebski90921742007-06-08 13:27:42 +02002325 my $line;
2326 my $result = '';
2327
2328 $line = $from_line;
2329 #assert($line =~ m/^---/) if DEBUG;
Jakub Narebskideaa01a2007-06-08 13:29:49 +02002330 # no extra formatting for "^--- /dev/null"
2331 if (! $diffinfo->{'nparents'}) {
2332 # ordinary (single parent) diff
2333 if ($line =~ m!^--- "?a/!) {
2334 if ($from->{'href'}) {
2335 $line = '--- a/' .
2336 $cgi->a({-href=>$from->{'href'}, -class=>"path"},
2337 esc_path($from->{'file'}));
2338 } else {
2339 $line = '--- a/' .
2340 esc_path($from->{'file'});
2341 }
2342 }
2343 $result .= qq!<div class="diff from_file">$line</div>\n!;
2344
2345 } else {
2346 # combined diff (merge commit)
2347 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
2348 if ($from->{'href'}[$i]) {
2349 $line = '--- ' .
Jakub Narebski91af4ce2007-06-08 13:32:44 +02002350 $cgi->a({-href=>href(action=>"blobdiff",
2351 hash_parent=>$diffinfo->{'from_id'}[$i],
2352 hash_parent_base=>$parents[$i],
2353 file_parent=>$from->{'file'}[$i],
2354 hash=>$diffinfo->{'to_id'},
2355 hash_base=>$hash,
2356 file_name=>$to->{'file'}),
2357 -class=>"path",
2358 -title=>"diff" . ($i+1)},
2359 $i+1) .
2360 '/' .
Jakub Narebskideaa01a2007-06-08 13:29:49 +02002361 $cgi->a({-href=>$from->{'href'}[$i], -class=>"path"},
2362 esc_path($from->{'file'}[$i]));
2363 } else {
2364 $line = '--- /dev/null';
2365 }
2366 $result .= qq!<div class="diff from_file">$line</div>\n!;
Jakub Narebski90921742007-06-08 13:27:42 +02002367 }
2368 }
Jakub Narebski90921742007-06-08 13:27:42 +02002369
2370 $line = $to_line;
2371 #assert($line =~ m/^\+\+\+/) if DEBUG;
2372 # no extra formatting for "^+++ /dev/null"
2373 if ($line =~ m!^\+\+\+ "?b/!) {
2374 if ($to->{'href'}) {
2375 $line = '+++ b/' .
2376 $cgi->a({-href=>$to->{'href'}, -class=>"path"},
2377 esc_path($to->{'file'}));
2378 } else {
2379 $line = '+++ b/' .
2380 esc_path($to->{'file'});
2381 }
2382 }
2383 $result .= qq!<div class="diff to_file">$line</div>\n!;
2384
2385 return $result;
2386}
2387
Jakub Narebskicd030c32007-06-08 13:33:28 +02002388# create note for patch simplified by combined diff
2389sub format_diff_cc_simplified {
2390 my ($diffinfo, @parents) = @_;
2391 my $result = '';
2392
2393 $result .= "<div class=\"diff header\">" .
2394 "diff --cc ";
2395 if (!is_deleted($diffinfo)) {
2396 $result .= $cgi->a({-href => href(action=>"blob",
2397 hash_base=>$hash,
2398 hash=>$diffinfo->{'to_id'},
2399 file_name=>$diffinfo->{'to_file'}),
2400 -class => "path"},
2401 esc_path($diffinfo->{'to_file'}));
2402 } else {
2403 $result .= esc_path($diffinfo->{'to_file'});
2404 }
2405 $result .= "</div>\n" . # class="diff header"
2406 "<div class=\"diff nodifferences\">" .
2407 "Simple merge" .
2408 "</div>\n"; # class="diff nodifferences"
2409
2410 return $result;
2411}
2412
Jakub Narebski20a864c2011-10-31 00:36:20 +01002413sub diff_line_class {
2414 my ($line, $from, $to) = @_;
2415
2416 # ordinary diff
2417 my $num_sign = 1;
2418 # combined diff
2419 if ($from && $to && ref($from->{'href'}) eq "ARRAY") {
2420 $num_sign = scalar @{$from->{'href'}};
2421 }
2422
2423 my @diff_line_classifier = (
2424 { regexp => qr/^\@\@{$num_sign} /, class => "chunk_header"},
2425 { regexp => qr/^\\/, class => "incomplete" },
2426 { regexp => qr/^ {$num_sign}/, class => "ctx" },
2427 # classifier for context must come before classifier add/rem,
2428 # or we would have to use more complicated regexp, for example
2429 # qr/(?= {0,$m}\+)[+ ]{$num_sign}/, where $m = $num_sign - 1;
2430 { regexp => qr/^[+ ]{$num_sign}/, class => "add" },
2431 { regexp => qr/^[- ]{$num_sign}/, class => "rem" },
2432 );
2433 for my $clsfy (@diff_line_classifier) {
2434 return $clsfy->{'class'}
2435 if ($line =~ $clsfy->{'regexp'});
2436 }
2437
2438 # fallback
2439 return "";
2440}
2441
Jakub Narebskif1310cf2011-10-31 00:36:21 +01002442# assumes that $from and $to are defined and correctly filled,
2443# and that $line holds a line of chunk header for unified diff
2444sub format_unidiff_chunk_header {
2445 my ($line, $from, $to) = @_;
2446
2447 my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
2448 $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
2449
2450 $from_lines = 0 unless defined $from_lines;
2451 $to_lines = 0 unless defined $to_lines;
2452
2453 if ($from->{'href'}) {
2454 $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
2455 -class=>"list"}, $from_text);
2456 }
2457 if ($to->{'href'}) {
2458 $to_text = $cgi->a({-href=>"$to->{'href'}#l$to_start",
2459 -class=>"list"}, $to_text);
2460 }
2461 $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
2462 "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
2463 return $line;
2464}
2465
2466# assumes that $from and $to are defined and correctly filled,
2467# and that $line holds a line of chunk header for combined diff
2468sub format_cc_diff_chunk_header {
2469 my ($line, $from, $to) = @_;
2470
2471 my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
2472 my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
2473
2474 @from_text = split(' ', $ranges);
2475 for (my $i = 0; $i < @from_text; ++$i) {
2476 ($from_start[$i], $from_nlines[$i]) =
2477 (split(',', substr($from_text[$i], 1)), 0);
2478 }
2479
2480 $to_text = pop @from_text;
2481 $to_start = pop @from_start;
2482 $to_nlines = pop @from_nlines;
2483
2484 $line = "<span class=\"chunk_info\">$prefix ";
2485 for (my $i = 0; $i < @from_text; ++$i) {
2486 if ($from->{'href'}[$i]) {
2487 $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
2488 -class=>"list"}, $from_text[$i]);
2489 } else {
2490 $line .= $from_text[$i];
2491 }
2492 $line .= " ";
2493 }
2494 if ($to->{'href'}) {
2495 $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
2496 -class=>"list"}, $to_text);
2497 } else {
2498 $line .= $to_text;
2499 }
2500 $line .= " $prefix</span>" .
2501 "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
2502 return $line;
2503}
2504
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01002505# process patch (diff) line (not to be used for diff headers),
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02002506# returning HTML-formatted (but not wrapped) line.
2507# If the line is passed as a reference, it is treated as HTML and not
2508# esc_html()'ed.
Michał Kiedrowiczf4a81022012-04-11 23:18:42 +02002509sub format_diff_line {
2510 my ($line, $diff_class, $from, $to) = @_;
Jakub Narebski20a864c2011-10-31 00:36:20 +01002511
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02002512 if (ref($line)) {
2513 $line = $$line;
Michał Kiedrowiczf4a81022012-04-11 23:18:42 +02002514 } else {
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02002515 chomp $line;
2516 $line = untabify($line);
Jakub Narebskieee08902006-08-24 00:15:14 +02002517
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02002518 if ($from && $to && $line =~ m/^\@{2} /) {
2519 $line = format_unidiff_chunk_header($line, $from, $to);
2520 } elsif ($from && $to && $line =~ m/^\@{3}/) {
2521 $line = format_cc_diff_chunk_header($line, $from, $to);
2522 } else {
2523 $line = esc_html($line, -nbsp=>1);
2524 }
Jakub Narebski59e3b142006-11-18 23:35:40 +01002525 }
Michał Kiedrowiczf4a81022012-04-11 23:18:42 +02002526
2527 my $diff_classes = "diff";
2528 $diff_classes .= " $diff_class" if ($diff_class);
2529 $line = "<div class=\"$diff_classes\">$line</div>\n";
2530
2531 return $line;
Jakub Narebskieee08902006-08-24 00:15:14 +02002532}
2533
Matt McCutchena3c8ab32007-07-22 01:30:27 +02002534# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
2535# linked. Pass the hash of the tree/commit to snapshot.
2536sub format_snapshot_links {
2537 my ($hash) = @_;
Matt McCutchena3c8ab32007-07-22 01:30:27 +02002538 my $num_fmts = @snapshot_fmts;
2539 if ($num_fmts > 1) {
2540 # A parenthesized list of links bearing format names.
Jakub Narebskia7817852007-07-22 23:41:20 +02002541 # e.g. "snapshot (_tar.gz_ _zip_)"
Matt McCutchena3c8ab32007-07-22 01:30:27 +02002542 return "snapshot (" . join(' ', map
2543 $cgi->a({
2544 -href => href(
2545 action=>"snapshot",
2546 hash=>$hash,
2547 snapshot_format=>$_
2548 )
2549 }, $known_snapshot_formats{$_}{'display'})
2550 , @snapshot_fmts) . ")";
2551 } elsif ($num_fmts == 1) {
2552 # A single "snapshot" link whose tooltip bears the format name.
Jakub Narebskia7817852007-07-22 23:41:20 +02002553 # i.e. "_snapshot_"
Matt McCutchena3c8ab32007-07-22 01:30:27 +02002554 my ($fmt) = @snapshot_fmts;
Jakub Narebskia7817852007-07-22 23:41:20 +02002555 return
2556 $cgi->a({
Matt McCutchena3c8ab32007-07-22 01:30:27 +02002557 -href => href(
2558 action=>"snapshot",
2559 hash=>$hash,
2560 snapshot_format=>$fmt
2561 ),
2562 -title => "in format: $known_snapshot_formats{$fmt}{'display'}"
2563 }, "snapshot");
2564 } else { # $num_fmts == 0
2565 return undef;
2566 }
2567}
2568
Jakub Narebski35621982008-04-20 22:09:48 +02002569## ......................................................................
2570## functions returning values to be passed, perhaps after some
2571## transformation, to other functions; e.g. returning arguments to href()
2572
2573# returns hash to be passed to href to generate gitweb URL
2574# in -title key it returns description of link
2575sub get_feed_info {
2576 my $format = shift || 'Atom';
2577 my %res = (action => lc($format));
Krzesimir Nowak8d646a92013-12-11 12:54:43 +01002578 my $matched_ref = 0;
Jakub Narebski35621982008-04-20 22:09:48 +02002579
2580 # feed links are possible only for project views
2581 return unless (defined $project);
2582 # some views should link to OPML, or to generic project feed,
2583 # or don't have specific feed yet (so they should use generic)
Jakub Narebski18ab83e2012-01-07 11:47:38 +01002584 return if (!$action || $action =~ /^(?:tags|heads|forks|tag|search)$/x);
Jakub Narebski35621982008-04-20 22:09:48 +02002585
Krzesimir Nowak8d646a92013-12-11 12:54:43 +01002586 my $branch = undef;
2587 # branches refs uses 'refs/' + $get_branch_refs()[x] + '/' prefix
2588 # (fullname) to differentiate from tag links; this also makes
2589 # possible to detect branch links
2590 for my $ref (get_branch_refs()) {
2591 if ((defined $hash_base && $hash_base =~ m!^refs/\Q$ref\E/(.*)$!) ||
2592 (defined $hash && $hash =~ m!^refs/\Q$ref\E/(.*)$!)) {
2593 $branch = $1;
2594 $matched_ref = $ref;
2595 last;
2596 }
Jakub Narebski35621982008-04-20 22:09:48 +02002597 }
2598 # find log type for feed description (title)
2599 my $type = 'log';
2600 if (defined $file_name) {
2601 $type = "history of $file_name";
2602 $type .= "/" if ($action eq 'tree');
2603 $type .= " on '$branch'" if (defined $branch);
2604 } else {
2605 $type = "log of $branch" if (defined $branch);
2606 }
2607
2608 $res{-title} = $type;
Krzesimir Nowak8d646a92013-12-11 12:54:43 +01002609 $res{'hash'} = (defined $branch ? "refs/$matched_ref/$branch" : undef);
Jakub Narebski35621982008-04-20 22:09:48 +02002610 $res{'file_name'} = $file_name;
2611
2612 return %res;
2613}
2614
Jakub Narebski717b8312006-07-31 21:22:15 +02002615## ----------------------------------------------------------------------
2616## git utility subroutines, invoking git commands
2617
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002618# returns path to the core git executable and the --git-dir parameter as list
2619sub git_cmd {
Jakub Narebskiaa7dd052009-09-01 13:39:16 +02002620 $number_of_git_cmds++;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002621 return $GIT, '--git-dir='.$git_dir;
2622}
2623
Lea Wiemann516381d2008-06-17 23:46:35 +02002624# quote the given arguments for passing them to the shell
2625# quote_command("command", "arg 1", "arg with ' and ! characters")
2626# => "'command' 'arg 1' 'arg with '\'' and '\!' characters'"
2627# Try to avoid using this function wherever possible.
2628sub quote_command {
2629 return join(' ',
Jakub Narebski68cedb12009-05-10 02:40:37 +02002630 map { my $a = $_; $a =~ s/(['!])/'\\$1'/g; "'$a'" } @_ );
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002631}
2632
Jakub Narebski717b8312006-07-31 21:22:15 +02002633# get HEAD ref of given project as hash
Jakub Narebski847e01f2006-08-14 02:05:47 +02002634sub git_get_head_hash {
Mark Radab6292752009-11-07 16:13:29 +01002635 return git_get_full_hash(shift, 'HEAD');
2636}
2637
2638sub git_get_full_hash {
2639 return git_get_hash(@_);
2640}
2641
2642sub git_get_short_hash {
2643 return git_get_hash(@_, '--short=7');
2644}
2645
2646sub git_get_hash {
2647 my ($project, $hash, @options) = @_;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002648 my $o_git_dir = $git_dir;
Jakub Narebski717b8312006-07-31 21:22:15 +02002649 my $retval = undef;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002650 $git_dir = "$projectroot/$project";
Mark Radab6292752009-11-07 16:13:29 +01002651 if (open my $fd, '-|', git_cmd(), 'rev-parse',
2652 '--verify', '-q', @options, $hash) {
2653 $retval = <$fd>;
2654 chomp $retval if defined $retval;
Jakub Narebski717b8312006-07-31 21:22:15 +02002655 close $fd;
Jakub Narebski717b8312006-07-31 21:22:15 +02002656 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002657 if (defined $o_git_dir) {
2658 $git_dir = $o_git_dir;
Jakub Narebski717b8312006-07-31 21:22:15 +02002659 }
2660 return $retval;
2661}
2662
2663# get type of given object
2664sub git_get_type {
2665 my $hash = shift;
2666
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002667 open my $fd, "-|", git_cmd(), "cat-file", '-t', $hash or return;
Jakub Narebski717b8312006-07-31 21:22:15 +02002668 my $type = <$fd>;
2669 close $fd or return;
2670 chomp $type;
2671 return $type;
2672}
2673
Jakub Narebskib2019272007-11-03 00:41:19 +01002674# repository configuration
2675our $config_file = '';
2676our %config;
2677
2678# store multiple values for single key as anonymous array reference
2679# single values stored directly in the hash, not as [ <value> ]
2680sub hash_set_multi {
2681 my ($hash, $key, $value) = @_;
2682
2683 if (!exists $hash->{$key}) {
2684 $hash->{$key} = $value;
2685 } elsif (!ref $hash->{$key}) {
2686 $hash->{$key} = [ $hash->{$key}, $value ];
2687 } else {
2688 push @{$hash->{$key}}, $value;
2689 }
2690}
2691
2692# return hash of git project configuration
2693# optionally limited to some section, e.g. 'gitweb'
2694sub git_parse_project_config {
2695 my $section_regexp = shift;
2696 my %config;
2697
2698 local $/ = "\0";
2699
2700 open my $fh, "-|", git_cmd(), "config", '-z', '-l',
2701 or return;
2702
2703 while (my $keyval = <$fh>) {
2704 chomp $keyval;
2705 my ($key, $value) = split(/\n/, $keyval, 2);
2706
2707 hash_set_multi(\%config, $key, $value)
2708 if (!defined $section_regexp || $key =~ /^(?:$section_regexp)\./o);
2709 }
2710 close $fh;
2711
2712 return %config;
2713}
2714
Marcel M. Carydf5d10a2009-02-18 14:09:41 +01002715# convert config value to boolean: 'true' or 'false'
Jakub Narebskib2019272007-11-03 00:41:19 +01002716# no value, number > 0, 'true' and 'yes' values are true
2717# rest of values are treated as false (never as error)
2718sub config_to_bool {
2719 my $val = shift;
2720
Marcel M. Carydf5d10a2009-02-18 14:09:41 +01002721 return 1 if !defined $val; # section.key
2722
Jakub Narebskib2019272007-11-03 00:41:19 +01002723 # strip leading and trailing whitespace
2724 $val =~ s/^\s+//;
2725 $val =~ s/\s+$//;
2726
Marcel M. Carydf5d10a2009-02-18 14:09:41 +01002727 return (($val =~ /^\d+$/ && $val) || # section.key = 1
Jakub Narebskib2019272007-11-03 00:41:19 +01002728 ($val =~ /^(?:true|yes)$/i)); # section.key = true
2729}
2730
2731# convert config value to simple decimal number
2732# an optional value suffix of 'k', 'm', or 'g' will cause the value
2733# to be multiplied by 1024, 1048576, or 1073741824
2734sub config_to_int {
2735 my $val = shift;
2736
2737 # strip leading and trailing whitespace
2738 $val =~ s/^\s+//;
2739 $val =~ s/\s+$//;
2740
2741 if (my ($num, $unit) = ($val =~ /^([0-9]*)([kmg])$/i)) {
2742 $unit = lc($unit);
2743 # unknown unit is treated as 1
2744 return $num * ($unit eq 'g' ? 1073741824 :
2745 $unit eq 'm' ? 1048576 :
2746 $unit eq 'k' ? 1024 : 1);
2747 }
2748 return $val;
2749}
2750
2751# convert config value to array reference, if needed
2752sub config_to_multi {
2753 my $val = shift;
2754
Jakub Narebskid76a5852007-12-20 10:48:09 +01002755 return ref($val) ? $val : (defined($val) ? [ $val ] : []);
Jakub Narebskib2019272007-11-03 00:41:19 +01002756}
2757
Jakub Narebski717b8312006-07-31 21:22:15 +02002758sub git_get_project_config {
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05302759 my ($key, $type) = @_;
Jakub Narebski717b8312006-07-31 21:22:15 +02002760
Jakub Narebski7a49c252010-03-27 20:26:59 +01002761 return unless defined $git_dir;
Jakub Narebski9be36142010-03-01 22:51:34 +01002762
Jakub Narebskib2019272007-11-03 00:41:19 +01002763 # key sanity check
Jakub Narebski717b8312006-07-31 21:22:15 +02002764 return unless ($key);
Jakub Narebski14569cd2011-07-28 23:38:03 +02002765 # only subsection, if exists, is case sensitive,
2766 # and not lowercased by 'git config -z -l'
2767 if (my ($hi, $mi, $lo) = ($key =~ /^([^.]*)\.(.*)\.([^.]*)$/)) {
Phil Pennockaf507942012-11-05 18:50:47 -05002768 $lo =~ s/_//g;
Jakub Narebski14569cd2011-07-28 23:38:03 +02002769 $key = join(".", lc($hi), $mi, lc($lo));
Phil Pennockaf507942012-11-05 18:50:47 -05002770 return if ($lo =~ /\W/ || $hi =~ /\W/);
Jakub Narebski14569cd2011-07-28 23:38:03 +02002771 } else {
2772 $key = lc($key);
Phil Pennockaf507942012-11-05 18:50:47 -05002773 $key =~ s/_//g;
2774 return if ($key =~ /\W/);
Jakub Narebski14569cd2011-07-28 23:38:03 +02002775 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002776 $key =~ s/^gitweb\.//;
Jakub Narebski717b8312006-07-31 21:22:15 +02002777
Jakub Narebskib2019272007-11-03 00:41:19 +01002778 # type sanity check
2779 if (defined $type) {
2780 $type =~ s/^--//;
2781 $type = undef
2782 unless ($type eq 'bool' || $type eq 'int');
2783 }
2784
2785 # get config
2786 if (!defined $config_file ||
2787 $config_file ne "$git_dir/config") {
2788 %config = git_parse_project_config('gitweb');
2789 $config_file = "$git_dir/config";
2790 }
2791
Marcel M. Carydf5d10a2009-02-18 14:09:41 +01002792 # check if config variable (key) exists
2793 return unless exists $config{"gitweb.$key"};
2794
Jakub Narebskib2019272007-11-03 00:41:19 +01002795 # ensure given type
2796 if (!defined $type) {
2797 return $config{"gitweb.$key"};
2798 } elsif ($type eq 'bool') {
2799 # backward compatibility: 'git config --bool' returns true/false
2800 return config_to_bool($config{"gitweb.$key"}) ? 'true' : 'false';
2801 } elsif ($type eq 'int') {
2802 return config_to_int($config{"gitweb.$key"});
2803 }
2804 return $config{"gitweb.$key"};
Jakub Narebski717b8312006-07-31 21:22:15 +02002805}
2806
Jakub Narebski717b8312006-07-31 21:22:15 +02002807# get hash of given path at given ref
2808sub git_get_hash_by_path {
2809 my $base = shift;
2810 my $path = shift || return undef;
Jakub Narebski1d782b02006-09-21 18:09:12 +02002811 my $type = shift;
Jakub Narebski717b8312006-07-31 21:22:15 +02002812
Jakub Narebski4b02f482006-09-26 01:54:24 +02002813 $path =~ s,/+$,,;
Jakub Narebski717b8312006-07-31 21:22:15 +02002814
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002815 open my $fd, "-|", git_cmd(), "ls-tree", $base, "--", $path
Lea Wiemann074afaa2008-06-19 22:03:21 +02002816 or die_error(500, "Open git-ls-tree failed");
Jakub Narebski717b8312006-07-31 21:22:15 +02002817 my $line = <$fd>;
2818 close $fd or return undef;
2819
Jakub Narebski198a2a82007-05-12 21:16:34 +02002820 if (!defined $line) {
2821 # there is no tree or hash given by $path at $base
2822 return undef;
2823 }
2824
Jakub Narebski717b8312006-07-31 21:22:15 +02002825 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
Jakub Narebski8b4b94c2006-10-30 22:25:11 +01002826 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/;
Jakub Narebski1d782b02006-09-21 18:09:12 +02002827 if (defined $type && $type ne $2) {
2828 # type doesn't match
2829 return undef;
2830 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002831 return $3;
2832}
2833
Jakub Narebskied224de2007-05-07 01:10:04 +02002834# get path of entry with given hash at given tree-ish (ref)
2835# used to get 'from' filename for combined diff (merge commit) for renames
2836sub git_get_path_by_hash {
2837 my $base = shift || return;
2838 my $hash = shift || return;
2839
2840 local $/ = "\0";
2841
2842 open my $fd, "-|", git_cmd(), "ls-tree", '-r', '-t', '-z', $base
2843 or return undef;
2844 while (my $line = <$fd>) {
2845 chomp $line;
2846
2847 #'040000 tree 595596a6a9117ddba9fe379b6b012b558bac8423 gitweb'
2848 #'100644 blob e02e90f0429be0d2a69b76571101f20b8f75530f gitweb/README'
2849 if ($line =~ m/(?:[0-9]+) (?:.+) $hash\t(.+)$/) {
2850 close $fd;
2851 return $1;
2852 }
2853 }
2854 close $fd;
2855 return undef;
2856}
2857
Jakub Narebski717b8312006-07-31 21:22:15 +02002858## ......................................................................
2859## git utility functions, directly accessing git repository
2860
Sebastien Ceveye4e3b322011-04-29 19:52:00 +02002861# get the value of config variable either from file named as the variable
2862# itself in the repository ($GIT_DIR/$name file), or from gitweb.$name
2863# configuration variable in the repository config file.
2864sub git_get_file_or_project_config {
2865 my ($path, $name) = @_;
Jakub Narebski717b8312006-07-31 21:22:15 +02002866
Jakub Narebski0e121a22007-11-03 00:41:20 +01002867 $git_dir = "$projectroot/$path";
Sebastien Ceveye4e3b322011-04-29 19:52:00 +02002868 open my $fd, '<', "$git_dir/$name"
2869 or return git_get_project_config($name);
2870 my $conf = <$fd>;
Jakub Narebski717b8312006-07-31 21:22:15 +02002871 close $fd;
Sebastien Ceveye4e3b322011-04-29 19:52:00 +02002872 if (defined $conf) {
2873 chomp $conf;
Junio C Hamano2eb54ef2007-05-16 21:04:16 -07002874 }
Sebastien Ceveye4e3b322011-04-29 19:52:00 +02002875 return $conf;
Jakub Narebski717b8312006-07-31 21:22:15 +02002876}
2877
Sebastien Ceveye4e3b322011-04-29 19:52:00 +02002878sub git_get_project_description {
2879 my $path = shift;
2880 return git_get_file_or_project_config($path, 'description');
Jakub Narebski717b8312006-07-31 21:22:15 +02002881}
2882
Sebastien Ceveyd940c902011-04-29 19:52:01 +02002883sub git_get_project_category {
2884 my $path = shift;
2885 return git_get_file_or_project_config($path, 'category');
2886}
2887
2888
Jakub Narebski0368c492011-04-29 19:51:57 +02002889# supported formats:
2890# * $GIT_DIR/ctags/<tagname> file (in 'ctags' subdirectory)
2891# - if its contents is a number, use it as tag weight,
2892# - otherwise add a tag with weight 1
2893# * $GIT_DIR/ctags file, each line is a tag (with weight 1)
2894# the same value multiple times increases tag weight
2895# * `gitweb.ctag' multi-valued repo config variable
Petr Baudisaed93de2008-10-02 17:13:02 +02002896sub git_get_project_ctags {
Jakub Narebski0368c492011-04-29 19:51:57 +02002897 my $project = shift;
Petr Baudisaed93de2008-10-02 17:13:02 +02002898 my $ctags = {};
2899
Jakub Narebski0368c492011-04-29 19:51:57 +02002900 $git_dir = "$projectroot/$project";
2901 if (opendir my $dh, "$git_dir/ctags") {
2902 my @files = grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh);
2903 foreach my $tagfile (@files) {
2904 open my $ct, '<', $tagfile
2905 or next;
2906 my $val = <$ct>;
2907 chomp $val if $val;
2908 close $ct;
2909
2910 (my $ctag = $tagfile) =~ s#.*/##;
Jonathan Nieder2c162b52011-06-09 02:08:57 -05002911 if ($val =~ /^\d+$/) {
Jakub Narebski0368c492011-04-29 19:51:57 +02002912 $ctags->{$ctag} = $val;
2913 } else {
2914 $ctags->{$ctag} = 1;
2915 }
2916 }
2917 closedir $dh;
2918
2919 } elsif (open my $fh, '<', "$git_dir/ctags") {
2920 while (my $line = <$fh>) {
2921 chomp $line;
2922 $ctags->{$line}++ if $line;
2923 }
2924 close $fh;
2925
2926 } else {
2927 my $taglist = config_to_multi(git_get_project_config('ctag'));
2928 foreach my $tag (@$taglist) {
2929 $ctags->{$tag}++;
2930 }
Petr Baudisaed93de2008-10-02 17:13:02 +02002931 }
Jakub Narebski0368c492011-04-29 19:51:57 +02002932
2933 return $ctags;
2934}
2935
2936# return hash, where keys are content tags ('ctags'),
2937# and values are sum of weights of given tag in every project
2938sub git_gather_all_ctags {
2939 my $projects = shift;
2940 my $ctags = {};
2941
2942 foreach my $p (@$projects) {
2943 foreach my $ct (keys %{$p->{'ctags'}}) {
2944 $ctags->{$ct} += $p->{'ctags'}->{$ct};
2945 }
2946 }
2947
2948 return $ctags;
Petr Baudisaed93de2008-10-02 17:13:02 +02002949}
2950
2951sub git_populate_project_tagcloud {
2952 my $ctags = shift;
2953
2954 # First, merge different-cased tags; tags vote on casing
2955 my %ctags_lc;
2956 foreach (keys %$ctags) {
2957 $ctags_lc{lc $_}->{count} += $ctags->{$_};
2958 if (not $ctags_lc{lc $_}->{topcount}
2959 or $ctags_lc{lc $_}->{topcount} < $ctags->{$_}) {
2960 $ctags_lc{lc $_}->{topcount} = $ctags->{$_};
2961 $ctags_lc{lc $_}->{topname} = $_;
2962 }
2963 }
2964
2965 my $cloud;
Jakub Narebski84d9e2d2012-02-03 13:44:54 +01002966 my $matched = $input_params{'ctag'};
Petr Baudisaed93de2008-10-02 17:13:02 +02002967 if (eval { require HTML::TagCloud; 1; }) {
2968 $cloud = HTML::TagCloud->new;
Jakub Narebski0368c492011-04-29 19:51:57 +02002969 foreach my $ctag (sort keys %ctags_lc) {
Petr Baudisaed93de2008-10-02 17:13:02 +02002970 # Pad the title with spaces so that the cloud looks
2971 # less crammed.
Jakub Narebski0368c492011-04-29 19:51:57 +02002972 my $title = esc_html($ctags_lc{$ctag}->{topname});
Petr Baudisaed93de2008-10-02 17:13:02 +02002973 $title =~ s/ /&nbsp;/g;
2974 $title =~ s/^/&nbsp;/g;
2975 $title =~ s/$/&nbsp;/g;
Jakub Narebski4b9447f2011-04-29 19:51:58 +02002976 if (defined $matched && $matched eq $ctag) {
2977 $title = qq(<span class="match">$title</span>);
2978 }
Jakub Narebski0368c492011-04-29 19:51:57 +02002979 $cloud->add($title, href(project=>undef, ctag=>$ctag),
2980 $ctags_lc{$ctag}->{count});
Petr Baudisaed93de2008-10-02 17:13:02 +02002981 }
2982 } else {
Jakub Narebski0368c492011-04-29 19:51:57 +02002983 $cloud = {};
2984 foreach my $ctag (keys %ctags_lc) {
Jakub Narebski4b9447f2011-04-29 19:51:58 +02002985 my $title = esc_html($ctags_lc{$ctag}->{topname}, -nbsp=>1);
2986 if (defined $matched && $matched eq $ctag) {
2987 $title = qq(<span class="match">$title</span>);
2988 }
Jakub Narebski0368c492011-04-29 19:51:57 +02002989 $cloud->{$ctag}{count} = $ctags_lc{$ctag}->{count};
2990 $cloud->{$ctag}{ctag} =
Jakub Narebski4b9447f2011-04-29 19:51:58 +02002991 $cgi->a({-href=>href(project=>undef, ctag=>$ctag)}, $title);
Jakub Narebski0368c492011-04-29 19:51:57 +02002992 }
Petr Baudisaed93de2008-10-02 17:13:02 +02002993 }
Jakub Narebski0368c492011-04-29 19:51:57 +02002994 return $cloud;
Petr Baudisaed93de2008-10-02 17:13:02 +02002995}
2996
2997sub git_show_project_tagcloud {
2998 my ($cloud, $count) = @_;
Petr Baudisaed93de2008-10-02 17:13:02 +02002999 if (ref $cloud eq 'HTML::TagCloud') {
3000 return $cloud->html_and_css($count);
3001 } else {
Jakub Narebski0368c492011-04-29 19:51:57 +02003002 my @tags = sort { $cloud->{$a}->{'count'} <=> $cloud->{$b}->{'count'} } keys %$cloud;
3003 return
3004 '<div id="htmltagcloud"'.($project ? '' : ' align="center"').'>' .
3005 join (', ', map {
3006 $cloud->{$_}->{'ctag'}
3007 } splice(@tags, 0, $count)) .
3008 '</div>';
Petr Baudisaed93de2008-10-02 17:13:02 +02003009 }
3010}
3011
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02003012sub git_get_project_url_list {
3013 my $path = shift;
3014
Jakub Narebski0e121a22007-11-03 00:41:20 +01003015 $git_dir = "$projectroot/$path";
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02003016 open my $fd, '<', "$git_dir/cloneurl"
Jakub Narebski0e121a22007-11-03 00:41:20 +01003017 or return wantarray ?
3018 @{ config_to_multi(git_get_project_config('url')) } :
3019 config_to_multi(git_get_project_config('url'));
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02003020 my @git_project_url_list = map { chomp; $_ } <$fd>;
3021 close $fd;
3022
3023 return wantarray ? @git_project_url_list : \@git_project_url_list;
3024}
3025
Jakub Narebski847e01f2006-08-14 02:05:47 +02003026sub git_get_projects_list {
Jakub Narebski12b14432011-04-29 19:51:56 +02003027 my $filter = shift || '';
Bernhard R. Link348a6582012-01-30 21:06:38 +01003028 my $paranoid = shift;
Jakub Narebski717b8312006-07-31 21:22:15 +02003029 my @list;
3030
3031 if (-d $projects_list) {
3032 # search in directory
Jakub Narebski12b14432011-04-29 19:51:56 +02003033 my $dir = $projects_list;
Aneesh Kumar K.V6768d6b2006-11-03 10:41:45 +05303034 # remove the trailing "/"
3035 $dir =~ s!/+$!!;
Matthieu Moyac593b72012-01-04 11:07:45 +01003036 my $pfxlen = length("$dir");
3037 my $pfxdepth = ($dir =~ tr!/!!);
Jakub Narebski12b14432011-04-29 19:51:56 +02003038 # when filtering, search only given subdirectory
Bernhard R. Link348a6582012-01-30 21:06:38 +01003039 if ($filter && !$paranoid) {
Jakub Narebski12b14432011-04-29 19:51:56 +02003040 $dir .= "/$filter";
3041 $dir =~ s!/+$!!;
3042 }
Jakub Narebskic0011ff2006-09-14 22:18:59 +02003043
3044 File::Find::find({
3045 follow_fast => 1, # follow symbolic links
Junio C Hamanod20602e2007-07-27 01:23:03 -07003046 follow_skip => 2, # ignore duplicates
Jakub Narebskic0011ff2006-09-14 22:18:59 +02003047 dangling_symlinks => 0, # ignore dangling symlinks, silently
3048 wanted => sub {
Jakub Narebskiee1d8ee2010-04-30 18:30:31 +02003049 # global variables
3050 our $project_maxdepth;
3051 our $projectroot;
Jakub Narebskic0011ff2006-09-14 22:18:59 +02003052 # skip project-list toplevel, if we get it.
3053 return if (m!^[/.]$!);
3054 # only directories can be git repositories
3055 return unless (-d $_);
Luke Luca5e9492007-10-16 20:45:25 -07003056 # don't traverse too deep (Find is super slow on os x)
Jakub Narebski12b14432011-04-29 19:51:56 +02003057 # $project_maxdepth excludes depth of $projectroot
Luke Luca5e9492007-10-16 20:45:25 -07003058 if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) {
3059 $File::Find::prune = 1;
3060 return;
3061 }
Jakub Narebskic0011ff2006-09-14 22:18:59 +02003062
Jakub Narebski12b14432011-04-29 19:51:56 +02003063 my $path = substr($File::Find::name, $pfxlen + 1);
Bernhard R. Link348a6582012-01-30 21:06:38 +01003064 # paranoidly only filter here
3065 if ($paranoid && $filter && $path !~ m!^\Q$filter\E/!) {
3066 next;
3067 }
Jakub Narebskic0011ff2006-09-14 22:18:59 +02003068 # we check related file in $projectroot
Devin Doucettefb3bb3d2008-12-27 02:39:31 -07003069 if (check_export_ok("$projectroot/$path")) {
3070 push @list, { path => $path };
Jakub Narebskic0011ff2006-09-14 22:18:59 +02003071 $File::Find::prune = 1;
3072 }
3073 },
3074 }, "$dir");
3075
Jakub Narebski717b8312006-07-31 21:22:15 +02003076 } elsif (-f $projects_list) {
3077 # read from file(url-encoded):
3078 # 'git%2Fgit.git Linus+Torvalds'
3079 # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
3080 # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02003081 open my $fd, '<', $projects_list or return;
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02003082 PROJECT:
Jakub Narebski717b8312006-07-31 21:22:15 +02003083 while (my $line = <$fd>) {
3084 chomp $line;
3085 my ($path, $owner) = split ' ', $line;
3086 $path = unescape($path);
3087 $owner = unescape($owner);
3088 if (!defined $path) {
3089 next;
3090 }
Jakub Narebski12b14432011-04-29 19:51:56 +02003091 # if $filter is rpovided, check if $path begins with $filter
3092 if ($filter && $path !~ m!^\Q$filter\E/!) {
3093 next;
Junio C Hamano83ee94c2006-11-07 22:37:17 -08003094 }
Junio C Hamano2172ce42006-10-03 02:30:47 -07003095 if (check_export_ok("$projectroot/$path")) {
Jakub Narebski717b8312006-07-31 21:22:15 +02003096 my $pr = {
Kacper Kornet75e0dff2012-04-24 19:50:05 +02003097 path => $path
Jakub Narebski717b8312006-07-31 21:22:15 +02003098 };
Kacper Kornet75e0dff2012-04-24 19:50:05 +02003099 if ($owner) {
3100 $pr->{'owner'} = to_utf8($owner);
3101 }
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02003102 push @list, $pr;
Jakub Narebski717b8312006-07-31 21:22:15 +02003103 }
3104 }
3105 close $fd;
3106 }
Jakub Narebski717b8312006-07-31 21:22:15 +02003107 return @list;
3108}
3109
Jakub Narebski12b14432011-04-29 19:51:56 +02003110# written with help of Tree::Trie module (Perl Artistic License, GPL compatibile)
3111# as side effects it sets 'forks' field to list of forks for forked projects
3112sub filter_forks_from_projects_list {
3113 my $projects = shift;
3114
3115 my %trie; # prefix tree of directories (path components)
3116 # generate trie out of those directories that might contain forks
3117 foreach my $pr (@$projects) {
3118 my $path = $pr->{'path'};
3119 $path =~ s/\.git$//; # forks of 'repo.git' are in 'repo/' directory
3120 next if ($path =~ m!/$!); # skip non-bare repositories, e.g. 'repo/.git'
3121 next unless ($path); # skip '.git' repository: tests, git-instaweb
Julien Muchembled53c632f2011-10-21 21:04:21 +02003122 next unless (-d "$projectroot/$path"); # containing directory exists
Jakub Narebski12b14432011-04-29 19:51:56 +02003123 $pr->{'forks'} = []; # there can be 0 or more forks of project
3124
3125 # add to trie
3126 my @dirs = split('/', $path);
3127 # walk the trie, until either runs out of components or out of trie
3128 my $ref = \%trie;
3129 while (scalar @dirs &&
3130 exists($ref->{$dirs[0]})) {
3131 $ref = $ref->{shift @dirs};
3132 }
3133 # create rest of trie structure from rest of components
3134 foreach my $dir (@dirs) {
3135 $ref = $ref->{$dir} = {};
3136 }
3137 # create end marker, store $pr as a data
3138 $ref->{''} = $pr if (!exists $ref->{''});
3139 }
3140
3141 # filter out forks, by finding shortest prefix match for paths
3142 my @filtered;
3143 PROJECT:
3144 foreach my $pr (@$projects) {
3145 # trie lookup
3146 my $ref = \%trie;
3147 DIR:
3148 foreach my $dir (split('/', $pr->{'path'})) {
3149 if (exists $ref->{''}) {
3150 # found [shortest] prefix, is a fork - skip it
3151 push @{$ref->{''}{'forks'}}, $pr;
3152 next PROJECT;
3153 }
3154 if (!exists $ref->{$dir}) {
3155 # not in trie, cannot have prefix, not a fork
3156 push @filtered, $pr;
3157 next PROJECT;
3158 }
3159 # If the dir is there, we just walk one step down the trie.
3160 $ref = $ref->{$dir};
3161 }
3162 # we ran out of trie
3163 # (shouldn't happen: it's either no match, or end marker)
3164 push @filtered, $pr;
3165 }
3166
3167 return @filtered;
3168}
3169
3170# note: fill_project_list_info must be run first,
3171# for 'descr_long' and 'ctags' to be filled
3172sub search_projects_list {
3173 my ($projlist, %opts) = @_;
3174 my $tagfilter = $opts{'tagfilter'};
Jakub Narebskie65ceb62012-03-02 23:34:24 +01003175 my $search_re = $opts{'search_regexp'};
Jakub Narebski12b14432011-04-29 19:51:56 +02003176
3177 return @$projlist
Jakub Narebskie65ceb62012-03-02 23:34:24 +01003178 unless ($tagfilter || $search_re);
Jakub Narebski12b14432011-04-29 19:51:56 +02003179
Jakub Narebski07b257f2012-02-23 16:54:48 +01003180 # searching projects require filling to be run before it;
3181 fill_project_list_info($projlist,
3182 $tagfilter ? 'ctags' : (),
Junio C Hamanoaa145bf2012-03-08 13:04:49 -08003183 $search_re ? ('path', 'descr') : ());
Jakub Narebski12b14432011-04-29 19:51:56 +02003184 my @projects;
3185 PROJECT:
3186 foreach my $pr (@$projlist) {
3187
3188 if ($tagfilter) {
3189 next unless ref($pr->{'ctags'}) eq 'HASH';
3190 next unless
3191 grep { lc($_) eq lc($tagfilter) } keys %{$pr->{'ctags'}};
3192 }
3193
Jakub Narebskie65ceb62012-03-02 23:34:24 +01003194 if ($search_re) {
Jakub Narebski12b14432011-04-29 19:51:56 +02003195 next unless
Jakub Narebskie65ceb62012-03-02 23:34:24 +01003196 $pr->{'path'} =~ /$search_re/ ||
3197 $pr->{'descr_long'} =~ /$search_re/;
Jakub Narebski12b14432011-04-29 19:51:56 +02003198 }
3199
3200 push @projects, $pr;
3201 }
3202
3203 return @projects;
3204}
3205
Junio C Hamano47852452007-07-03 22:10:42 -07003206our $gitweb_project_owner = undef;
3207sub git_get_project_list_from_file {
Jakub Narebski1e0cf032006-08-14 02:10:06 +02003208
Junio C Hamano47852452007-07-03 22:10:42 -07003209 return if (defined $gitweb_project_owner);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02003210
Junio C Hamano47852452007-07-03 22:10:42 -07003211 $gitweb_project_owner = {};
Jakub Narebski1e0cf032006-08-14 02:10:06 +02003212 # read from file (url-encoded):
3213 # 'git%2Fgit.git Linus+Torvalds'
3214 # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
3215 # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
3216 if (-f $projects_list) {
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02003217 open(my $fd, '<', $projects_list);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02003218 while (my $line = <$fd>) {
3219 chomp $line;
3220 my ($pr, $ow) = split ' ', $line;
3221 $pr = unescape($pr);
3222 $ow = unescape($ow);
Junio C Hamano47852452007-07-03 22:10:42 -07003223 $gitweb_project_owner->{$pr} = to_utf8($ow);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02003224 }
3225 close $fd;
3226 }
Junio C Hamano47852452007-07-03 22:10:42 -07003227}
3228
3229sub git_get_project_owner {
3230 my $project = shift;
3231 my $owner;
3232
3233 return undef unless $project;
Bruno Ribasb59012e2008-02-08 14:38:04 -02003234 $git_dir = "$projectroot/$project";
Junio C Hamano47852452007-07-03 22:10:42 -07003235
3236 if (!defined $gitweb_project_owner) {
3237 git_get_project_list_from_file();
3238 }
3239
3240 if (exists $gitweb_project_owner->{$project}) {
3241 $owner = $gitweb_project_owner->{$project};
3242 }
Bruno Ribasb59012e2008-02-08 14:38:04 -02003243 if (!defined $owner){
3244 $owner = git_get_project_config('owner');
3245 }
Jakub Narebski1e0cf032006-08-14 02:10:06 +02003246 if (!defined $owner) {
Bruno Ribasb59012e2008-02-08 14:38:04 -02003247 $owner = get_file_owner("$git_dir");
Jakub Narebski1e0cf032006-08-14 02:10:06 +02003248 }
3249
3250 return $owner;
3251}
3252
Jakub Narebskic60c56c2006-10-28 19:43:40 +02003253sub git_get_last_activity {
3254 my ($path) = @_;
3255 my $fd;
3256
3257 $git_dir = "$projectroot/$path";
3258 open($fd, "-|", git_cmd(), 'for-each-ref',
Robert Fitzsimons0ff5ec72006-12-22 19:38:13 +00003259 '--format=%(committer)',
Jakub Narebskic60c56c2006-10-28 19:43:40 +02003260 '--sort=-committerdate',
Robert Fitzsimons0ff5ec72006-12-22 19:38:13 +00003261 '--count=1',
Krzesimir Nowak8d646a92013-12-11 12:54:43 +01003262 map { "refs/$_" } get_branch_refs ()) or return;
Jakub Narebskic60c56c2006-10-28 19:43:40 +02003263 my $most_recent = <$fd>;
3264 close $fd or return;
Jakub Narebski785cdea2007-05-13 12:39:22 +02003265 if (defined $most_recent &&
3266 $most_recent =~ / (\d+) [-+][01]\d\d\d$/) {
Jakub Narebskic60c56c2006-10-28 19:43:40 +02003267 my $timestamp = $1;
3268 my $age = time - $timestamp;
3269 return ($age, age_string($age));
3270 }
Matt McCutchenc9563952007-06-28 18:15:22 -04003271 return (undef, undef);
Jakub Narebskic60c56c2006-10-28 19:43:40 +02003272}
3273
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01003274# Implementation note: when a single remote is wanted, we cannot use 'git
3275# remote show -n' because that command always work (assuming it's a remote URL
3276# if it's not defined), and we cannot use 'git remote show' because that would
3277# try to make a network roundtrip. So the only way to find if that particular
3278# remote is defined is to walk the list provided by 'git remote -v' and stop if
3279# and when we find what we want.
3280sub git_get_remotes_list {
3281 my $wanted = shift;
3282 my %remotes = ();
3283
3284 open my $fd, '-|' , git_cmd(), 'remote', '-v';
3285 return unless $fd;
3286 while (my $remote = <$fd>) {
3287 chomp $remote;
3288 $remote =~ s!\t(.*?)\s+\((\w+)\)$!!;
3289 next if $wanted and not $remote eq $wanted;
3290 my ($url, $key) = ($1, $2);
3291
3292 $remotes{$remote} ||= { 'heads' => () };
3293 $remotes{$remote}{$key} = $url;
3294 }
3295 close $fd or return;
3296 return wantarray ? %remotes : \%remotes;
3297}
3298
3299# Takes a hash of remotes as first parameter and fills it by adding the
3300# available remote heads for each of the indicated remotes.
3301sub fill_remote_heads {
3302 my $remotes = shift;
3303 my @heads = map { "remotes/$_" } keys %$remotes;
3304 my @remoteheads = git_get_heads_list(undef, @heads);
3305 foreach my $remote (keys %$remotes) {
3306 $remotes->{$remote}{'heads'} = [ grep {
3307 $_->{'name'} =~ s!^$remote/!!
3308 } @remoteheads ];
3309 }
3310}
3311
Jakub Narebski847e01f2006-08-14 02:05:47 +02003312sub git_get_references {
Jakub Narebski717b8312006-07-31 21:22:15 +02003313 my $type = shift || "";
3314 my %refs;
Jakub Narebski28b9d9f2006-11-25 11:32:08 +01003315 # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
3316 # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
3317 open my $fd, "-|", git_cmd(), "show-ref", "--dereference",
3318 ($type ? ("--", "refs/$type") : ()) # use -- <pattern> if $type
Jakub Narebski9704d752006-09-19 14:31:49 +02003319 or return;
Jakub Narebskid294e1c2006-08-14 02:14:20 +02003320
Jakub Narebski717b8312006-07-31 21:22:15 +02003321 while (my $line = <$fd>) {
3322 chomp $line;
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02003323 if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type.*)$!) {
Jakub Narebski717b8312006-07-31 21:22:15 +02003324 if (defined $refs{$1}) {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02003325 push @{$refs{$1}}, $2;
Jakub Narebski717b8312006-07-31 21:22:15 +02003326 } else {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02003327 $refs{$1} = [ $2 ];
Jakub Narebski717b8312006-07-31 21:22:15 +02003328 }
3329 }
3330 }
3331 close $fd or return;
3332 return \%refs;
3333}
3334
Jakub Narebski56a322f2006-08-24 19:41:23 +02003335sub git_get_rev_name_tags {
3336 my $hash = shift || return undef;
3337
Dennis Stosberg25691fb2006-08-28 17:49:58 +02003338 open my $fd, "-|", git_cmd(), "name-rev", "--tags", $hash
Jakub Narebski56a322f2006-08-24 19:41:23 +02003339 or return;
3340 my $name_rev = <$fd>;
3341 close $fd;
3342
3343 if ($name_rev =~ m|^$hash tags/(.*)$|) {
3344 return $1;
3345 } else {
3346 # catches also '$hash undefined' output
3347 return undef;
3348 }
3349}
3350
Jakub Narebski717b8312006-07-31 21:22:15 +02003351## ----------------------------------------------------------------------
3352## parse to hash functions
3353
Jakub Narebski847e01f2006-08-14 02:05:47 +02003354sub parse_date {
Jakub Narebski717b8312006-07-31 21:22:15 +02003355 my $epoch = shift;
3356 my $tz = shift || "-0000";
3357
3358 my %date;
3359 my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
3360 my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
3361 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
3362 $date{'hour'} = $hour;
3363 $date{'minute'} = $min;
3364 $date{'mday'} = $mday;
3365 $date{'day'} = $days[$wday];
3366 $date{'month'} = $months[$mon];
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01003367 $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
3368 $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
Jakub Narebski952c65f2006-08-22 16:52:50 +02003369 $date{'mday-time'} = sprintf "%d %s %02d:%02d",
3370 $mday, $months[$mon], $hour ,$min;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01003371 $date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
Vincent Zanottia62d6d82007-11-10 19:55:27 +01003372 1900+$year, 1+$mon, $mday, $hour ,$min, $sec;
Jakub Narebski717b8312006-07-31 21:22:15 +02003373
Jakub Narebski2b1e1722011-03-25 20:20:49 +01003374 my ($tz_sign, $tz_hour, $tz_min) =
3375 ($tz =~ m/^([-+])(\d\d)(\d\d)$/);
3376 $tz_sign = ($tz_sign eq '-' ? -1 : +1);
3377 my $local = $epoch + $tz_sign*((($tz_hour*60) + $tz_min)*60);
Jakub Narebski717b8312006-07-31 21:22:15 +02003378 ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
3379 $date{'hour_local'} = $hour;
3380 $date{'minute_local'} = $min;
3381 $date{'tz_local'} = $tz;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01003382 $date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
3383 1900+$year, $mon+1, $mday,
3384 $hour, $min, $sec, $tz);
Jakub Narebski717b8312006-07-31 21:22:15 +02003385 return %date;
3386}
3387
Jakub Narebski847e01f2006-08-14 02:05:47 +02003388sub parse_tag {
Jakub Narebski717b8312006-07-31 21:22:15 +02003389 my $tag_id = shift;
3390 my %tag;
3391 my @comment;
3392
Dennis Stosberg25691fb2006-08-28 17:49:58 +02003393 open my $fd, "-|", git_cmd(), "cat-file", "tag", $tag_id or return;
Jakub Narebski717b8312006-07-31 21:22:15 +02003394 $tag{'id'} = $tag_id;
3395 while (my $line = <$fd>) {
3396 chomp $line;
3397 if ($line =~ m/^object ([0-9a-fA-F]{40})$/) {
3398 $tag{'object'} = $1;
3399 } elsif ($line =~ m/^type (.+)$/) {
3400 $tag{'type'} = $1;
3401 } elsif ($line =~ m/^tag (.+)$/) {
3402 $tag{'name'} = $1;
3403 } elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
3404 $tag{'author'} = $1;
Giuseppe Bilottaba924732009-06-30 00:00:50 +02003405 $tag{'author_epoch'} = $2;
3406 $tag{'author_tz'} = $3;
3407 if ($tag{'author'} =~ m/^([^<]+) <([^>]*)>/) {
3408 $tag{'author_name'} = $1;
3409 $tag{'author_email'} = $2;
3410 } else {
3411 $tag{'author_name'} = $tag{'author'};
3412 }
Jakub Narebski717b8312006-07-31 21:22:15 +02003413 } elsif ($line =~ m/--BEGIN/) {
3414 push @comment, $line;
3415 last;
3416 } elsif ($line eq "") {
3417 last;
3418 }
3419 }
3420 push @comment, <$fd>;
3421 $tag{'comment'} = \@comment;
3422 close $fd or return;
3423 if (!defined $tag{'name'}) {
3424 return
3425 };
3426 return %tag
3427}
3428
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003429sub parse_commit_text {
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00003430 my ($commit_text, $withparents) = @_;
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003431 my @commit_lines = split '\n', $commit_text;
Jakub Narebski717b8312006-07-31 21:22:15 +02003432 my %co;
3433
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003434 pop @commit_lines; # Remove '\0'
3435
Jakub Narebski198a2a82007-05-12 21:16:34 +02003436 if (! @commit_lines) {
3437 return;
3438 }
3439
Jakub Narebski717b8312006-07-31 21:22:15 +02003440 my $header = shift @commit_lines;
Jakub Narebski198a2a82007-05-12 21:16:34 +02003441 if ($header !~ m/^[0-9a-fA-F]{40}/) {
Jakub Narebski717b8312006-07-31 21:22:15 +02003442 return;
3443 }
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00003444 ($co{'id'}, my @parents) = split ' ', $header;
Jakub Narebski717b8312006-07-31 21:22:15 +02003445 while (my $line = shift @commit_lines) {
3446 last if $line eq "\n";
3447 if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
3448 $co{'tree'} = $1;
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00003449 } elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) {
Robert Fitzsimons208b2df2006-12-24 14:31:43 +00003450 push @parents, $1;
Jakub Narebski717b8312006-07-31 21:22:15 +02003451 } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
Zoltán Füzesi5ed5bbc2009-08-02 09:42:24 +02003452 $co{'author'} = to_utf8($1);
Jakub Narebski717b8312006-07-31 21:22:15 +02003453 $co{'author_epoch'} = $2;
3454 $co{'author_tz'} = $3;
Jakub Narebskiba00b8c2006-11-25 15:54:32 +01003455 if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
3456 $co{'author_name'} = $1;
3457 $co{'author_email'} = $2;
Jakub Narebski717b8312006-07-31 21:22:15 +02003458 } else {
3459 $co{'author_name'} = $co{'author'};
3460 }
3461 } elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
Zoltán Füzesi5ed5bbc2009-08-02 09:42:24 +02003462 $co{'committer'} = to_utf8($1);
Jakub Narebski717b8312006-07-31 21:22:15 +02003463 $co{'committer_epoch'} = $2;
3464 $co{'committer_tz'} = $3;
Jakub Narebskiba00b8c2006-11-25 15:54:32 +01003465 if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
3466 $co{'committer_name'} = $1;
3467 $co{'committer_email'} = $2;
3468 } else {
3469 $co{'committer_name'} = $co{'committer'};
3470 }
Jakub Narebski717b8312006-07-31 21:22:15 +02003471 }
3472 }
3473 if (!defined $co{'tree'}) {
3474 return;
3475 };
Robert Fitzsimons208b2df2006-12-24 14:31:43 +00003476 $co{'parents'} = \@parents;
3477 $co{'parent'} = $parents[0];
Jakub Narebski717b8312006-07-31 21:22:15 +02003478
3479 foreach my $title (@commit_lines) {
3480 $title =~ s/^ //;
3481 if ($title ne "") {
3482 $co{'title'} = chop_str($title, 80, 5);
3483 # remove leading stuff of merges to make the interesting part visible
3484 if (length($title) > 50) {
3485 $title =~ s/^Automatic //;
3486 $title =~ s/^merge (of|with) /Merge ... /i;
3487 if (length($title) > 50) {
3488 $title =~ s/(http|rsync):\/\///;
3489 }
3490 if (length($title) > 50) {
3491 $title =~ s/(master|www|rsync)\.//;
3492 }
3493 if (length($title) > 50) {
3494 $title =~ s/kernel.org:?//;
3495 }
3496 if (length($title) > 50) {
3497 $title =~ s/\/pub\/scm//;
3498 }
3499 }
3500 $co{'title_short'} = chop_str($title, 50, 5);
3501 last;
3502 }
3503 }
Joey Hess53c39672008-09-05 14:26:29 -04003504 if (! defined $co{'title'} || $co{'title'} eq "") {
Petr Baudis7e0fe5c2006-10-06 18:55:04 +02003505 $co{'title'} = $co{'title_short'} = '(no commit message)';
3506 }
Jakub Narebski717b8312006-07-31 21:22:15 +02003507 # remove added spaces
3508 foreach my $line (@commit_lines) {
3509 $line =~ s/^ //;
3510 }
3511 $co{'comment'} = \@commit_lines;
3512
3513 my $age = time - $co{'committer_epoch'};
3514 $co{'age'} = $age;
3515 $co{'age_string'} = age_string($age);
3516 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'});
3517 if ($age > 60*60*24*7*2) {
3518 $co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
3519 $co{'age_string_age'} = $co{'age_string'};
3520 } else {
3521 $co{'age_string_date'} = $co{'age_string'};
3522 $co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
3523 }
3524 return %co;
3525}
3526
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003527sub parse_commit {
3528 my ($commit_id) = @_;
3529 my %co;
3530
3531 local $/ = "\0";
3532
3533 open my $fd, "-|", git_cmd(), "rev-list",
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00003534 "--parents",
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003535 "--header",
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003536 "--max-count=1",
3537 $commit_id,
3538 "--",
Lea Wiemann074afaa2008-06-19 22:03:21 +02003539 or die_error(500, "Open git-rev-list failed");
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00003540 %co = parse_commit_text(<$fd>, 1);
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003541 close $fd;
3542
3543 return %co;
3544}
3545
3546sub parse_commits {
Jakub Narebski311e5522008-02-26 13:22:06 +01003547 my ($commit_id, $maxcount, $skip, $filename, @args) = @_;
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003548 my @cos;
3549
3550 $maxcount ||= 1;
3551 $skip ||= 0;
3552
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003553 local $/ = "\0";
3554
3555 open my $fd, "-|", git_cmd(), "rev-list",
3556 "--header",
Jakub Narebski311e5522008-02-26 13:22:06 +01003557 @args,
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003558 ("--max-count=" . $maxcount),
Robert Fitzsimonsf47efbb2006-12-24 14:31:49 +00003559 ("--skip=" . $skip),
Miklos Vajna868bc062007-07-12 20:39:27 +02003560 @extra_options,
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003561 $commit_id,
3562 "--",
3563 ($filename ? ($filename) : ())
Lea Wiemann074afaa2008-06-19 22:03:21 +02003564 or die_error(500, "Open git-rev-list failed");
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003565 while (my $line = <$fd>) {
3566 my %co = parse_commit_text($line);
3567 push @cos, \%co;
3568 }
3569 close $fd;
3570
3571 return wantarray ? @cos : \@cos;
3572}
3573
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003574# parse line of git-diff-tree "raw" output
Jakub Narebski740e67f2006-08-21 23:07:00 +02003575sub parse_difftree_raw_line {
3576 my $line = shift;
3577 my %res;
3578
3579 # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c'
3580 # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c'
3581 if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) {
3582 $res{'from_mode'} = $1;
3583 $res{'to_mode'} = $2;
3584 $res{'from_id'} = $3;
3585 $res{'to_id'} = $4;
Jakub Narebski4ed4a342008-04-05 21:13:24 +01003586 $res{'status'} = $5;
Jakub Narebski740e67f2006-08-21 23:07:00 +02003587 $res{'similarity'} = $6;
3588 if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003589 ($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7);
Jakub Narebski740e67f2006-08-21 23:07:00 +02003590 } else {
Jakub Narebski9d301452007-11-01 12:38:08 +01003591 $res{'from_file'} = $res{'to_file'} = $res{'file'} = unquote($7);
Jakub Narebski740e67f2006-08-21 23:07:00 +02003592 }
3593 }
Jakub Narebski78bc4032007-05-07 01:10:03 +02003594 # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh'
3595 # combined diff (for merge commit)
3596 elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) {
3597 $res{'nparents'} = length($1);
3598 $res{'from_mode'} = [ split(' ', $2) ];
3599 $res{'to_mode'} = pop @{$res{'from_mode'}};
3600 $res{'from_id'} = [ split(' ', $3) ];
3601 $res{'to_id'} = pop @{$res{'from_id'}};
3602 $res{'status'} = [ split('', $4) ];
3603 $res{'to_file'} = unquote($5);
3604 }
Jakub Narebski740e67f2006-08-21 23:07:00 +02003605 # 'c512b523472485aef4fff9e57b229d9d243c967f'
Jakub Narebski0edcb372006-08-31 00:36:04 +02003606 elsif ($line =~ m/^([0-9a-fA-F]{40})$/) {
3607 $res{'commit'} = $1;
3608 }
Jakub Narebski740e67f2006-08-21 23:07:00 +02003609
3610 return wantarray ? %res : \%res;
3611}
3612
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003613# wrapper: return parsed line of git-diff-tree "raw" output
3614# (the argument might be raw line, or parsed info)
3615sub parsed_difftree_line {
3616 my $line_or_ref = shift;
3617
3618 if (ref($line_or_ref) eq "HASH") {
3619 # pre-parsed (or generated by hand)
3620 return $line_or_ref;
3621 } else {
3622 return parse_difftree_raw_line($line_or_ref);
3623 }
3624}
3625
Jakub Narebskicb849b42006-08-31 00:32:15 +02003626# parse line of git-ls-tree output
Jakub Narebski74fd8722009-05-07 19:11:29 +02003627sub parse_ls_tree_line {
Jakub Narebskicb849b42006-08-31 00:32:15 +02003628 my $line = shift;
3629 my %opts = @_;
3630 my %res;
3631
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02003632 if ($opts{'-l'}) {
3633 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa 16717 panic.c'
3634 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40}) +(-|[0-9]+)\t(.+)$/s;
Jakub Narebskicb849b42006-08-31 00:32:15 +02003635
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02003636 $res{'mode'} = $1;
3637 $res{'type'} = $2;
3638 $res{'hash'} = $3;
3639 $res{'size'} = $4;
3640 if ($opts{'-z'}) {
3641 $res{'name'} = $5;
3642 } else {
3643 $res{'name'} = unquote($5);
3644 }
Jakub Narebskicb849b42006-08-31 00:32:15 +02003645 } else {
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02003646 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
3647 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
3648
3649 $res{'mode'} = $1;
3650 $res{'type'} = $2;
3651 $res{'hash'} = $3;
3652 if ($opts{'-z'}) {
3653 $res{'name'} = $4;
3654 } else {
3655 $res{'name'} = unquote($4);
3656 }
Jakub Narebskicb849b42006-08-31 00:32:15 +02003657 }
3658
3659 return wantarray ? %res : \%res;
3660}
3661
Jakub Narebski90921742007-06-08 13:27:42 +02003662# generates _two_ hashes, references to which are passed as 2 and 3 argument
3663sub parse_from_to_diffinfo {
3664 my ($diffinfo, $from, $to, @parents) = @_;
3665
3666 if ($diffinfo->{'nparents'}) {
3667 # combined diff
3668 $from->{'file'} = [];
3669 $from->{'href'} = [];
3670 fill_from_file_info($diffinfo, @parents)
3671 unless exists $diffinfo->{'from_file'};
3672 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
Jakub Narebski9d301452007-11-01 12:38:08 +01003673 $from->{'file'}[$i] =
3674 defined $diffinfo->{'from_file'}[$i] ?
3675 $diffinfo->{'from_file'}[$i] :
3676 $diffinfo->{'to_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02003677 if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
3678 $from->{'href'}[$i] = href(action=>"blob",
3679 hash_base=>$parents[$i],
3680 hash=>$diffinfo->{'from_id'}[$i],
3681 file_name=>$from->{'file'}[$i]);
3682 } else {
3683 $from->{'href'}[$i] = undef;
3684 }
3685 }
3686 } else {
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003687 # ordinary (not combined) diff
Jakub Narebski9d301452007-11-01 12:38:08 +01003688 $from->{'file'} = $diffinfo->{'from_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02003689 if ($diffinfo->{'status'} ne "A") { # not new (added) file
3690 $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent,
3691 hash=>$diffinfo->{'from_id'},
3692 file_name=>$from->{'file'});
3693 } else {
3694 delete $from->{'href'};
3695 }
3696 }
3697
Jakub Narebski9d301452007-11-01 12:38:08 +01003698 $to->{'file'} = $diffinfo->{'to_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02003699 if (!is_deleted($diffinfo)) { # file exists in result
3700 $to->{'href'} = href(action=>"blob", hash_base=>$hash,
3701 hash=>$diffinfo->{'to_id'},
3702 file_name=>$to->{'file'});
3703 } else {
3704 delete $to->{'href'};
3705 }
3706}
3707
Jakub Narebski717b8312006-07-31 21:22:15 +02003708## ......................................................................
3709## parse to array of hashes functions
3710
Jakub Narebskicd146402006-11-02 20:23:11 +01003711sub git_get_heads_list {
Giuseppe Bilotta9b3f3de2010-11-11 13:26:10 +01003712 my ($limit, @classes) = @_;
Krzesimir Nowak8d646a92013-12-11 12:54:43 +01003713 @classes = get_branch_refs() unless @classes;
Giuseppe Bilotta9b3f3de2010-11-11 13:26:10 +01003714 my @patterns = map { "refs/$_" } @classes;
Jakub Narebskicd146402006-11-02 20:23:11 +01003715 my @headslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02003716
Jakub Narebskicd146402006-11-02 20:23:11 +01003717 open my $fd, '-|', git_cmd(), 'for-each-ref',
3718 ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
3719 '--format=%(objectname) %(refname) %(subject)%00%(committer)',
Giuseppe Bilotta9b3f3de2010-11-11 13:26:10 +01003720 @patterns
Jakub Narebskic83a77e2006-09-15 03:43:28 +02003721 or return;
3722 while (my $line = <$fd>) {
Jakub Narebskicd146402006-11-02 20:23:11 +01003723 my %ref_item;
Jakub Narebski120ddde2006-09-19 14:33:22 +02003724
Jakub Narebskicd146402006-11-02 20:23:11 +01003725 chomp $line;
3726 my ($refinfo, $committerinfo) = split(/\0/, $line);
3727 my ($hash, $name, $title) = split(' ', $refinfo, 3);
3728 my ($committer, $epoch, $tz) =
3729 ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
Jakub Narebskibf901f82007-12-15 15:40:28 +01003730 $ref_item{'fullname'} = $name;
Krzesimir Nowak8d646a92013-12-11 12:54:43 +01003731 my $strip_refs = join '|', map { quotemeta } get_branch_refs();
3732 $name =~ s!^refs/($strip_refs|remotes)/!!;
Krzesimir Nowake3747472013-12-11 12:54:44 +01003733 $ref_item{'name'} = $name;
3734 # for refs neither in 'heads' nor 'remotes' we want to
3735 # show their ref dir
3736 my $ref_dir = (defined $1) ? $1 : '';
3737 if ($ref_dir ne '' and $ref_dir ne 'heads' and $ref_dir ne 'remotes') {
3738 $ref_item{'name'} .= ' (' . $ref_dir . ')';
3739 }
Jakub Narebskicd146402006-11-02 20:23:11 +01003740
Jakub Narebskicd146402006-11-02 20:23:11 +01003741 $ref_item{'id'} = $hash;
3742 $ref_item{'title'} = $title || '(no commit message)';
3743 $ref_item{'epoch'} = $epoch;
3744 if ($epoch) {
3745 $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
3746 } else {
3747 $ref_item{'age'} = "unknown";
Jakub Narebski717b8312006-07-31 21:22:15 +02003748 }
Jakub Narebskicd146402006-11-02 20:23:11 +01003749
3750 push @headslist, \%ref_item;
Jakub Narebskic83a77e2006-09-15 03:43:28 +02003751 }
3752 close $fd;
Junio C Hamano7a13b992006-07-31 19:18:34 -07003753
Jakub Narebskicd146402006-11-02 20:23:11 +01003754 return wantarray ? @headslist : \@headslist;
3755}
Jakub Narebskic83a77e2006-09-15 03:43:28 +02003756
Jakub Narebskicd146402006-11-02 20:23:11 +01003757sub git_get_tags_list {
3758 my $limit = shift;
3759 my @tagslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02003760
Jakub Narebskicd146402006-11-02 20:23:11 +01003761 open my $fd, '-|', git_cmd(), 'for-each-ref',
3762 ($limit ? '--count='.($limit+1) : ()), '--sort=-creatordate',
3763 '--format=%(objectname) %(objecttype) %(refname) '.
3764 '%(*objectname) %(*objecttype) %(subject)%00%(creator)',
3765 'refs/tags'
3766 or return;
3767 while (my $line = <$fd>) {
3768 my %ref_item;
3769
3770 chomp $line;
3771 my ($refinfo, $creatorinfo) = split(/\0/, $line);
3772 my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6);
3773 my ($creator, $epoch, $tz) =
3774 ($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/);
Jakub Narebskibf901f82007-12-15 15:40:28 +01003775 $ref_item{'fullname'} = $name;
Jakub Narebskicd146402006-11-02 20:23:11 +01003776 $name =~ s!^refs/tags/!!;
3777
3778 $ref_item{'type'} = $type;
3779 $ref_item{'id'} = $id;
3780 $ref_item{'name'} = $name;
3781 if ($type eq "tag") {
3782 $ref_item{'subject'} = $title;
3783 $ref_item{'reftype'} = $reftype;
3784 $ref_item{'refid'} = $refid;
3785 } else {
3786 $ref_item{'reftype'} = $type;
3787 $ref_item{'refid'} = $id;
3788 }
3789
3790 if ($type eq "tag" || $type eq "commit") {
3791 $ref_item{'epoch'} = $epoch;
3792 if ($epoch) {
3793 $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
3794 } else {
3795 $ref_item{'age'} = "unknown";
3796 }
3797 }
3798
3799 push @tagslist, \%ref_item;
Jakub Narebski717b8312006-07-31 21:22:15 +02003800 }
Jakub Narebskicd146402006-11-02 20:23:11 +01003801 close $fd;
3802
3803 return wantarray ? @tagslist : \@tagslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02003804}
3805
3806## ----------------------------------------------------------------------
3807## filesystem-related functions
3808
3809sub get_file_owner {
3810 my $path = shift;
3811
3812 my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path);
3813 my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid);
3814 if (!defined $gcos) {
3815 return undef;
3816 }
3817 my $owner = $gcos;
3818 $owner =~ s/[,;].*$//;
Martin Koegler00f429a2007-06-03 17:42:44 +02003819 return to_utf8($owner);
Jakub Narebski717b8312006-07-31 21:22:15 +02003820}
3821
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01003822# assume that file exists
3823sub insert_file {
3824 my $filename = shift;
3825
3826 open my $fd, '<', $filename;
Jakub Narebski45868642008-12-08 14:13:21 +01003827 print map { to_utf8($_) } <$fd>;
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01003828 close $fd;
3829}
3830
Jakub Narebski717b8312006-07-31 21:22:15 +02003831## ......................................................................
3832## mimetype related functions
3833
3834sub mimetype_guess_file {
3835 my $filename = shift;
3836 my $mimemap = shift;
3837 -r $mimemap or return undef;
3838
3839 my %mimemap;
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02003840 open(my $mh, '<', $mimemap) or return undef;
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02003841 while (<$mh>) {
Jakub Narebski618918e2006-08-14 02:15:22 +02003842 next if m/^#/; # skip comments
Ludwig Nussel93a6ad12011-06-15 08:10:08 +02003843 my ($mimetype, @exts) = split(/\s+/);
3844 foreach my $ext (@exts) {
3845 $mimemap{$ext} = $mimetype;
Jakub Narebski717b8312006-07-31 21:22:15 +02003846 }
3847 }
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02003848 close($mh);
Jakub Narebski717b8312006-07-31 21:22:15 +02003849
Jakub Narebski80593192006-09-19 13:57:03 +02003850 $filename =~ /\.([^.]*)$/;
Jakub Narebski717b8312006-07-31 21:22:15 +02003851 return $mimemap{$1};
3852}
3853
3854sub mimetype_guess {
3855 my $filename = shift;
3856 my $mime;
3857 $filename =~ /\./ or return undef;
3858
3859 if ($mimetypes_file) {
3860 my $file = $mimetypes_file;
Jakub Narebskid5aa50d2006-08-14 02:16:33 +02003861 if ($file !~ m!^/!) { # if it is relative path
3862 # it is relative to project
3863 $file = "$projectroot/$project/$file";
3864 }
Jakub Narebski717b8312006-07-31 21:22:15 +02003865 $mime = mimetype_guess_file($filename, $file);
3866 }
3867 $mime ||= mimetype_guess_file($filename, '/etc/mime.types');
3868 return $mime;
3869}
3870
Jakub Narebski847e01f2006-08-14 02:05:47 +02003871sub blob_mimetype {
Jakub Narebski717b8312006-07-31 21:22:15 +02003872 my $fd = shift;
3873 my $filename = shift;
3874
3875 if ($filename) {
3876 my $mime = mimetype_guess($filename);
3877 $mime and return $mime;
3878 }
3879
3880 # just in case
3881 return $default_blob_plain_mimetype unless $fd;
3882
3883 if (-T $fd) {
Jakub Narebski7f718e82008-06-03 16:47:10 +02003884 return 'text/plain';
Jakub Narebski717b8312006-07-31 21:22:15 +02003885 } elsif (! $filename) {
3886 return 'application/octet-stream';
3887 } elsif ($filename =~ m/\.png$/i) {
3888 return 'image/png';
3889 } elsif ($filename =~ m/\.gif$/i) {
3890 return 'image/gif';
3891 } elsif ($filename =~ m/\.jpe?g$/i) {
3892 return 'image/jpeg';
3893 } else {
3894 return 'application/octet-stream';
3895 }
3896}
3897
Jakub Narebski7f718e82008-06-03 16:47:10 +02003898sub blob_contenttype {
3899 my ($fd, $file_name, $type) = @_;
3900
3901 $type ||= blob_mimetype($fd, $file_name);
3902 if ($type eq 'text/plain' && defined $default_text_plain_charset) {
3903 $type .= "; charset=$default_text_plain_charset";
3904 }
3905
3906 return $type;
3907}
3908
Jakub Narebski592ea412010-04-27 21:34:45 +02003909# guess file syntax for syntax highlighting; return undef if no highlighting
3910# the name of syntax can (in the future) depend on syntax highlighter used
3911sub guess_file_syntax {
3912 my ($highlight, $mimetype, $file_name) = @_;
3913 return undef unless ($highlight && defined $file_name);
Jakub Narebski592ea412010-04-27 21:34:45 +02003914 my $basename = basename($file_name, '.in');
3915 return $highlight_basename{$basename}
3916 if exists $highlight_basename{$basename};
3917
3918 $basename =~ /\.([^.]*)$/;
3919 my $ext = $1 or return undef;
3920 return $highlight_ext{$ext}
3921 if exists $highlight_ext{$ext};
3922
3923 return undef;
3924}
3925
3926# run highlighter and return FD of its output,
3927# or return original FD if no highlighting
3928sub run_highlighter {
3929 my ($fd, $highlight, $syntax) = @_;
3930 return $fd unless ($highlight && defined $syntax);
3931
Sylvain Rabot3ca73532010-12-30 22:20:29 +01003932 close $fd;
Jakub Narebski592ea412010-04-27 21:34:45 +02003933 open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
Christopher Wilson7ce896b2010-09-21 00:25:19 -07003934 quote_command($highlight_bin).
Kevin Cernekee6affdbe2011-03-16 15:34:13 -07003935 " --replace-tabs=8 --fragment --syntax $syntax |"
Jakub Narebski592ea412010-04-27 21:34:45 +02003936 or die_error(500, "Couldn't open file or run syntax highlighter");
3937 return $fd;
3938}
3939
Jakub Narebski717b8312006-07-31 21:22:15 +02003940## ======================================================================
3941## functions printing HTML: header, footer, error page
3942
Jakub Narebskiefb2d0c2010-04-24 16:01:10 +02003943sub get_page_title {
3944 my $title = to_utf8($site_name);
3945
Bernhard R. Link19d2d232012-01-30 21:07:37 +01003946 unless (defined $project) {
3947 if (defined $project_filter) {
Jakub Narebskif4212082012-02-12 16:21:30 +01003948 $title .= " - projects in '" . esc_path($project_filter) . "'";
Bernhard R. Link19d2d232012-01-30 21:07:37 +01003949 }
3950 return $title;
3951 }
Jakub Narebskiefb2d0c2010-04-24 16:01:10 +02003952 $title .= " - " . to_utf8($project);
3953
3954 return $title unless (defined $action);
3955 $title .= "/$action"; # $action is US-ASCII (7bit ASCII)
3956
3957 return $title unless (defined $file_name);
3958 $title .= " - " . esc_path($file_name);
3959 if ($action eq "tree" && $file_name !~ m|/$|) {
3960 $title .= "/";
3961 }
3962
3963 return $title;
3964}
3965
Jakub Narebski6ee90332011-06-22 13:50:46 +02003966sub get_content_type_html {
3967 # require explicit support from the UA if we are to send the page as
3968 # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
3969 # we have to do this because MSIE sometimes globs '*/*', pretending to
3970 # support xhtml+xml but choking when it gets what it asked for.
3971 if (defined $cgi->http('HTTP_ACCEPT') &&
3972 $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
3973 $cgi->Accept('application/xhtml+xml') != 0) {
3974 return 'application/xhtml+xml';
3975 } else {
3976 return 'text/html';
3977 }
3978}
3979
Jakub Narebski05bb5a22010-12-18 21:02:13 +01003980sub print_feed_meta {
3981 if (defined $project) {
3982 my %href_params = get_feed_info();
3983 if (!exists $href_params{'-title'}) {
3984 $href_params{'-title'} = 'log';
3985 }
3986
Ævar Arnfjörð Bjarmason0f54b7d2011-02-19 15:27:41 +00003987 foreach my $format (qw(RSS Atom)) {
Jakub Narebski05bb5a22010-12-18 21:02:13 +01003988 my $type = lc($format);
3989 my %link_attr = (
3990 '-rel' => 'alternate',
3991 '-title' => esc_attr("$project - $href_params{'-title'} - $format feed"),
3992 '-type' => "application/$type+xml"
3993 );
3994
Sebastian Pippingcc999a32012-04-04 14:25:44 +02003995 $href_params{'extra_options'} = undef;
Jakub Narebski05bb5a22010-12-18 21:02:13 +01003996 $href_params{'action'} = $type;
3997 $link_attr{'-href'} = href(%href_params);
3998 print "<link ".
3999 "rel=\"$link_attr{'-rel'}\" ".
4000 "title=\"$link_attr{'-title'}\" ".
4001 "href=\"$link_attr{'-href'}\" ".
4002 "type=\"$link_attr{'-type'}\" ".
4003 "/>\n";
4004
4005 $href_params{'extra_options'} = '--no-merges';
4006 $link_attr{'-href'} = href(%href_params);
4007 $link_attr{'-title'} .= ' (no merges)';
4008 print "<link ".
4009 "rel=\"$link_attr{'-rel'}\" ".
4010 "title=\"$link_attr{'-title'}\" ".
4011 "href=\"$link_attr{'-href'}\" ".
4012 "type=\"$link_attr{'-type'}\" ".
4013 "/>\n";
4014 }
4015
4016 } else {
4017 printf('<link rel="alternate" title="%s projects list" '.
4018 'href="%s" type="text/plain; charset=utf-8" />'."\n",
4019 esc_attr($site_name), href(project=>undef, action=>"project_index"));
4020 printf('<link rel="alternate" title="%s projects feeds" '.
4021 'href="%s" type="text/x-opml" />'."\n",
4022 esc_attr($site_name), href(project=>undef, action=>"opml"));
4023 }
4024}
4025
Jakub Narebski6ee90332011-06-22 13:50:46 +02004026sub print_header_links {
4027 my $status = shift;
4028
4029 # print out each stylesheet that exist, providing backwards capability
4030 # for those people who defined $stylesheet in a config file
4031 if (defined $stylesheet) {
4032 print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
4033 } else {
4034 foreach my $stylesheet (@stylesheets) {
4035 next unless $stylesheet;
4036 print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
4037 }
4038 }
4039 print_feed_meta()
4040 if ($status eq '200 OK');
4041 if (defined $favicon) {
4042 print qq(<link rel="shortcut icon" href=").esc_url($favicon).qq(" type="image/png" />\n);
4043 }
4044}
4045
Bernhard R. Link40efa222012-01-30 21:09:43 +01004046sub print_nav_breadcrumbs_path {
4047 my $dirprefix = undef;
4048 while (my $part = shift) {
4049 $dirprefix .= "/" if defined $dirprefix;
4050 $dirprefix .= $part;
4051 print $cgi->a({-href => href(project => undef,
4052 project_filter => $dirprefix,
4053 action => "project_list")},
4054 esc_html($part)) . " / ";
4055 }
4056}
4057
Jakub Narebski6ee90332011-06-22 13:50:46 +02004058sub print_nav_breadcrumbs {
4059 my %opts = @_;
4060
Tony Finchad9c2e22013-07-04 18:02:12 +01004061 for my $crumb (@extra_breadcrumbs, [ $home_link_str => $home_link ]) {
4062 print $cgi->a({-href => esc_url($crumb->[1])}, $crumb->[0]) . " / ";
4063 }
Jakub Narebski6ee90332011-06-22 13:50:46 +02004064 if (defined $project) {
Bernhard R. Link4426ba22012-01-30 21:10:23 +01004065 my @dirname = split '/', $project;
4066 my $projectbasename = pop @dirname;
4067 print_nav_breadcrumbs_path(@dirname);
4068 print $cgi->a({-href => href(action=>"summary")}, esc_html($projectbasename));
Jakub Narebski6ee90332011-06-22 13:50:46 +02004069 if (defined $action) {
4070 my $action_print = $action ;
4071 if (defined $opts{-action_extra}) {
4072 $action_print = $cgi->a({-href => href(action=>$action)},
4073 $action);
4074 }
4075 print " / $action_print";
4076 }
4077 if (defined $opts{-action_extra}) {
4078 print " / $opts{-action_extra}";
4079 }
4080 print "\n";
Bernhard R. Link40efa222012-01-30 21:09:43 +01004081 } elsif (defined $project_filter) {
4082 print_nav_breadcrumbs_path(split '/', $project_filter);
Jakub Narebski6ee90332011-06-22 13:50:46 +02004083 }
4084}
4085
4086sub print_search_form {
4087 if (!defined $searchtext) {
4088 $searchtext = "";
4089 }
4090 my $search_hash;
4091 if (defined $hash_base) {
4092 $search_hash = $hash_base;
4093 } elsif (defined $hash) {
4094 $search_hash = $hash;
4095 } else {
4096 $search_hash = "HEAD";
4097 }
4098 my $action = $my_uri;
4099 my $use_pathinfo = gitweb_check_feature('pathinfo');
4100 if ($use_pathinfo) {
4101 $action .= "/".esc_url($project);
4102 }
4103 print $cgi->startform(-method => "get", -action => $action) .
4104 "<div class=\"search\">\n" .
4105 (!$use_pathinfo &&
4106 $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
4107 $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
4108 $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
4109 $cgi->popup_menu(-name => 'st', -default => 'commit',
4110 -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
Tony Finchaf52bd52013-08-20 17:59:54 +01004111 " " . $cgi->a({-href => href(action=>"search_help"),
4112 -title => "search help" }, "?") . " search:\n",
Jakub Narebski84d9e2d2012-02-03 13:44:54 +01004113 $cgi->textfield(-name => "s", -value => $searchtext, -override => 1) . "\n" .
Jakub Narebski6ee90332011-06-22 13:50:46 +02004114 "<span title=\"Extended regular expression\">" .
4115 $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
4116 -checked => $search_use_regexp) .
4117 "</span>" .
4118 "</div>" .
4119 $cgi->end_form() . "\n";
4120}
4121
Kay Sievers12a88f22005-08-07 20:02:47 +02004122sub git_header_html {
Kay Sieversa59d4af2005-08-07 20:15:44 +02004123 my $status = shift || "200 OK";
Kay Sievers11044292005-10-19 03:18:45 +02004124 my $expires = shift;
Jakub Narebski7a597452010-04-24 16:00:04 +02004125 my %opts = @_;
Kay Sieversa59d4af2005-08-07 20:15:44 +02004126
Jakub Narebskiefb2d0c2010-04-24 16:01:10 +02004127 my $title = get_page_title();
Jakub Narebski6ee90332011-06-22 13:50:46 +02004128 my $content_type = get_content_type_html();
Jakub Narebski952c65f2006-08-22 16:52:50 +02004129 print $cgi->header(-type=>$content_type, -charset => 'utf-8',
Jakub Narebski7a597452010-04-24 16:00:04 +02004130 -status=> $status, -expires => $expires)
Jakub Narebskiad709ea2010-06-13 00:35:59 +02004131 unless ($opts{'-no_http_header'});
Jakub Narebski45c9a752006-12-27 23:59:51 +01004132 my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
Kay Sieversa59d4af2005-08-07 20:15:44 +02004133 print <<EOF;
Kay Sievers6191f8e2005-08-07 20:19:56 +02004134<?xml version="1.0" encoding="utf-8"?>
Kay Sievers161332a2005-08-07 19:49:46 +02004135<!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 +02004136<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
Jakub Narebskid4baf9e2006-08-17 11:21:28 +02004137<!-- git web interface version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
Jakub Narebskiae20de52006-06-21 09:48:03 +02004138<!-- git core binaries version $git_version -->
Kay Sievers161332a2005-08-07 19:49:46 +02004139<head>
Alp Tokerf6801d62006-07-11 11:19:34 +01004140<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
Jakub Narebski45c9a752006-12-27 23:59:51 +01004141<meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/>
Kay Sieversc994d622005-08-07 20:27:18 +02004142<meta name="robots" content="index, nofollow"/>
Kay Sieversb87d78d2005-08-07 20:21:04 +02004143<title>$title</title>
Kay Sievers161332a2005-08-07 19:49:46 +02004144EOF
Giuseppe Bilotta41a4d162009-01-31 02:31:52 +01004145 # the stylesheet, favicon etc urls won't work correctly with path_info
4146 # unless we set the appropriate base URL
Giuseppe Bilottac3254ae2009-01-31 02:31:50 +01004147 if ($ENV{'PATH_INFO'}) {
Giuseppe Bilotta81d3fe92009-02-15 10:18:36 +01004148 print "<base href=\"".esc_url($base_url)."\" />\n";
Giuseppe Bilottac3254ae2009-01-31 02:31:50 +01004149 }
Jakub Narebski6ee90332011-06-22 13:50:46 +02004150 print_header_links($status);
Lénaïc Huardc1355b72011-10-21 09:09:29 +02004151
4152 if (defined $site_html_head_string) {
4153 print to_utf8($site_html_head_string);
4154 }
4155
Matthias Lederhoferdd04c422006-08-06 13:25:41 +02004156 print "</head>\n" .
Alan Chandlerb2d34762006-10-03 13:49:03 +01004157 "<body>\n";
4158
John 'Warthog9' Hawley24d4afc2010-01-30 23:30:41 +01004159 if (defined $site_header && -f $site_header) {
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01004160 insert_file($site_header);
Alan Chandlerb2d34762006-10-03 13:49:03 +01004161 }
4162
Jonathan Nieder68220522010-09-03 19:45:09 -05004163 print "<div class=\"page_header\">\n";
4164 if (defined $logo) {
4165 print $cgi->a({-href => esc_url($logo_url),
4166 -title => $logo_label},
4167 $cgi->img({-src => esc_url($logo),
4168 -width => 72, -height => 27,
4169 -alt => "git",
4170 -class => "logo"}));
4171 }
Jakub Narebski6ee90332011-06-22 13:50:46 +02004172 print_nav_breadcrumbs(%opts);
Petr Baudisd77b5672007-05-17 04:24:19 +02004173 print "</div>\n";
4174
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004175 my $have_search = gitweb_check_feature('search');
Jakub Narebskif70dda22008-06-02 11:54:41 +02004176 if (defined $project && $have_search) {
Jakub Narebski6ee90332011-06-22 13:50:46 +02004177 print_search_form();
Kay Sievers4c02e3c2005-08-07 19:52:52 +02004178 }
Kay Sievers161332a2005-08-07 19:49:46 +02004179}
4180
Kay Sievers12a88f22005-08-07 20:02:47 +02004181sub git_footer_html {
Jakub Narebski35621982008-04-20 22:09:48 +02004182 my $feed_class = 'rss_logo';
4183
Kay Sievers6191f8e2005-08-07 20:19:56 +02004184 print "<div class=\"page_footer\">\n";
Kay Sieversb87d78d2005-08-07 20:21:04 +02004185 if (defined $project) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004186 my $descr = git_get_project_description($project);
Kay Sieversb87d78d2005-08-07 20:21:04 +02004187 if (defined $descr) {
Kay Sievers40c13812005-11-19 17:41:29 +01004188 print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
Kay Sievers6191f8e2005-08-07 20:19:56 +02004189 }
Jakub Narebski35621982008-04-20 22:09:48 +02004190
4191 my %href_params = get_feed_info();
4192 if (!%href_params) {
4193 $feed_class .= ' generic';
4194 }
4195 $href_params{'-title'} ||= 'log';
4196
Ævar Arnfjörð Bjarmason0f54b7d2011-02-19 15:27:41 +00004197 foreach my $format (qw(RSS Atom)) {
Jakub Narebski35621982008-04-20 22:09:48 +02004198 $href_params{'action'} = lc($format);
4199 print $cgi->a({-href => href(%href_params),
4200 -title => "$href_params{'-title'} $format feed",
4201 -class => $feed_class}, $format)."\n";
4202 }
4203
Kay Sieversc994d622005-08-07 20:27:18 +02004204 } else {
Bernhard R. Link56efd9d2012-01-30 21:09:00 +01004205 print $cgi->a({-href => href(project=>undef, action=>"opml",
4206 project_filter => $project_filter),
Jakub Narebski35621982008-04-20 22:09:48 +02004207 -class => $feed_class}, "OPML") . " ";
Bernhard R. Link56efd9d2012-01-30 21:09:00 +01004208 print $cgi->a({-href => href(project=>undef, action=>"project_index",
4209 project_filter => $project_filter),
Jakub Narebski35621982008-04-20 22:09:48 +02004210 -class => $feed_class}, "TXT") . "\n";
Kay Sieversff7669a2005-08-07 20:13:02 +02004211 }
Jakub Narebski35621982008-04-20 22:09:48 +02004212 print "</div>\n"; # class="page_footer"
Alan Chandlerb2d34762006-10-03 13:49:03 +01004213
Jakub Narebskiaa7dd052009-09-01 13:39:16 +02004214 if (defined $t0 && gitweb_check_feature('timed')) {
4215 print "<div id=\"generating_info\">\n";
4216 print 'This page took '.
4217 '<span id="generating_time" class="time_span">'.
Jakub Narebski3962f1d72010-11-09 19:27:54 +01004218 tv_interval($t0, [ gettimeofday() ]).
Jakub Narebskiaa7dd052009-09-01 13:39:16 +02004219 ' seconds </span>'.
4220 ' and '.
4221 '<span id="generating_cmd">'.
4222 $number_of_git_cmds.
4223 '</span> git commands '.
4224 " to generate.\n";
4225 print "</div>\n"; # class="page_footer"
4226 }
4227
John 'Warthog9' Hawley24d4afc2010-01-30 23:30:41 +01004228 if (defined $site_footer && -f $site_footer) {
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01004229 insert_file($site_footer);
Alan Chandlerb2d34762006-10-03 13:49:03 +01004230 }
4231
Junio C Hamanoabf411e2010-12-15 11:32:57 -08004232 print qq!<script type="text/javascript" src="!.esc_url($javascript).qq!"></script>\n!;
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +01004233 if (defined $action &&
4234 $action eq 'blame_incremental') {
Jakub Narebskic4ccf612009-09-01 13:39:19 +02004235 print qq!<script type="text/javascript">\n!.
4236 qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
4237 qq! "!. href() .qq!");\n!.
4238 qq!</script>\n!;
John 'Warthog9' Hawley291e52b2011-04-28 21:04:09 +02004239 } else {
Jakub Narebski2e987f92011-04-28 21:04:11 +02004240 my ($jstimezone, $tz_cookie, $datetime_class) =
4241 gitweb_get_feature('javascript-timezone');
4242
Jakub Narebskic4ccf612009-09-01 13:39:19 +02004243 print qq!<script type="text/javascript">\n!.
Jakub Narebski2e987f92011-04-28 21:04:11 +02004244 qq!window.onload = function () {\n!;
4245 if (gitweb_check_feature('javascript-actions')) {
4246 print qq! fixLinks();\n!;
4247 }
4248 if ($jstimezone && $tz_cookie && $datetime_class) {
4249 print qq! var tz_cookie = { name: '$tz_cookie', expires: 14, path: '/' };\n!. # in days
4250 qq! onloadTZSetup('$jstimezone', tz_cookie, '$datetime_class');\n!;
4251 }
4252 print qq!};\n!.
Jakub Narebskic4ccf612009-09-01 13:39:19 +02004253 qq!</script>\n!;
4254 }
4255
Alan Chandlerb2d34762006-10-03 13:49:03 +01004256 print "</body>\n" .
Kay Sievers9cd3d982005-08-07 20:17:42 +02004257 "</html>";
Kay Sievers161332a2005-08-07 19:49:46 +02004258}
4259
Jakub Narebski453541f2010-02-07 21:51:18 +01004260# die_error(<http_status_code>, <error_message>[, <detailed_html_description>])
Lea Wiemann074afaa2008-06-19 22:03:21 +02004261# Example: die_error(404, 'Hash not found')
4262# By convention, use the following status codes (as defined in RFC 2616):
4263# 400: Invalid or missing CGI parameters, or
4264# requested object exists but has wrong type.
4265# 403: Requested feature (like "pickaxe" or "snapshot") not enabled on
4266# this server or project.
4267# 404: Requested object/revision/project doesn't exist.
4268# 500: The server isn't configured properly, or
4269# an internal error occurred (e.g. failed assertions caused by bugs), or
4270# an unknown error occurred (e.g. the git binary died unexpectedly).
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +01004271# 503: The server is currently unavailable (because it is overloaded,
4272# or down for maintenance). Generally, this is a temporary state.
Kay Sievers061cc7c2005-08-07 20:15:57 +02004273sub die_error {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004274 my $status = shift || 500;
Jakub Narebski1df48762010-02-07 21:52:25 +01004275 my $error = esc_html(shift) || "Internal Server Error";
John 'Warthog9' Hawleyaa140132010-01-30 23:30:44 +01004276 my $extra = shift;
Jakub Narebski7a597452010-04-24 16:00:04 +02004277 my %opts = @_;
Kay Sievers664f4cc2005-08-07 20:17:19 +02004278
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +01004279 my %http_responses = (
4280 400 => '400 Bad Request',
4281 403 => '403 Forbidden',
4282 404 => '404 Not Found',
4283 500 => '500 Internal Server Error',
4284 503 => '503 Service Unavailable',
4285 );
Jakub Narebski7a597452010-04-24 16:00:04 +02004286 git_header_html($http_responses{$status}, undef, %opts);
Jakub Narebski59b9f612006-08-22 23:42:53 +02004287 print <<EOF;
4288<div class="page_body">
4289<br /><br />
4290$status - $error
4291<br />
Jakub Narebski59b9f612006-08-22 23:42:53 +02004292EOF
John 'Warthog9' Hawleyaa140132010-01-30 23:30:44 +01004293 if (defined $extra) {
4294 print "<hr />\n" .
4295 "$extra\n";
4296 }
4297 print "</div>\n";
4298
Kay Sieversa59d4af2005-08-07 20:15:44 +02004299 git_footer_html();
Jakub Narebski7a597452010-04-24 16:00:04 +02004300 goto DONE_GITWEB
4301 unless ($opts{'-error_handler'});
Kay Sieversa59d4af2005-08-07 20:15:44 +02004302}
4303
Jakub Narebski717b8312006-07-31 21:22:15 +02004304## ----------------------------------------------------------------------
4305## functions printing or outputting HTML: navigation
4306
Jakub Narebski847e01f2006-08-14 02:05:47 +02004307sub git_print_page_nav {
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02004308 my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_;
4309 $extra = '' if !defined $extra; # pager or formats
4310
4311 my @navs = qw(summary shortlog log commit commitdiff tree);
4312 if ($suppress) {
4313 @navs = grep { $_ ne $suppress } @navs;
4314 }
4315
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004316 my %arg = map { $_ => {action=>$_} } @navs;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02004317 if (defined $head) {
4318 for (qw(commit commitdiff)) {
Jakub Narebski3be8e722007-04-01 22:22:21 +02004319 $arg{$_}{'hash'} = $head;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02004320 }
4321 if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
4322 for (qw(shortlog log)) {
Jakub Narebski3be8e722007-04-01 22:22:21 +02004323 $arg{$_}{'hash'} = $head;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02004324 }
4325 }
4326 }
Petr Baudisd627f682008-10-02 16:36:52 +02004327
Jakub Narebski3be8e722007-04-01 22:22:21 +02004328 $arg{'tree'}{'hash'} = $treehead if defined $treehead;
4329 $arg{'tree'}{'hash_base'} = $treebase if defined $treebase;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02004330
Junio C Hamanoa7c5a282008-11-29 13:02:08 -08004331 my @actions = gitweb_get_feature('actions');
Jakub Narebski2b11e052008-10-12 00:02:32 +02004332 my %repl = (
4333 '%' => '%',
4334 'n' => $project, # project name
4335 'f' => $git_dir, # project path within filesystem
4336 'h' => $treehead || '', # current hash ('h' parameter)
4337 'b' => $treebase || '', # hash base ('hb' parameter)
4338 );
Petr Baudisd627f682008-10-02 16:36:52 +02004339 while (@actions) {
Jakub Narebski2b11e052008-10-12 00:02:32 +02004340 my ($label, $link, $pos) = splice(@actions,0,3);
4341 # insert
Petr Baudisd627f682008-10-02 16:36:52 +02004342 @navs = map { $_ eq $pos ? ($_, $label) : $_ } @navs;
4343 # munch munch
Jakub Narebski2b11e052008-10-12 00:02:32 +02004344 $link =~ s/%([%nfhb])/$repl{$1}/g;
Petr Baudisd627f682008-10-02 16:36:52 +02004345 $arg{$label}{'_href'} = $link;
4346 }
4347
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02004348 print "<div class=\"page_nav\">\n" .
4349 (join " | ",
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004350 map { $_ eq $current ?
Petr Baudisd627f682008-10-02 16:36:52 +02004351 $_ : $cgi->a({-href => ($arg{$_}{_href} ? $arg{$_}{_href} : href(%{$arg{$_}}))}, "$_")
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004352 } @navs);
Jakub Narebski898a8932006-07-30 16:14:43 +02004353 print "<br/>\n$extra<br/>\n" .
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02004354 "</div>\n";
4355}
4356
Giuseppe Bilotta11e7bec2010-11-11 13:26:12 +01004357# returns a submenu for the nagivation of the refs views (tags, heads,
4358# remotes) with the current view disabled and the remotes view only
4359# available if the feature is enabled
4360sub format_ref_views {
4361 my ($current) = @_;
4362 my @ref_views = qw{tags heads};
4363 push @ref_views, 'remotes' if gitweb_check_feature('remote_heads');
4364 return join " | ", map {
4365 $_ eq $current ? $_ :
4366 $cgi->a({-href => href(action=>$_)}, $_)
4367 } @ref_views
4368}
4369
Jakub Narebski847e01f2006-08-14 02:05:47 +02004370sub format_paging_nav {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01004371 my ($action, $page, $has_next_link) = @_;
Jakub Narebski43ffc062006-07-30 17:49:00 +02004372 my $paging_nav;
4373
4374
Jakub Narebski43ffc062006-07-30 17:49:00 +02004375 if ($page > 0) {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01004376 $paging_nav .=
4377 $cgi->a({-href => href(-replay=>1, page=>undef)}, "first") .
4378 " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01004379 $cgi->a({-href => href(-replay=>1, page=>$page-1),
Jakub Narebski26298b52006-08-10 12:38:47 +02004380 -accesskey => "p", -title => "Alt-p"}, "prev");
Jakub Narebski43ffc062006-07-30 17:49:00 +02004381 } else {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01004382 $paging_nav .= "first &sdot; prev";
Jakub Narebski43ffc062006-07-30 17:49:00 +02004383 }
4384
Lea Wiemann1f684dc2008-05-28 01:25:42 +02004385 if ($has_next_link) {
Jakub Narebski43ffc062006-07-30 17:49:00 +02004386 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01004387 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Jakub Narebski26298b52006-08-10 12:38:47 +02004388 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski43ffc062006-07-30 17:49:00 +02004389 } else {
4390 $paging_nav .= " &sdot; next";
4391 }
4392
4393 return $paging_nav;
4394}
4395
Jakub Narebski717b8312006-07-31 21:22:15 +02004396## ......................................................................
4397## functions printing or outputting HTML: div
Kay Sievers42f7eb92005-08-07 20:21:46 +02004398
Jakub Narebski847e01f2006-08-14 02:05:47 +02004399sub git_print_header_div {
Jakub Narebski717b8312006-07-31 21:22:15 +02004400 my ($action, $title, $hash, $hash_base) = @_;
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004401 my %args = ();
Jakub Narebski717b8312006-07-31 21:22:15 +02004402
Jakub Narebski3be8e722007-04-01 22:22:21 +02004403 $args{'action'} = $action;
4404 $args{'hash'} = $hash if $hash;
4405 $args{'hash_base'} = $hash_base if $hash_base;
Jakub Narebski717b8312006-07-31 21:22:15 +02004406
4407 print "<div class=\"header\">\n" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004408 $cgi->a({-href => href(%args), -class => "title"},
4409 $title ? $title : $action) .
4410 "\n</div>\n";
Kay Sievers42f7eb92005-08-07 20:21:46 +02004411}
4412
Giuseppe Bilotta0e656992010-11-11 13:26:15 +01004413sub format_repo_url {
4414 my ($name, $url) = @_;
4415 return "<tr class=\"metadata_url\"><td>$name</td><td>$url</td></tr>\n";
4416}
4417
Giuseppe Bilottab891d522010-11-11 13:26:16 +01004418# Group output by placing it in a DIV element and adding a header.
4419# Options for start_div() can be provided by passing a hash reference as the
4420# first parameter to the function.
4421# Options to git_print_header_div() can be provided by passing an array
4422# reference. This must follow the options to start_div if they are present.
4423# The content can be a scalar, which is output as-is, a scalar reference, which
4424# is output after html escaping, an IO handle passed either as *handle or
4425# *handle{IO}, or a function reference. In the latter case all following
4426# parameters will be taken as argument to the content function call.
4427sub git_print_section {
4428 my ($div_args, $header_args, $content);
4429 my $arg = shift;
4430 if (ref($arg) eq 'HASH') {
4431 $div_args = $arg;
4432 $arg = shift;
4433 }
4434 if (ref($arg) eq 'ARRAY') {
4435 $header_args = $arg;
4436 $arg = shift;
4437 }
4438 $content = $arg;
4439
4440 print $cgi->start_div($div_args);
4441 git_print_header_div(@$header_args);
4442
4443 if (ref($content) eq 'CODE') {
4444 $content->(@_);
4445 } elsif (ref($content) eq 'SCALAR') {
4446 print esc_html($$content);
4447 } elsif (ref($content) eq 'GLOB' or ref($content) eq 'IO::Handle') {
4448 print <$content>;
4449 } elsif (!ref($content) && defined($content)) {
4450 print $content;
4451 }
4452
4453 print $cgi->end_div;
4454}
4455
Jakub Narebski256b7b42011-04-28 21:04:07 +02004456sub format_timestamp_html {
Jakub Narebskice71b072011-04-28 21:04:08 +02004457 my $date = shift;
Jakub Narebski2e987f92011-04-28 21:04:11 +02004458 my $strtime = $date->{'rfc2822'};
John 'Warthog9' Hawley0cf207f2010-01-30 23:30:42 +01004459
Jakub Narebski2e987f92011-04-28 21:04:11 +02004460 my (undef, undef, $datetime_class) =
4461 gitweb_get_feature('javascript-timezone');
4462 if ($datetime_class) {
4463 $strtime = qq!<span class="$datetime_class">$strtime</span>!;
Jakub Narebskia44465c2006-08-28 23:17:31 +02004464 }
John 'Warthog9' Hawley0cf207f2010-01-30 23:30:42 +01004465
Jakub Narebski256b7b42011-04-28 21:04:07 +02004466 my $localtime_format = '(%02d:%02d %s)';
4467 if ($date->{'hour_local'} < 6) {
4468 $localtime_format = '(<span class="atnight">%02d:%02d</span> %s)';
Jakub Narebskia44465c2006-08-28 23:17:31 +02004469 }
Jakub Narebski256b7b42011-04-28 21:04:07 +02004470 $strtime .= ' ' .
4471 sprintf($localtime_format,
4472 $date->{'hour_local'}, $date->{'minute_local'}, $date->{'tz_local'});
John 'Warthog9' Hawley0cf207f2010-01-30 23:30:42 +01004473
Jakub Narebski256b7b42011-04-28 21:04:07 +02004474 return $strtime;
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004475}
4476
4477# Outputs the author name and date in long form
4478sub git_print_authorship {
4479 my $co = shift;
4480 my %opts = @_;
4481 my $tag = $opts{-tag} || 'div';
Stephen Boyde133d652009-10-15 21:14:59 -07004482 my $author = $co->{'author_name'};
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004483
4484 my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
4485 print "<$tag class=\"author_date\">" .
Stephen Boyde133d652009-10-15 21:14:59 -07004486 format_search_author($author, "author", esc_html($author)) .
Jakub Narebskice71b072011-04-28 21:04:08 +02004487 " [".format_timestamp_html(\%ad)."]".
Jakub Narebski256b7b42011-04-28 21:04:07 +02004488 git_get_avatar($co->{'author_email'}, -pad_before => 1) .
4489 "</$tag>\n";
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004490}
4491
4492# Outputs table rows containing the full author or committer information,
Ralf Wildenhues22e5e582010-08-22 13:12:12 +02004493# in the format expected for 'commit' view (& similar).
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004494# Parameters are a commit hash reference, followed by the list of people
Ralf Wildenhues22e5e582010-08-22 13:12:12 +02004495# to output information for. If the list is empty it defaults to both
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004496# author and committer.
4497sub git_print_authorship_rows {
4498 my $co = shift;
4499 # too bad we can't use @people = @_ || ('author', 'committer')
4500 my @people = @_;
4501 @people = ('author', 'committer') unless @people;
4502 foreach my $who (@people) {
4503 my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"});
Stephen Boyde133d652009-10-15 21:14:59 -07004504 print "<tr><td>$who</td><td>" .
4505 format_search_author($co->{"${who}_name"}, $who,
Jakub Narebski256b7b42011-04-28 21:04:07 +02004506 esc_html($co->{"${who}_name"})) . " " .
Stephen Boyde133d652009-10-15 21:14:59 -07004507 format_search_author($co->{"${who}_email"}, $who,
Jakub Narebski256b7b42011-04-28 21:04:07 +02004508 esc_html("<" . $co->{"${who}_email"} . ">")) .
Stephen Boyde133d652009-10-15 21:14:59 -07004509 "</td><td rowspan=\"2\">" .
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02004510 git_get_avatar($co->{"${who}_email"}, -size => 'double') .
4511 "</td></tr>\n" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004512 "<tr>" .
Jakub Narebski256b7b42011-04-28 21:04:07 +02004513 "<td></td><td>" .
Jakub Narebskice71b072011-04-28 21:04:08 +02004514 format_timestamp_html(\%wd) .
Jakub Narebski256b7b42011-04-28 21:04:07 +02004515 "</td>" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004516 "</tr>\n";
4517 }
Jakub Narebski6fd92a22006-08-28 14:48:12 +02004518}
4519
Jakub Narebski717b8312006-07-31 21:22:15 +02004520sub git_print_page_path {
4521 my $name = shift;
4522 my $type = shift;
Luben Tuikov59fb1c92006-08-17 10:39:29 -07004523 my $hb = shift;
Junio C Hamanodf2c37a2006-01-09 13:13:39 +01004524
Jakub Narebski4df118e2006-10-21 17:53:55 +02004525
4526 print "<div class=\"page_path\">";
4527 print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
Martin Koegler00f429a2007-06-03 17:42:44 +02004528 -title => 'tree root'}, to_utf8("[$project]"));
Jakub Narebski4df118e2006-10-21 17:53:55 +02004529 print " / ";
4530 if (defined $name) {
Jakub Narebski762c7202006-09-04 18:17:58 +02004531 my @dirname = split '/', $name;
4532 my $basename = pop @dirname;
4533 my $fullname = '';
4534
Jakub Narebski762c7202006-09-04 18:17:58 +02004535 foreach my $dir (@dirname) {
Petr Baudis16fdb482006-09-21 02:05:50 +02004536 $fullname .= ($fullname ? '/' : '') . $dir;
Jakub Narebski762c7202006-09-04 18:17:58 +02004537 print $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
4538 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01004539 -title => $fullname}, esc_path($dir));
Petr Baudis26d0a972006-09-23 01:00:12 +02004540 print " / ";
Jakub Narebski762c7202006-09-04 18:17:58 +02004541 }
4542 if (defined $type && $type eq 'blob') {
Jakub Narebski952c65f2006-08-22 16:52:50 +02004543 print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name,
Jakub Narebski762c7202006-09-04 18:17:58 +02004544 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01004545 -title => $name}, esc_path($basename));
Jakub Narebski762c7202006-09-04 18:17:58 +02004546 } elsif (defined $type && $type eq 'tree') {
4547 print $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
4548 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01004549 -title => $name}, esc_path($basename));
Jakub Narebski4df118e2006-10-21 17:53:55 +02004550 print " / ";
Luben Tuikov59fb1c92006-08-17 10:39:29 -07004551 } else {
Jakub Narebski403d0902006-11-08 11:48:56 +01004552 print esc_path($basename);
Luben Tuikov59fb1c92006-08-17 10:39:29 -07004553 }
Kay Sievers8ed23e12005-08-07 20:05:44 +02004554 }
Jakub Narebski4df118e2006-10-21 17:53:55 +02004555 print "<br/></div>\n";
Kay Sievers4c02e3c2005-08-07 19:52:52 +02004556}
4557
Jakub Narebski74fd8722009-05-07 19:11:29 +02004558sub git_print_log {
Jakub Narebskid16d0932006-08-17 11:21:23 +02004559 my $log = shift;
Jakub Narebskib7f92532006-08-28 14:48:10 +02004560 my %opts = @_;
Jakub Narebskid16d0932006-08-17 11:21:23 +02004561
Jakub Narebskib7f92532006-08-28 14:48:10 +02004562 if ($opts{'-remove_title'}) {
4563 # remove title, i.e. first line of log
4564 shift @$log;
4565 }
Jakub Narebskid16d0932006-08-17 11:21:23 +02004566 # remove leading empty lines
4567 while (defined $log->[0] && $log->[0] eq "") {
4568 shift @$log;
4569 }
4570
4571 # print log
Namhyung Kim5a45c0c2012-07-04 11:47:24 +09004572 my $skip_blank_line = 0;
Jakub Narebskid16d0932006-08-17 11:21:23 +02004573 foreach my $line (@$log) {
Namhyung Kim3d1110a2012-07-04 11:47:25 +09004574 if ($line =~ m/^\s*([A-Z][-A-Za-z]*-[Bb]y|C[Cc]): /) {
Jakub Narebskib7f92532006-08-28 14:48:10 +02004575 if (! $opts{'-remove_signoff'}) {
4576 print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n";
Namhyung Kim5a45c0c2012-07-04 11:47:24 +09004577 $skip_blank_line = 1;
Jakub Narebskib7f92532006-08-28 14:48:10 +02004578 }
Namhyung Kim5a45c0c2012-07-04 11:47:24 +09004579 next;
Jakub Narebskib7f92532006-08-28 14:48:10 +02004580 }
4581
Namhyung Kim66c857e2012-07-04 11:47:26 +09004582 if ($line =~ m,\s*([a-z]*link): (https?://\S+),i) {
4583 if (! $opts{'-remove_signoff'}) {
4584 print "<span class=\"signoff\">" . esc_html($1) . ": " .
4585 "<a href=\"" . esc_html($2) . "\">" . esc_html($2) . "</a>" .
4586 "</span><br/>\n";
4587 $skip_blank_line = 1;
4588 }
4589 next;
Jakub Narebskid16d0932006-08-17 11:21:23 +02004590 }
4591
4592 # print only one empty line
4593 # do not print empty line after signoff
4594 if ($line eq "") {
Namhyung Kim5a45c0c2012-07-04 11:47:24 +09004595 next if ($skip_blank_line);
4596 $skip_blank_line = 1;
Jakub Narebskid16d0932006-08-17 11:21:23 +02004597 } else {
Namhyung Kim5a45c0c2012-07-04 11:47:24 +09004598 $skip_blank_line = 0;
Jakub Narebskid16d0932006-08-17 11:21:23 +02004599 }
Jakub Narebskib7f92532006-08-28 14:48:10 +02004600
4601 print format_log_line_html($line) . "<br/>\n";
4602 }
4603
4604 if ($opts{'-final_empty_line'}) {
4605 # end with single empty line
Namhyung Kim5a45c0c2012-07-04 11:47:24 +09004606 print "<br/>\n" unless $skip_blank_line;
Jakub Narebskid16d0932006-08-17 11:21:23 +02004607 }
4608}
4609
Jakub Narebskie33fba42006-12-10 13:25:46 +01004610# return link target (what link points to)
4611sub git_get_link_target {
4612 my $hash = shift;
4613 my $link_target;
4614
4615 # read link
4616 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
4617 or return;
4618 {
Jakub Narebski34122b52009-05-11 03:29:40 +02004619 local $/ = undef;
Jakub Narebskie33fba42006-12-10 13:25:46 +01004620 $link_target = <$fd>;
4621 }
4622 close $fd
4623 or return;
4624
4625 return $link_target;
4626}
4627
Jakub Narebski3bf9d572006-12-10 13:25:48 +01004628# given link target, and the directory (basedir) the link is in,
4629# return target of link relative to top directory (top tree);
4630# return undef if it is not possible (including absolute links).
4631sub normalize_link_target {
Jakub Narebski15c54fe2009-05-11 19:45:11 +02004632 my ($link_target, $basedir) = @_;
Jakub Narebski3bf9d572006-12-10 13:25:48 +01004633
4634 # absolute symlinks (beginning with '/') cannot be normalized
4635 return if (substr($link_target, 0, 1) eq '/');
4636
4637 # normalize link target to path from top (root) tree (dir)
4638 my $path;
4639 if ($basedir) {
4640 $path = $basedir . '/' . $link_target;
4641 } else {
4642 # we are in top (root) tree (dir)
4643 $path = $link_target;
4644 }
4645
4646 # remove //, /./, and /../
4647 my @path_parts;
4648 foreach my $part (split('/', $path)) {
4649 # discard '.' and ''
4650 next if (!$part || $part eq '.');
4651 # handle '..'
4652 if ($part eq '..') {
4653 if (@path_parts) {
4654 pop @path_parts;
4655 } else {
4656 # link leads outside repository (outside top dir)
4657 return;
4658 }
4659 } else {
4660 push @path_parts, $part;
4661 }
4662 }
4663 $path = join('/', @path_parts);
4664
4665 return $path;
4666}
Jakub Narebskie33fba42006-12-10 13:25:46 +01004667
Jakub Narebskifa702002006-08-31 00:35:07 +02004668# print tree entry (row of git_tree), but without encompassing <tr> element
4669sub git_print_tree_entry {
4670 my ($t, $basedir, $hash_base, $have_blame) = @_;
4671
4672 my %base_key = ();
Jakub Narebskie33fba42006-12-10 13:25:46 +01004673 $base_key{'hash_base'} = $hash_base if defined $hash_base;
Jakub Narebskifa702002006-08-31 00:35:07 +02004674
Luben Tuikov4de741b2006-09-25 22:38:16 -07004675 # The format of a table row is: mode list link. Where mode is
4676 # the mode of the entry, list is the name of the entry, an href,
4677 # and link is the action links of the entry.
4678
Jakub Narebskifa702002006-08-31 00:35:07 +02004679 print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n";
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02004680 if (exists $t->{'size'}) {
4681 print "<td class=\"size\">$t->{'size'}</td>\n";
4682 }
Jakub Narebskifa702002006-08-31 00:35:07 +02004683 if ($t->{'type'} eq "blob") {
4684 print "<td class=\"list\">" .
Luben Tuikov4de741b2006-09-25 22:38:16 -07004685 $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02004686 file_name=>"$basedir$t->{'name'}", %base_key),
Jakub Narebskie33fba42006-12-10 13:25:46 +01004687 -class => "list"}, esc_path($t->{'name'}));
4688 if (S_ISLNK(oct $t->{'mode'})) {
4689 my $link_target = git_get_link_target($t->{'hash'});
4690 if ($link_target) {
Jakub Narebski15c54fe2009-05-11 19:45:11 +02004691 my $norm_target = normalize_link_target($link_target, $basedir);
Jakub Narebski3bf9d572006-12-10 13:25:48 +01004692 if (defined $norm_target) {
4693 print " -> " .
4694 $cgi->a({-href => href(action=>"object", hash_base=>$hash_base,
4695 file_name=>$norm_target),
4696 -title => $norm_target}, esc_path($link_target));
4697 } else {
4698 print " -> " . esc_path($link_target);
4699 }
Jakub Narebskie33fba42006-12-10 13:25:46 +01004700 }
4701 }
4702 print "</td>\n";
Luben Tuikov4de741b2006-09-25 22:38:16 -07004703 print "<td class=\"link\">";
Petr Baudis4777b012006-10-24 05:36:10 +02004704 print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
Jakub Narebskie33fba42006-12-10 13:25:46 +01004705 file_name=>"$basedir$t->{'name'}", %base_key)},
4706 "blob");
Jakub Narebskifa702002006-08-31 00:35:07 +02004707 if ($have_blame) {
Petr Baudis4777b012006-10-24 05:36:10 +02004708 print " | " .
4709 $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
Jakub Narebskie33fba42006-12-10 13:25:46 +01004710 file_name=>"$basedir$t->{'name'}", %base_key)},
4711 "blame");
Jakub Narebskifa702002006-08-31 00:35:07 +02004712 }
4713 if (defined $hash_base) {
Petr Baudis4777b012006-10-24 05:36:10 +02004714 print " | " .
4715 $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
Jakub Narebskifa702002006-08-31 00:35:07 +02004716 hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
4717 "history");
4718 }
4719 print " | " .
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07004720 $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base,
Jakub Narebskie7fb0222006-10-21 17:52:19 +02004721 file_name=>"$basedir$t->{'name'}")},
4722 "raw");
Luben Tuikov4de741b2006-09-25 22:38:16 -07004723 print "</td>\n";
Jakub Narebskifa702002006-08-31 00:35:07 +02004724
4725 } elsif ($t->{'type'} eq "tree") {
Luben Tuikov0fa105e2006-09-26 12:45:37 -07004726 print "<td class=\"list\">";
4727 print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02004728 file_name=>"$basedir$t->{'name'}",
4729 %base_key)},
Jakub Narebski403d0902006-11-08 11:48:56 +01004730 esc_path($t->{'name'}));
Luben Tuikov0fa105e2006-09-26 12:45:37 -07004731 print "</td>\n";
4732 print "<td class=\"link\">";
Petr Baudis4777b012006-10-24 05:36:10 +02004733 print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02004734 file_name=>"$basedir$t->{'name'}",
4735 %base_key)},
Jakub Narebskie33fba42006-12-10 13:25:46 +01004736 "tree");
Jakub Narebskifa702002006-08-31 00:35:07 +02004737 if (defined $hash_base) {
Petr Baudis4777b012006-10-24 05:36:10 +02004738 print " | " .
4739 $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
Jakub Narebskifa702002006-08-31 00:35:07 +02004740 file_name=>"$basedir$t->{'name'}")},
4741 "history");
4742 }
4743 print "</td>\n";
Jakub Narebski01ac1e32007-07-28 16:27:31 +02004744 } else {
4745 # unknown object: we can only present history for it
4746 # (this includes 'commit' object, i.e. submodule support)
4747 print "<td class=\"list\">" .
4748 esc_path($t->{'name'}) .
4749 "</td>\n";
4750 print "<td class=\"link\">";
4751 if (defined $hash_base) {
4752 print $cgi->a({-href => href(action=>"history",
4753 hash_base=>$hash_base,
4754 file_name=>"$basedir$t->{'name'}")},
4755 "history");
4756 }
4757 print "</td>\n";
Jakub Narebskifa702002006-08-31 00:35:07 +02004758 }
4759}
4760
Jakub Narebski717b8312006-07-31 21:22:15 +02004761## ......................................................................
4762## functions printing large fragments of HTML
Kay Sieversede5e102005-08-07 20:23:12 +02004763
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004764# get pre-image filenames for merge (combined) diff
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02004765sub fill_from_file_info {
4766 my ($diff, @parents) = @_;
4767
4768 $diff->{'from_file'} = [ ];
4769 $diff->{'from_file'}[$diff->{'nparents'} - 1] = undef;
4770 for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
4771 if ($diff->{'status'}[$i] eq 'R' ||
4772 $diff->{'status'}[$i] eq 'C') {
4773 $diff->{'from_file'}[$i] =
4774 git_get_path_by_hash($parents[$i], $diff->{'from_id'}[$i]);
4775 }
4776 }
4777
4778 return $diff;
4779}
4780
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004781# is current raw difftree line of file deletion
Jakub Narebski90921742007-06-08 13:27:42 +02004782sub is_deleted {
4783 my $diffinfo = shift;
4784
Jakub Narebski4ed4a342008-04-05 21:13:24 +01004785 return $diffinfo->{'to_id'} eq ('0' x 40);
Jakub Narebski90921742007-06-08 13:27:42 +02004786}
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02004787
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004788# does patch correspond to [previous] difftree raw line
4789# $diffinfo - hashref of parsed raw diff format
4790# $patchinfo - hashref of parsed patch diff format
4791# (the same keys as in $diffinfo)
4792sub is_patch_split {
4793 my ($diffinfo, $patchinfo) = @_;
4794
4795 return defined $diffinfo && defined $patchinfo
Jakub Narebski9d301452007-11-01 12:38:08 +01004796 && $diffinfo->{'to_file'} eq $patchinfo->{'to_file'};
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004797}
4798
4799
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004800sub git_difftree_body {
Jakub Narebskied224de2007-05-07 01:10:04 +02004801 my ($difftree, $hash, @parents) = @_;
4802 my ($parent) = $parents[0];
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004803 my $have_blame = gitweb_check_feature('blame');
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004804 print "<div class=\"list_head\">\n";
4805 if ($#{$difftree} > 10) {
4806 print(($#{$difftree} + 1) . " files changed:\n");
4807 }
4808 print "</div>\n";
4809
Jakub Narebskied224de2007-05-07 01:10:04 +02004810 print "<table class=\"" .
4811 (@parents > 1 ? "combined " : "") .
4812 "diff_tree\">\n";
Jakub Narebski47598d72007-06-08 13:24:56 +02004813
4814 # header only for combined diff in 'commitdiff' view
Jakub Narebski3ef408a2007-09-08 21:54:28 +02004815 my $has_header = @$difftree && @parents > 1 && $action eq 'commitdiff';
Jakub Narebski47598d72007-06-08 13:24:56 +02004816 if ($has_header) {
4817 # table header
4818 print "<thead><tr>\n" .
4819 "<th></th><th></th>\n"; # filename, patchN link
4820 for (my $i = 0; $i < @parents; $i++) {
4821 my $par = $parents[$i];
4822 print "<th>" .
4823 $cgi->a({-href => href(action=>"commitdiff",
4824 hash=>$hash, hash_parent=>$par),
4825 -title => 'commitdiff to parent number ' .
4826 ($i+1) . ': ' . substr($par,0,7)},
4827 $i+1) .
4828 "&nbsp;</th>\n";
4829 }
4830 print "</tr></thead>\n<tbody>\n";
4831 }
4832
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004833 my $alternate = 1;
Jakub Narebskib4657e72006-08-28 14:48:14 +02004834 my $patchno = 0;
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004835 foreach my $line (@{$difftree}) {
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004836 my $diff = parsed_difftree_line($line);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004837
4838 if ($alternate) {
4839 print "<tr class=\"dark\">\n";
4840 } else {
4841 print "<tr class=\"light\">\n";
4842 }
4843 $alternate ^= 1;
4844
Jakub Narebski493e01d2007-05-07 01:10:06 +02004845 if (exists $diff->{'nparents'}) { # combined diff
Jakub Narebskied224de2007-05-07 01:10:04 +02004846
Jakub Narebski493e01d2007-05-07 01:10:06 +02004847 fill_from_file_info($diff, @parents)
4848 unless exists $diff->{'from_file'};
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02004849
Jakub Narebski90921742007-06-08 13:27:42 +02004850 if (!is_deleted($diff)) {
Jakub Narebskied224de2007-05-07 01:10:04 +02004851 # file exists in the result (child) commit
4852 print "<td>" .
Jakub Narebski493e01d2007-05-07 01:10:06 +02004853 $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4854 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02004855 hash_base=>$hash),
Jakub Narebski493e01d2007-05-07 01:10:06 +02004856 -class => "list"}, esc_path($diff->{'to_file'})) .
Jakub Narebskied224de2007-05-07 01:10:04 +02004857 "</td>\n";
4858 } else {
4859 print "<td>" .
Jakub Narebski493e01d2007-05-07 01:10:06 +02004860 esc_path($diff->{'to_file'}) .
Jakub Narebskied224de2007-05-07 01:10:04 +02004861 "</td>\n";
4862 }
4863
4864 if ($action eq 'commitdiff') {
4865 # link to patch
4866 $patchno++;
4867 print "<td class=\"link\">" .
Kevin Cernekee5e96a842011-03-18 17:00:16 +01004868 $cgi->a({-href => href(-anchor=>"patch$patchno")},
4869 "patch") .
Jakub Narebskied224de2007-05-07 01:10:04 +02004870 " | " .
4871 "</td>\n";
4872 }
4873
4874 my $has_history = 0;
4875 my $not_deleted = 0;
Jakub Narebski493e01d2007-05-07 01:10:06 +02004876 for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
Jakub Narebskied224de2007-05-07 01:10:04 +02004877 my $hash_parent = $parents[$i];
Jakub Narebski493e01d2007-05-07 01:10:06 +02004878 my $from_hash = $diff->{'from_id'}[$i];
4879 my $from_path = $diff->{'from_file'}[$i];
4880 my $status = $diff->{'status'}[$i];
Jakub Narebskied224de2007-05-07 01:10:04 +02004881
4882 $has_history ||= ($status ne 'A');
4883 $not_deleted ||= ($status ne 'D');
4884
Jakub Narebskied224de2007-05-07 01:10:04 +02004885 if ($status eq 'A') {
4886 print "<td class=\"link\" align=\"right\"> | </td>\n";
4887 } elsif ($status eq 'D') {
4888 print "<td class=\"link\">" .
4889 $cgi->a({-href => href(action=>"blob",
4890 hash_base=>$hash,
4891 hash=>$from_hash,
4892 file_name=>$from_path)},
4893 "blob" . ($i+1)) .
4894 " | </td>\n";
4895 } else {
Jakub Narebski493e01d2007-05-07 01:10:06 +02004896 if ($diff->{'to_id'} eq $from_hash) {
Jakub Narebskied224de2007-05-07 01:10:04 +02004897 print "<td class=\"link nochange\">";
4898 } else {
4899 print "<td class=\"link\">";
4900 }
4901 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02004902 hash=>$diff->{'to_id'},
Jakub Narebskied224de2007-05-07 01:10:04 +02004903 hash_parent=>$from_hash,
4904 hash_base=>$hash,
4905 hash_parent_base=>$hash_parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004906 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02004907 file_parent=>$from_path)},
4908 "diff" . ($i+1)) .
4909 " | </td>\n";
4910 }
4911 }
4912
4913 print "<td class=\"link\">";
4914 if ($not_deleted) {
4915 print $cgi->a({-href => href(action=>"blob",
Jakub Narebski493e01d2007-05-07 01:10:06 +02004916 hash=>$diff->{'to_id'},
4917 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02004918 hash_base=>$hash)},
4919 "blob");
4920 print " | " if ($has_history);
4921 }
4922 if ($has_history) {
4923 print $cgi->a({-href => href(action=>"history",
Jakub Narebski493e01d2007-05-07 01:10:06 +02004924 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02004925 hash_base=>$hash)},
4926 "history");
4927 }
4928 print "</td>\n";
4929
4930 print "</tr>\n";
4931 next; # instead of 'else' clause, to avoid extra indent
4932 }
4933 # else ordinary diff
4934
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004935 my ($to_mode_oct, $to_mode_str, $to_file_type);
4936 my ($from_mode_oct, $from_mode_str, $from_file_type);
Jakub Narebski493e01d2007-05-07 01:10:06 +02004937 if ($diff->{'to_mode'} ne ('0' x 6)) {
4938 $to_mode_oct = oct $diff->{'to_mode'};
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004939 if (S_ISREG($to_mode_oct)) { # only for regular file
4940 $to_mode_str = sprintf("%04o", $to_mode_oct & 0777); # permission bits
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004941 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004942 $to_file_type = file_type($diff->{'to_mode'});
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004943 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004944 if ($diff->{'from_mode'} ne ('0' x 6)) {
4945 $from_mode_oct = oct $diff->{'from_mode'};
Ævar Arnfjörð Bjarmason98885c22011-02-19 15:27:42 +00004946 if (S_ISREG($from_mode_oct)) { # only for regular file
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004947 $from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits
4948 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004949 $from_file_type = file_type($diff->{'from_mode'});
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004950 }
4951
Jakub Narebski493e01d2007-05-07 01:10:06 +02004952 if ($diff->{'status'} eq "A") { # created
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004953 my $mode_chng = "<span class=\"file_status new\">[new $to_file_type";
4954 $mode_chng .= " with mode: $to_mode_str" if $to_mode_str;
4955 $mode_chng .= "]</span>";
Luben Tuikov499faed2006-09-27 17:23:25 -07004956 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004957 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4958 hash_base=>$hash, file_name=>$diff->{'file'}),
4959 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07004960 print "</td>\n";
4961 print "<td>$mode_chng</td>\n";
4962 print "<td class=\"link\">";
Jakub Narebski72dbafa2006-09-04 18:19:58 +02004963 if ($action eq 'commitdiff') {
Jakub Narebskib4657e72006-08-28 14:48:14 +02004964 # link to patch
4965 $patchno++;
Kevin Cernekee5e96a842011-03-18 17:00:16 +01004966 print $cgi->a({-href => href(-anchor=>"patch$patchno")},
4967 "patch") .
4968 " | ";
Jakub Narebskib4657e72006-08-28 14:48:14 +02004969 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004970 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4971 hash_base=>$hash, file_name=>$diff->{'file'})},
Jakub Narebski3faa5412007-01-08 02:10:42 +01004972 "blob");
Jakub Narebskib4657e72006-08-28 14:48:14 +02004973 print "</td>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004974
Jakub Narebski493e01d2007-05-07 01:10:06 +02004975 } elsif ($diff->{'status'} eq "D") { # deleted
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004976 my $mode_chng = "<span class=\"file_status deleted\">[deleted $from_file_type]</span>";
Luben Tuikov499faed2006-09-27 17:23:25 -07004977 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004978 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
4979 hash_base=>$parent, file_name=>$diff->{'file'}),
4980 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07004981 print "</td>\n";
4982 print "<td>$mode_chng</td>\n";
4983 print "<td class=\"link\">";
Jakub Narebski72dbafa2006-09-04 18:19:58 +02004984 if ($action eq 'commitdiff') {
Jakub Narebskib4657e72006-08-28 14:48:14 +02004985 # link to patch
4986 $patchno++;
Kevin Cernekee5e96a842011-03-18 17:00:16 +01004987 print $cgi->a({-href => href(-anchor=>"patch$patchno")},
4988 "patch") .
4989 " | ";
Jakub Narebskib4657e72006-08-28 14:48:14 +02004990 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004991 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
4992 hash_base=>$parent, file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004993 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004994 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01004995 print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004996 file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004997 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004998 }
Jakub Narebskib4657e72006-08-28 14:48:14 +02004999 print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02005000 file_name=>$diff->{'file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02005001 "history");
Luben Tuikov499faed2006-09-27 17:23:25 -07005002 print "</td>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005003
Jakub Narebski493e01d2007-05-07 01:10:06 +02005004 } elsif ($diff->{'status'} eq "M" || $diff->{'status'} eq "T") { # modified, or type changed
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005005 my $mode_chnge = "";
Jakub Narebski493e01d2007-05-07 01:10:06 +02005006 if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02005007 $mode_chnge = "<span class=\"file_status mode_chnge\">[changed";
Jakub Narebski6e72cf42007-01-03 20:47:24 +01005008 if ($from_file_type ne $to_file_type) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02005009 $mode_chnge .= " from $from_file_type to $to_file_type";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005010 }
Jakub Narebskie8e41a92006-08-21 23:08:52 +02005011 if (($from_mode_oct & 0777) != ($to_mode_oct & 0777)) {
5012 if ($from_mode_str && $to_mode_str) {
5013 $mode_chnge .= " mode: $from_mode_str->$to_mode_str";
5014 } elsif ($to_mode_str) {
5015 $mode_chnge .= " mode: $to_mode_str";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005016 }
5017 }
5018 $mode_chnge .= "]</span>\n";
5019 }
5020 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02005021 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
5022 hash_base=>$hash, file_name=>$diff->{'file'}),
5023 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07005024 print "</td>\n";
5025 print "<td>$mode_chnge</td>\n";
5026 print "<td class=\"link\">";
Jakub Narebski241cc592006-10-31 17:36:27 +01005027 if ($action eq 'commitdiff') {
5028 # link to patch
5029 $patchno++;
Kevin Cernekee5e96a842011-03-18 17:00:16 +01005030 print $cgi->a({-href => href(-anchor=>"patch$patchno")},
5031 "patch") .
Jakub Narebski241cc592006-10-31 17:36:27 +01005032 " | ";
Jakub Narebski493e01d2007-05-07 01:10:06 +02005033 } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
Jakub Narebski241cc592006-10-31 17:36:27 +01005034 # "commit" view and modified file (not onlu mode changed)
5035 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02005036 hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
Jakub Narebski241cc592006-10-31 17:36:27 +01005037 hash_base=>$hash, hash_parent_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02005038 file_name=>$diff->{'file'})},
Jakub Narebski241cc592006-10-31 17:36:27 +01005039 "diff") .
5040 " | ";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005041 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02005042 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
5043 hash_base=>$hash, file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01005044 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08005045 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01005046 print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02005047 file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01005048 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08005049 }
Luben Tuikoveb51ec92006-09-27 17:24:49 -07005050 print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02005051 file_name=>$diff->{'file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02005052 "history");
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005053 print "</td>\n";
5054
Jakub Narebski493e01d2007-05-07 01:10:06 +02005055 } elsif ($diff->{'status'} eq "R" || $diff->{'status'} eq "C") { # renamed or copied
Jakub Narebskie8e41a92006-08-21 23:08:52 +02005056 my %status_name = ('R' => 'moved', 'C' => 'copied');
Jakub Narebski493e01d2007-05-07 01:10:06 +02005057 my $nstatus = $status_name{$diff->{'status'}};
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005058 my $mode_chng = "";
Jakub Narebski493e01d2007-05-07 01:10:06 +02005059 if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02005060 # mode also for directories, so we cannot use $to_mode_str
5061 $mode_chng = sprintf(", mode: %04o", $to_mode_oct & 0777);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005062 }
5063 print "<td>" .
Jakub Narebskie8e41a92006-08-21 23:08:52 +02005064 $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02005065 hash=>$diff->{'to_id'}, file_name=>$diff->{'to_file'}),
5066 -class => "list"}, esc_path($diff->{'to_file'})) . "</td>\n" .
Jakub Narebskie8e41a92006-08-21 23:08:52 +02005067 "<td><span class=\"file_status $nstatus\">[$nstatus from " .
5068 $cgi->a({-href => href(action=>"blob", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02005069 hash=>$diff->{'from_id'}, file_name=>$diff->{'from_file'}),
5070 -class => "list"}, esc_path($diff->{'from_file'})) .
5071 " with " . (int $diff->{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
Luben Tuikov499faed2006-09-27 17:23:25 -07005072 "<td class=\"link\">";
Jakub Narebski241cc592006-10-31 17:36:27 +01005073 if ($action eq 'commitdiff') {
5074 # link to patch
5075 $patchno++;
Kevin Cernekee5e96a842011-03-18 17:00:16 +01005076 print $cgi->a({-href => href(-anchor=>"patch$patchno")},
5077 "patch") .
Jakub Narebski241cc592006-10-31 17:36:27 +01005078 " | ";
Jakub Narebski493e01d2007-05-07 01:10:06 +02005079 } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
Jakub Narebski241cc592006-10-31 17:36:27 +01005080 # "commit" view and modified file (not only pure rename or copy)
5081 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02005082 hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
Jakub Narebski241cc592006-10-31 17:36:27 +01005083 hash_base=>$hash, hash_parent_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02005084 file_name=>$diff->{'to_file'}, file_parent=>$diff->{'from_file'})},
Jakub Narebski241cc592006-10-31 17:36:27 +01005085 "diff") .
5086 " | ";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005087 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02005088 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
5089 hash_base=>$parent, file_name=>$diff->{'to_file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01005090 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08005091 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01005092 print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02005093 file_name=>$diff->{'to_file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01005094 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08005095 }
Jakub Narebski897d1d22006-11-19 22:51:39 +01005096 print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02005097 file_name=>$diff->{'to_file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02005098 "history");
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005099 print "</td>\n";
5100
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005101 } # we should not encounter Unmerged (U) or Unknown (X) status
5102 print "</tr>\n";
5103 }
Jakub Narebski47598d72007-06-08 13:24:56 +02005104 print "</tbody>" if $has_header;
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005105 print "</table>\n";
5106}
5107
Michał Kiedrowiczd21102c2012-04-11 23:18:40 +02005108# Print context lines and then rem/add lines in a side-by-side manner.
5109sub print_sidebyside_diff_lines {
5110 my ($ctx, $rem, $add) = @_;
5111
5112 # print context block before add/rem block
5113 if (@$ctx) {
5114 print join '',
5115 '<div class="chunk_block ctx">',
5116 '<div class="old">',
5117 @$ctx,
5118 '</div>',
5119 '<div class="new">',
5120 @$ctx,
5121 '</div>',
5122 '</div>';
5123 }
5124
5125 if (!@$add) {
5126 # pure removal
5127 print join '',
5128 '<div class="chunk_block rem">',
5129 '<div class="old">',
5130 @$rem,
5131 '</div>',
5132 '</div>';
5133 } elsif (!@$rem) {
5134 # pure addition
5135 print join '',
5136 '<div class="chunk_block add">',
5137 '<div class="new">',
5138 @$add,
5139 '</div>',
5140 '</div>';
5141 } else {
5142 print join '',
5143 '<div class="chunk_block chg">',
5144 '<div class="old">',
5145 @$rem,
5146 '</div>',
5147 '<div class="new">',
5148 @$add,
5149 '</div>',
5150 '</div>';
5151 }
5152}
5153
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005154# Print context lines and then rem/add lines in inline manner.
5155sub print_inline_diff_lines {
5156 my ($ctx, $rem, $add) = @_;
5157
5158 print @$ctx, @$rem, @$add;
5159}
5160
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02005161# Format removed and added line, mark changed part and HTML-format them.
5162# Implementation is based on contrib/diff-highlight
5163sub format_rem_add_lines_pair {
Michał Kiedrowicz51ef7a62012-04-11 23:18:44 +02005164 my ($rem, $add, $num_parents) = @_;
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02005165
5166 # We need to untabify lines before split()'ing them;
5167 # otherwise offsets would be invalid.
5168 chomp $rem;
5169 chomp $add;
5170 $rem = untabify($rem);
5171 $add = untabify($add);
5172
5173 my @rem = split(//, $rem);
5174 my @add = split(//, $add);
5175 my ($esc_rem, $esc_add);
Michał Kiedrowicz51ef7a62012-04-11 23:18:44 +02005176 # Ignore leading +/- characters for each parent.
5177 my ($prefix_len, $suffix_len) = ($num_parents, 0);
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02005178 my ($prefix_has_nonspace, $suffix_has_nonspace);
5179
5180 my $shorter = (@rem < @add) ? @rem : @add;
5181 while ($prefix_len < $shorter) {
5182 last if ($rem[$prefix_len] ne $add[$prefix_len]);
5183
5184 $prefix_has_nonspace = 1 if ($rem[$prefix_len] !~ /\s/);
5185 $prefix_len++;
5186 }
5187
5188 while ($prefix_len + $suffix_len < $shorter) {
5189 last if ($rem[-1 - $suffix_len] ne $add[-1 - $suffix_len]);
5190
5191 $suffix_has_nonspace = 1 if ($rem[-1 - $suffix_len] !~ /\s/);
5192 $suffix_len++;
5193 }
5194
5195 # Mark lines that are different from each other, but have some common
5196 # part that isn't whitespace. If lines are completely different, don't
5197 # mark them because that would make output unreadable, especially if
5198 # diff consists of multiple lines.
5199 if ($prefix_has_nonspace || $suffix_has_nonspace) {
5200 $esc_rem = esc_html_hl_regions($rem, 'marked',
5201 [$prefix_len, @rem - $suffix_len], -nbsp=>1);
5202 $esc_add = esc_html_hl_regions($add, 'marked',
5203 [$prefix_len, @add - $suffix_len], -nbsp=>1);
5204 } else {
5205 $esc_rem = esc_html($rem, -nbsp=>1);
5206 $esc_add = esc_html($add, -nbsp=>1);
5207 }
5208
5209 return format_diff_line(\$esc_rem, 'rem'),
5210 format_diff_line(\$esc_add, 'add');
5211}
5212
5213# HTML-format diff context, removed and added lines.
5214sub format_ctx_rem_add_lines {
Michał Kiedrowicz51ef7a62012-04-11 23:18:44 +02005215 my ($ctx, $rem, $add, $num_parents) = @_;
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02005216 my (@new_ctx, @new_rem, @new_add);
Michał Kiedrowicz51ef7a62012-04-11 23:18:44 +02005217 my $can_highlight = 0;
5218 my $is_combined = ($num_parents > 1);
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02005219
5220 # Highlight if every removed line has a corresponding added line.
Michał Kiedrowicz51ef7a62012-04-11 23:18:44 +02005221 if (@$add > 0 && @$add == @$rem) {
5222 $can_highlight = 1;
5223
5224 # Highlight lines in combined diff only if the chunk contains
5225 # diff between the same version, e.g.
5226 #
5227 # - a
5228 # - b
5229 # + c
5230 # + d
5231 #
5232 # Otherwise the highlightling would be confusing.
5233 if ($is_combined) {
5234 for (my $i = 0; $i < @$add; $i++) {
5235 my $prefix_rem = substr($rem->[$i], 0, $num_parents);
5236 my $prefix_add = substr($add->[$i], 0, $num_parents);
5237
5238 $prefix_rem =~ s/-/+/g;
5239
5240 if ($prefix_rem ne $prefix_add) {
5241 $can_highlight = 0;
5242 last;
5243 }
5244 }
5245 }
5246 }
5247
5248 if ($can_highlight) {
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02005249 for (my $i = 0; $i < @$add; $i++) {
5250 my ($line_rem, $line_add) = format_rem_add_lines_pair(
Michał Kiedrowicz51ef7a62012-04-11 23:18:44 +02005251 $rem->[$i], $add->[$i], $num_parents);
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02005252 push @new_rem, $line_rem;
5253 push @new_add, $line_add;
5254 }
5255 } else {
5256 @new_rem = map { format_diff_line($_, 'rem') } @$rem;
5257 @new_add = map { format_diff_line($_, 'add') } @$add;
5258 }
5259
5260 @new_ctx = map { format_diff_line($_, 'ctx') } @$ctx;
5261
5262 return (\@new_ctx, \@new_rem, \@new_add);
5263}
5264
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005265# Print context lines and then rem/add lines.
5266sub print_diff_lines {
Michał Kiedrowicz51ef7a62012-04-11 23:18:44 +02005267 my ($ctx, $rem, $add, $diff_style, $num_parents) = @_;
5268 my $is_combined = $num_parents > 1;
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005269
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02005270 ($ctx, $rem, $add) = format_ctx_rem_add_lines($ctx, $rem, $add,
Michał Kiedrowicz51ef7a62012-04-11 23:18:44 +02005271 $num_parents);
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02005272
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005273 if ($diff_style eq 'sidebyside' && !$is_combined) {
5274 print_sidebyside_diff_lines($ctx, $rem, $add);
5275 } else {
5276 # default 'inline' style and unknown styles
5277 print_inline_diff_lines($ctx, $rem, $add);
5278 }
5279}
5280
5281sub print_diff_chunk {
Michał Kiedrowicz51ef7a62012-04-11 23:18:44 +02005282 my ($diff_style, $num_parents, $from, $to, @chunk) = @_;
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005283 my (@ctx, @rem, @add);
5284
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005285 # The class of the previous line.
5286 my $prev_class = '';
5287
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005288 return unless @chunk;
5289
5290 # incomplete last line might be among removed or added lines,
5291 # or both, or among context lines: find which
5292 for (my $i = 1; $i < @chunk; $i++) {
5293 if ($chunk[$i][0] eq 'incomplete') {
5294 $chunk[$i][0] = $chunk[$i-1][0];
5295 }
5296 }
5297
5298 # guardian
5299 push @chunk, ["", ""];
5300
5301 foreach my $line_info (@chunk) {
5302 my ($class, $line) = @$line_info;
5303
5304 # print chunk headers
5305 if ($class && $class eq 'chunk_header') {
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02005306 print format_diff_line($line, $class, $from, $to);
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005307 next;
5308 }
5309
Michał Kiedrowiczd21102c2012-04-11 23:18:40 +02005310 ## print from accumulator when have some add/rem lines or end
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005311 # of chunk (flush context lines), or when have add and rem
5312 # lines and new block is reached (otherwise add/rem lines could
5313 # be reordered)
5314 if (!$class || ((@rem || @add) && $class eq 'ctx') ||
5315 (@rem && @add && $class ne $prev_class)) {
5316 print_diff_lines(\@ctx, \@rem, \@add,
Michał Kiedrowicz51ef7a62012-04-11 23:18:44 +02005317 $diff_style, $num_parents);
Michał Kiedrowiczd21102c2012-04-11 23:18:40 +02005318 @ctx = @rem = @add = ();
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005319 }
5320
5321 ## adding lines to accumulator
5322 # guardian value
5323 last unless $line;
5324 # rem, add or change
5325 if ($class eq 'rem') {
5326 push @rem, $line;
5327 } elsif ($class eq 'add') {
5328 push @add, $line;
5329 }
5330 # context line
5331 if ($class eq 'ctx') {
5332 push @ctx, $line;
5333 }
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005334
5335 $prev_class = $class;
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005336 }
5337}
5338
Jakub Narebskieee08902006-08-24 00:15:14 +02005339sub git_patchset_body {
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005340 my ($fd, $diff_style, $difftree, $hash, @hash_parents) = @_;
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02005341 my ($hash_parent) = $hash_parents[0];
Jakub Narebskieee08902006-08-24 00:15:14 +02005342
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005343 my $is_combined = (@hash_parents > 1);
Jakub Narebskieee08902006-08-24 00:15:14 +02005344 my $patch_idx = 0;
Martin Koegler4280cde2007-04-22 22:49:25 -07005345 my $patch_number = 0;
Jakub Narebski6d55f052006-11-18 23:35:39 +01005346 my $patch_line;
Jakub Narebskife875852006-08-25 20:59:39 +02005347 my $diffinfo;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005348 my $to_name;
Jakub Narebski744d0ac2006-11-08 17:59:41 +01005349 my (%from, %to);
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005350 my @chunk; # for side-by-side diff
Jakub Narebskieee08902006-08-24 00:15:14 +02005351
5352 print "<div class=\"patchset\">\n";
5353
Jakub Narebski6d55f052006-11-18 23:35:39 +01005354 # skip to first patch
5355 while ($patch_line = <$fd>) {
Jakub Narebski157e43b2006-08-24 19:34:36 +02005356 chomp $patch_line;
Jakub Narebskieee08902006-08-24 00:15:14 +02005357
Jakub Narebski6d55f052006-11-18 23:35:39 +01005358 last if ($patch_line =~ m/^diff /);
5359 }
5360
5361 PATCH:
5362 while ($patch_line) {
Jakub Narebski6d55f052006-11-18 23:35:39 +01005363
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005364 # parse "git diff" header line
5365 if ($patch_line =~ m/^diff --git (\"(?:[^\\\"]*(?:\\.[^\\\"]*)*)\"|[^ "]*) (.*)$/) {
5366 # $1 is from_name, which we do not use
5367 $to_name = unquote($2);
5368 $to_name =~ s!^b/!!;
5369 } elsif ($patch_line =~ m/^diff --(cc|combined) ("?.*"?)$/) {
5370 # $1 is 'cc' or 'combined', which we do not use
5371 $to_name = unquote($2);
5372 } else {
5373 $to_name = undef;
Jakub Narebski6d55f052006-11-18 23:35:39 +01005374 }
Jakub Narebski6d55f052006-11-18 23:35:39 +01005375
5376 # check if current patch belong to current raw line
5377 # and parse raw git-diff line if needed
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005378 if (is_patch_split($diffinfo, { 'to_file' => $to_name })) {
Jakub Narebski22065372007-05-12 12:42:32 +02005379 # this is continuation of a split patch
Jakub Narebski6d55f052006-11-18 23:35:39 +01005380 print "<div class=\"patch cont\">\n";
5381 } else {
5382 # advance raw git-diff output if needed
5383 $patch_idx++ if defined $diffinfo;
Jakub Narebskieee08902006-08-24 00:15:14 +02005384
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005385 # read and prepare patch information
5386 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
5387
Jakub Narebskicd030c32007-06-08 13:33:28 +02005388 # compact combined diff output can have some patches skipped
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005389 # find which patch (using pathname of result) we are at now;
5390 if ($is_combined) {
5391 while ($to_name ne $diffinfo->{'to_file'}) {
Jakub Narebskicd030c32007-06-08 13:33:28 +02005392 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
5393 format_diff_cc_simplified($diffinfo, @hash_parents) .
5394 "</div>\n"; # class="patch"
5395
5396 $patch_idx++;
5397 $patch_number++;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005398
5399 last if $patch_idx > $#$difftree;
5400 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
Jakub Narebskicd030c32007-06-08 13:33:28 +02005401 }
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005402 }
Jakub Narebski711fa742007-09-08 21:49:11 +02005403
Jakub Narebski90921742007-06-08 13:27:42 +02005404 # modifies %from, %to hashes
5405 parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents);
Jakub Narebski5f855052007-05-17 22:54:28 +02005406
Jakub Narebski6d55f052006-11-18 23:35:39 +01005407 # this is first patch for raw difftree line with $patch_idx index
5408 # we index @$difftree array from 0, but number patches from 1
5409 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
Jakub Narebski744d0ac2006-11-08 17:59:41 +01005410 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005411
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005412 # git diff header
5413 #assert($patch_line =~ m/^diff /) if DEBUG;
5414 #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
5415 $patch_number++;
Jakub Narebski6d55f052006-11-18 23:35:39 +01005416 # print "git diff" header
Jakub Narebski90921742007-06-08 13:27:42 +02005417 print format_git_diff_header_line($patch_line, $diffinfo,
5418 \%from, \%to);
Jakub Narebskieee08902006-08-24 00:15:14 +02005419
Jakub Narebski6d55f052006-11-18 23:35:39 +01005420 # print extended diff header
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005421 print "<div class=\"diff extended_header\">\n";
Jakub Narebski6d55f052006-11-18 23:35:39 +01005422 EXTENDED_HEADER:
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005423 while ($patch_line = <$fd>) {
5424 chomp $patch_line;
5425
5426 last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /);
5427
Jakub Narebski90921742007-06-08 13:27:42 +02005428 print format_extended_diff_header_line($patch_line, $diffinfo,
5429 \%from, \%to);
Jakub Narebski6d55f052006-11-18 23:35:39 +01005430 }
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005431 print "</div>\n"; # class="diff extended_header"
Jakub Narebskie4e4f822006-08-25 21:04:13 +02005432
Jakub Narebski6d55f052006-11-18 23:35:39 +01005433 # from-file/to-file diff header
Jakub Narebski0bdb28c2007-01-10 00:07:43 +01005434 if (! $patch_line) {
5435 print "</div>\n"; # class="patch"
5436 last PATCH;
5437 }
Jakub Narebski66399ef2007-01-07 02:52:25 +01005438 next PATCH if ($patch_line =~ m/^diff /);
Jakub Narebski6d55f052006-11-18 23:35:39 +01005439 #assert($patch_line =~ m/^---/) if DEBUG;
Jakub Narebski6d55f052006-11-18 23:35:39 +01005440
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005441 my $last_patch_line = $patch_line;
Jakub Narebski6d55f052006-11-18 23:35:39 +01005442 $patch_line = <$fd>;
Jakub Narebski6d55f052006-11-18 23:35:39 +01005443 chomp $patch_line;
Jakub Narebski90921742007-06-08 13:27:42 +02005444 #assert($patch_line =~ m/^\+\+\+/) if DEBUG;
Jakub Narebski6d55f052006-11-18 23:35:39 +01005445
Jakub Narebski90921742007-06-08 13:27:42 +02005446 print format_diff_from_to_header($last_patch_line, $patch_line,
Jakub Narebski91af4ce2007-06-08 13:32:44 +02005447 $diffinfo, \%from, \%to,
5448 @hash_parents);
Jakub Narebski6d55f052006-11-18 23:35:39 +01005449
5450 # the patch itself
5451 LINE:
5452 while ($patch_line = <$fd>) {
5453 chomp $patch_line;
5454
5455 next PATCH if ($patch_line =~ m/^diff /);
5456
Michał Kiedrowiczf4a81022012-04-11 23:18:42 +02005457 my $class = diff_line_class($patch_line, \%from, \%to);
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005458
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005459 if ($class eq 'chunk_header') {
Michał Kiedrowicz51ef7a62012-04-11 23:18:44 +02005460 print_diff_chunk($diff_style, scalar @hash_parents, \%from, \%to, @chunk);
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005461 @chunk = ();
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005462 }
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005463
Michał Kiedrowiczf4a81022012-04-11 23:18:42 +02005464 push @chunk, [ $class, $patch_line ];
Jakub Narebskieee08902006-08-24 00:15:14 +02005465 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005466
Jakub Narebski6d55f052006-11-18 23:35:39 +01005467 } continue {
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005468 if (@chunk) {
Michał Kiedrowicz51ef7a62012-04-11 23:18:44 +02005469 print_diff_chunk($diff_style, scalar @hash_parents, \%from, \%to, @chunk);
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005470 @chunk = ();
5471 }
Jakub Narebski6d55f052006-11-18 23:35:39 +01005472 print "</div>\n"; # class="patch"
Jakub Narebskieee08902006-08-24 00:15:14 +02005473 }
Jakub Narebskid26c4262007-05-17 00:05:55 +02005474
Ralf Wildenhues22e5e582010-08-22 13:12:12 +02005475 # for compact combined (--cc) format, with chunk and patch simplification
5476 # the patchset might be empty, but there might be unprocessed raw lines
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005477 for (++$patch_idx if $patch_number > 0;
Jakub Narebskicd030c32007-06-08 13:33:28 +02005478 $patch_idx < @$difftree;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005479 ++$patch_idx) {
Jakub Narebskicd030c32007-06-08 13:33:28 +02005480 # read and prepare patch information
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005481 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
Jakub Narebskicd030c32007-06-08 13:33:28 +02005482
5483 # generate anchor for "patch" links in difftree / whatchanged part
5484 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
5485 format_diff_cc_simplified($diffinfo, @hash_parents) .
5486 "</div>\n"; # class="patch"
5487
5488 $patch_number++;
5489 }
5490
Jakub Narebskid26c4262007-05-17 00:05:55 +02005491 if ($patch_number == 0) {
5492 if (@hash_parents > 1) {
5493 print "<div class=\"diff nodifferences\">Trivial merge</div>\n";
5494 } else {
5495 print "<div class=\"diff nodifferences\">No differences found</div>\n";
5496 }
5497 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005498
5499 print "</div>\n"; # class="patchset"
5500}
5501
5502# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5503
Jakub Narebskia1e1b2d2012-01-31 01:20:54 +01005504sub git_project_search_form {
Jakub Narebskib22939a2012-03-02 23:50:01 +01005505 my ($searchtext, $search_use_regexp) = @_;
Jakub Narebskia1e1b2d2012-01-31 01:20:54 +01005506
Jakub Narebskiabc0c9d2012-01-31 01:20:55 +01005507 my $limit = '';
5508 if ($project_filter) {
5509 $limit = " in '$project_filter/'";
5510 }
5511
Jakub Narebskia1e1b2d2012-01-31 01:20:54 +01005512 print "<div class=\"projsearch\">\n";
5513 print $cgi->startform(-method => 'get', -action => $my_uri) .
Jakub Narebskiabc0c9d2012-01-31 01:20:55 +01005514 $cgi->hidden(-name => 'a', -value => 'project_list') . "\n";
5515 print $cgi->hidden(-name => 'pf', -value => $project_filter). "\n"
5516 if (defined $project_filter);
5517 print $cgi->textfield(-name => 's', -value => $searchtext,
5518 -title => "Search project by name and description$limit",
Jakub Narebskia1e1b2d2012-01-31 01:20:54 +01005519 -size => 60) . "\n" .
5520 "<span title=\"Extended regular expression\">" .
5521 $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
5522 -checked => $search_use_regexp) .
5523 "</span>\n" .
5524 $cgi->submit(-name => 'btnS', -value => 'Search') .
5525 $cgi->end_form() . "\n" .
Jakub Narebskiabc0c9d2012-01-31 01:20:55 +01005526 $cgi->a({-href => href(project => undef, searchtext => undef,
5527 project_filter => $project_filter)},
5528 esc_html("List all projects$limit")) . "<br />\n";
Jakub Narebskia1e1b2d2012-01-31 01:20:54 +01005529 print "</div>\n";
5530}
5531
Jakub Narebski14b289b2012-02-23 16:54:46 +01005532# entry for given @keys needs filling if at least one of keys in list
5533# is not present in %$project_info
5534sub project_info_needs_filling {
5535 my ($project_info, @keys) = @_;
5536
5537 # return List::MoreUtils::any { !exists $project_info->{$_} } @keys;
5538 foreach my $key (@keys) {
5539 if (!exists $project_info->{$key}) {
5540 return 1;
5541 }
5542 }
5543 return;
5544}
5545
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005546# fills project list info (age, description, owner, category, forks, etc.)
Sebastien Ceveyd940c902011-04-29 19:52:01 +02005547# for each project in the list, removing invalid projects from
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005548# returned list, or fill only specified info.
5549#
5550# Invalid projects are removed from the returned list if and only if you
5551# ask 'age' or 'age_string' to be filled, because they are the only fields
5552# that run unconditionally git command that requires repository, and
5553# therefore do always check if project repository is invalid.
5554#
5555# USAGE:
5556# * fill_project_list_info(\@project_list, 'descr_long', 'ctags')
5557# ensures that 'descr_long' and 'ctags' fields are filled
5558# * @project_list = fill_project_list_info(\@project_list)
5559# ensures that all fields are filled (and invalid projects removed)
5560#
Jakub Narebski69913412008-06-10 19:21:01 +02005561# NOTE: modifies $projlist, but does not remove entries from it
5562sub fill_project_list_info {
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005563 my ($projlist, @wanted_keys) = @_;
Petr Baudise30496d2006-10-24 05:33:17 +02005564 my @projects;
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005565 my $filter_set = sub { return @_; };
5566 if (@wanted_keys) {
5567 my %wanted_keys = map { $_ => 1 } @wanted_keys;
5568 $filter_set = sub { return grep { $wanted_keys{$_} } @_; };
5569 }
Jakub Narebski69913412008-06-10 19:21:01 +02005570
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08005571 my $show_ctags = gitweb_check_feature('ctags');
Jakub Narebski69913412008-06-10 19:21:01 +02005572 PROJECT:
Petr Baudise30496d2006-10-24 05:33:17 +02005573 foreach my $pr (@$projlist) {
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005574 if (project_info_needs_filling($pr, $filter_set->('age', 'age_string'))) {
Jakub Narebski14b289b2012-02-23 16:54:46 +01005575 my (@activity) = git_get_last_activity($pr->{'path'});
5576 unless (@activity) {
5577 next PROJECT;
5578 }
5579 ($pr->{'age'}, $pr->{'age_string'}) = @activity;
Petr Baudise30496d2006-10-24 05:33:17 +02005580 }
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005581 if (project_info_needs_filling($pr, $filter_set->('descr', 'descr_long'))) {
Petr Baudise30496d2006-10-24 05:33:17 +02005582 my $descr = git_get_project_description($pr->{'path'}) || "";
Jakub Narebski69913412008-06-10 19:21:01 +02005583 $descr = to_utf8($descr);
5584 $pr->{'descr_long'} = $descr;
Michael Hendricks55feb122007-07-04 18:36:48 -06005585 $pr->{'descr'} = chop_str($descr, $projects_list_description_width, 5);
Petr Baudise30496d2006-10-24 05:33:17 +02005586 }
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005587 if (project_info_needs_filling($pr, $filter_set->('owner'))) {
Miklos Vajna76e4f5d2007-07-04 00:11:23 +02005588 $pr->{'owner'} = git_get_project_owner("$pr->{'path'}") || "";
Petr Baudise30496d2006-10-24 05:33:17 +02005589 }
Jakub Narebski14b289b2012-02-23 16:54:46 +01005590 if ($show_ctags &&
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005591 project_info_needs_filling($pr, $filter_set->('ctags'))) {
Jakub Narebski12b14432011-04-29 19:51:56 +02005592 $pr->{'ctags'} = git_get_project_ctags($pr->{'path'});
Petr Baudise30496d2006-10-24 05:33:17 +02005593 }
Jakub Narebski14b289b2012-02-23 16:54:46 +01005594 if ($projects_list_group_categories &&
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005595 project_info_needs_filling($pr, $filter_set->('category'))) {
Sebastien Ceveyd940c902011-04-29 19:52:01 +02005596 my $cat = git_get_project_category($pr->{'path'}) ||
5597 $project_list_default_category;
5598 $pr->{'category'} = to_utf8($cat);
5599 }
5600
Petr Baudise30496d2006-10-24 05:33:17 +02005601 push @projects, $pr;
5602 }
5603
Jakub Narebski69913412008-06-10 19:21:01 +02005604 return @projects;
5605}
5606
Jakub Narebski12b14432011-04-29 19:51:56 +02005607sub sort_projects_list {
5608 my ($projlist, $order) = @_;
Jakub Narebski12b14432011-04-29 19:51:56 +02005609
Matthew Daley28dae182012-12-11 23:56:07 +13005610 sub order_str {
5611 my $key = shift;
5612 return sub { $a->{$key} cmp $b->{$key} };
Jakub Narebski12b14432011-04-29 19:51:56 +02005613 }
5614
Matthew Daley28dae182012-12-11 23:56:07 +13005615 sub order_num_then_undef {
5616 my $key = shift;
5617 return sub {
5618 defined $a->{$key} ?
5619 (defined $b->{$key} ? $a->{$key} <=> $b->{$key} : -1) :
5620 (defined $b->{$key} ? 1 : 0)
5621 };
5622 }
5623
5624 my %orderings = (
5625 project => order_str('path'),
5626 descr => order_str('descr_long'),
5627 owner => order_str('owner'),
5628 age => order_num_then_undef('age'),
5629 );
5630
5631 my $ordering = $orderings{$order};
5632 return defined $ordering ? sort $ordering @$projlist : @$projlist;
Jakub Narebski12b14432011-04-29 19:51:56 +02005633}
5634
Sebastien Ceveyd940c902011-04-29 19:52:01 +02005635# returns a hash of categories, containing the list of project
5636# belonging to each category
5637sub build_projlist_by_category {
5638 my ($projlist, $from, $to) = @_;
5639 my %categories;
5640
5641 $from = 0 unless defined $from;
5642 $to = $#$projlist if (!defined $to || $#$projlist < $to);
5643
5644 for (my $i = $from; $i <= $to; $i++) {
5645 my $pr = $projlist->[$i];
5646 push @{$categories{ $pr->{'category'} }}, $pr;
5647 }
5648
5649 return wantarray ? %categories : \%categories;
5650}
5651
Petr Baudis6b28da62008-09-25 18:48:37 +02005652# print 'sort by' <th> element, generating 'sort by $name' replay link
5653# if that order is not selected
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02005654sub print_sort_th {
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01005655 print format_sort_th(@_);
5656}
5657
5658sub format_sort_th {
Petr Baudis6b28da62008-09-25 18:48:37 +02005659 my ($name, $order, $header) = @_;
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01005660 my $sort_th = "";
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02005661 $header ||= ucfirst($name);
5662
5663 if ($order eq $name) {
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01005664 $sort_th .= "<th>$header</th>\n";
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02005665 } else {
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01005666 $sort_th .= "<th>" .
5667 $cgi->a({-href => href(-replay=>1, order=>$name),
5668 -class => "header"}, $header) .
5669 "</th>\n";
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02005670 }
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01005671
5672 return $sort_th;
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02005673}
5674
Sebastien Cevey0fa920c2011-04-29 19:51:59 +02005675sub git_project_list_rows {
5676 my ($projlist, $from, $to, $check_forks) = @_;
5677
5678 $from = 0 unless defined $from;
5679 $to = $#$projlist if (!defined $to || $#$projlist < $to);
5680
5681 my $alternate = 1;
5682 for (my $i = $from; $i <= $to; $i++) {
5683 my $pr = $projlist->[$i];
5684
5685 if ($alternate) {
5686 print "<tr class=\"dark\">\n";
5687 } else {
5688 print "<tr class=\"light\">\n";
5689 }
5690 $alternate ^= 1;
5691
5692 if ($check_forks) {
5693 print "<td>";
5694 if ($pr->{'forks'}) {
5695 my $nforks = scalar @{$pr->{'forks'}};
5696 if ($nforks > 0) {
5697 print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks"),
5698 -title => "$nforks forks"}, "+");
5699 } else {
5700 print $cgi->span({-title => "$nforks forks"}, "+");
5701 }
5702 }
5703 print "</td>\n";
5704 }
5705 print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
Jakub Narebski07a40062012-02-27 02:55:20 +01005706 -class => "list"},
5707 esc_html_match_hl($pr->{'path'}, $search_regexp)) .
5708 "</td>\n" .
Sebastien Cevey0fa920c2011-04-29 19:51:59 +02005709 "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
Jakub Narebski5fb3cf22012-02-27 02:55:21 +01005710 -class => "list",
Jakub Narebskie607b792012-02-27 02:55:22 +01005711 -title => $pr->{'descr_long'}},
Jakub Narebski5fb3cf22012-02-27 02:55:21 +01005712 $search_regexp
Jakub Narebskie607b792012-02-27 02:55:22 +01005713 ? esc_html_match_hl_chopped($pr->{'descr_long'},
5714 $pr->{'descr'}, $search_regexp)
Jakub Narebski5fb3cf22012-02-27 02:55:21 +01005715 : esc_html($pr->{'descr'})) .
Kacper Kornet0ebe7822012-04-26 18:45:44 +02005716 "</td>\n";
5717 unless ($omit_owner) {
5718 print "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
5719 }
Kacper Kornet5710be42012-04-24 19:39:15 +02005720 unless ($omit_age_column) {
5721 print "<td class=\"". age_class($pr->{'age'}) . "\">" .
5722 (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n";
5723 }
5724 print"<td class=\"link\">" .
Sebastien Cevey0fa920c2011-04-29 19:51:59 +02005725 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " .
5726 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
5727 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
5728 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
5729 ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') .
5730 "</td>\n" .
5731 "</tr>\n";
5732 }
5733}
5734
Jakub Narebski69913412008-06-10 19:21:01 +02005735sub git_project_list_body {
Petr Baudis42326112008-10-02 17:17:01 +02005736 # actually uses global variable $project
Jakub Narebski69913412008-06-10 19:21:01 +02005737 my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
Jakub Narebski12b14432011-04-29 19:51:56 +02005738 my @projects = @$projlist;
Jakub Narebski69913412008-06-10 19:21:01 +02005739
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08005740 my $check_forks = gitweb_check_feature('forks');
Jakub Narebski12b14432011-04-29 19:51:56 +02005741 my $show_ctags = gitweb_check_feature('ctags');
Jakub Narebski84d9e2d2012-02-03 13:44:54 +01005742 my $tagfilter = $show_ctags ? $input_params{'ctag'} : undef;
Jakub Narebski12b14432011-04-29 19:51:56 +02005743 $check_forks = undef
Jakub Narebskie65ceb62012-03-02 23:34:24 +01005744 if ($tagfilter || $search_regexp);
Jakub Narebski12b14432011-04-29 19:51:56 +02005745
5746 # filtering out forks before filling info allows to do less work
5747 @projects = filter_forks_from_projects_list(\@projects)
5748 if ($check_forks);
Jakub Narebski07b257f2012-02-23 16:54:48 +01005749 # search_projects_list pre-fills required info
Jakub Narebski12b14432011-04-29 19:51:56 +02005750 @projects = search_projects_list(\@projects,
Jakub Narebskie65ceb62012-03-02 23:34:24 +01005751 'search_regexp' => $search_regexp,
Jakub Narebski12b14432011-04-29 19:51:56 +02005752 'tagfilter' => $tagfilter)
Jakub Narebskie65ceb62012-03-02 23:34:24 +01005753 if ($tagfilter || $search_regexp);
Jakub Narebski07b257f2012-02-23 16:54:48 +01005754 # fill the rest
Kacper Kornet0ebe7822012-04-26 18:45:44 +02005755 my @all_fields = ('descr', 'descr_long', 'ctags', 'category');
5756 push @all_fields, ('age', 'age_string') unless($omit_age_column);
5757 push @all_fields, 'owner' unless($omit_owner);
Kacper Kornet5710be42012-04-24 19:39:15 +02005758 @projects = fill_project_list_info(\@projects, @all_fields);
Jakub Narebski69913412008-06-10 19:21:01 +02005759
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02005760 $order ||= $default_projects_order;
Petr Baudise30496d2006-10-24 05:33:17 +02005761 $from = 0 unless defined $from;
5762 $to = $#projects if (!defined $to || $#projects < $to);
5763
Jakub Narebski12b14432011-04-29 19:51:56 +02005764 # short circuit
5765 if ($from > $to) {
5766 print "<center>\n".
5767 "<b>No such projects found</b><br />\n".
5768 "Click ".$cgi->a({-href=>href(project=>undef)},"here")." to view all projects<br />\n".
5769 "</center>\n<br />\n";
5770 return;
Petr Baudis6b28da62008-09-25 18:48:37 +02005771 }
5772
Jakub Narebski12b14432011-04-29 19:51:56 +02005773 @projects = sort_projects_list(\@projects, $order);
5774
Petr Baudisaed93de2008-10-02 17:13:02 +02005775 if ($show_ctags) {
Jakub Narebski0368c492011-04-29 19:51:57 +02005776 my $ctags = git_gather_all_ctags(\@projects);
5777 my $cloud = git_populate_project_tagcloud($ctags);
Petr Baudisaed93de2008-10-02 17:13:02 +02005778 print git_show_project_tagcloud($cloud, 64);
5779 }
5780
Petr Baudise30496d2006-10-24 05:33:17 +02005781 print "<table class=\"project_list\">\n";
5782 unless ($no_header) {
5783 print "<tr>\n";
5784 if ($check_forks) {
5785 print "<th></th>\n";
5786 }
Petr Baudis6b28da62008-09-25 18:48:37 +02005787 print_sort_th('project', $order, 'Project');
5788 print_sort_th('descr', $order, 'Description');
Kacper Kornet0ebe7822012-04-26 18:45:44 +02005789 print_sort_th('owner', $order, 'Owner') unless $omit_owner;
Kacper Kornet5710be42012-04-24 19:39:15 +02005790 print_sort_th('age', $order, 'Last Change') unless $omit_age_column;
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02005791 print "<th></th>\n" . # for links
Petr Baudise30496d2006-10-24 05:33:17 +02005792 "</tr>\n";
5793 }
Petr Baudis42326112008-10-02 17:17:01 +02005794
Sebastien Ceveyd940c902011-04-29 19:52:01 +02005795 if ($projects_list_group_categories) {
5796 # only display categories with projects in the $from-$to window
5797 @projects = sort {$a->{'category'} cmp $b->{'category'}} @projects[$from..$to];
5798 my %categories = build_projlist_by_category(\@projects, $from, $to);
5799 foreach my $cat (sort keys %categories) {
5800 unless ($cat eq "") {
5801 print "<tr>\n";
5802 if ($check_forks) {
5803 print "<td></td>\n";
Jakub Narebski12b14432011-04-29 19:51:56 +02005804 }
Sebastien Ceveyd940c902011-04-29 19:52:01 +02005805 print "<td class=\"category\" colspan=\"5\">".esc_html($cat)."</td>\n";
5806 print "</tr>\n";
Petr Baudise30496d2006-10-24 05:33:17 +02005807 }
Sebastien Ceveyd940c902011-04-29 19:52:01 +02005808
5809 git_project_list_rows($categories{$cat}, undef, undef, $check_forks);
Petr Baudise30496d2006-10-24 05:33:17 +02005810 }
Sebastien Ceveyd940c902011-04-29 19:52:01 +02005811 } else {
5812 git_project_list_rows(\@projects, $from, $to, $check_forks);
Petr Baudise30496d2006-10-24 05:33:17 +02005813 }
Jakub Narebski12b14432011-04-29 19:51:56 +02005814
Petr Baudise30496d2006-10-24 05:33:17 +02005815 if (defined $extra) {
5816 print "<tr>\n";
5817 if ($check_forks) {
5818 print "<td></td>\n";
5819 }
5820 print "<td colspan=\"5\">$extra</td>\n" .
5821 "</tr>\n";
5822 }
5823 print "</table>\n";
5824}
5825
Jakub Narebski42671ca2009-11-13 02:02:12 +01005826sub git_log_body {
5827 # uses global variable $project
5828 my ($commitlist, $from, $to, $refs, $extra) = @_;
5829
5830 $from = 0 unless defined $from;
5831 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
5832
5833 for (my $i = 0; $i <= $to; $i++) {
5834 my %co = %{$commitlist->[$i]};
5835 next if !%co;
5836 my $commit = $co{'id'};
5837 my $ref = format_ref_marker($refs, $commit);
Jakub Narebski42671ca2009-11-13 02:02:12 +01005838 git_print_header_div('commit',
5839 "<span class=\"age\">$co{'age_string'}</span>" .
5840 esc_html($co{'title'}) . $ref,
5841 $commit);
5842 print "<div class=\"title_text\">\n" .
5843 "<div class=\"log_link\">\n" .
5844 $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") .
5845 " | " .
5846 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") .
5847 " | " .
5848 $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
5849 "<br/>\n" .
5850 "</div>\n";
5851 git_print_authorship(\%co, -tag => 'span');
5852 print "<br/>\n</div>\n";
5853
5854 print "<div class=\"log_body\">\n";
5855 git_print_log($co{'comment'}, -final_empty_line=> 1);
5856 print "</div>\n";
5857 }
5858 if ($extra) {
5859 print "<div class=\"page_nav\">\n";
5860 print "$extra\n";
5861 print "</div>\n";
5862 }
5863}
5864
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005865sub git_shortlog_body {
5866 # uses global variable $project
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00005867 my ($commitlist, $from, $to, $refs, $extra) = @_;
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05305868
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005869 $from = 0 unless defined $from;
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00005870 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005871
Jakub Narebski591ebf62007-11-19 14:16:11 +01005872 print "<table class=\"shortlog\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07005873 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005874 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00005875 my %co = %{$commitlist->[$i]};
5876 my $commit = $co{'id'};
Jakub Narebski847e01f2006-08-14 02:05:47 +02005877 my $ref = format_ref_marker($refs, $commit);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005878 if ($alternate) {
5879 print "<tr class=\"dark\">\n";
5880 } else {
5881 print "<tr class=\"light\">\n";
5882 }
5883 $alternate ^= 1;
5884 # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
5885 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02005886 format_author_html('td', \%co, 10) . "<td>";
Jakub Narebski952c65f2006-08-22 16:52:50 +02005887 print format_subject_html($co{'title'}, $co{'title_short'},
5888 href(action=>"commit", hash=>$commit), $ref);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005889 print "</td>\n" .
5890 "<td class=\"link\">" .
Petr Baudis4777b012006-10-24 05:36:10 +02005891 $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
Petr Baudis35749ae2006-09-22 03:19:44 +02005892 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
Petr Baudis55ff35c2006-10-06 15:57:52 +02005893 $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
Matt McCutchena3c8ab32007-07-22 01:30:27 +02005894 my $snapshot_links = format_snapshot_links($commit);
5895 if (defined $snapshot_links) {
5896 print " | " . $snapshot_links;
Petr Baudis55ff35c2006-10-06 15:57:52 +02005897 }
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305898 print "</td>\n" .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005899 "</tr>\n";
5900 }
5901 if (defined $extra) {
5902 print "<tr>\n" .
5903 "<td colspan=\"4\">$extra</td>\n" .
5904 "</tr>\n";
5905 }
5906 print "</table>\n";
5907}
5908
Jakub Narebski581860e2006-08-14 02:09:08 +02005909sub git_history_body {
5910 # Warning: assumes constant type (blob or tree) during history
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005911 my ($commitlist, $from, $to, $refs, $extra,
5912 $file_name, $file_hash, $ftype) = @_;
Jakub Narebski8be68352006-09-11 00:36:04 +02005913
5914 $from = 0 unless defined $from;
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00005915 $to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist});
Jakub Narebski581860e2006-08-14 02:09:08 +02005916
Jakub Narebski591ebf62007-11-19 14:16:11 +01005917 print "<table class=\"history\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07005918 my $alternate = 1;
Jakub Narebski8be68352006-09-11 00:36:04 +02005919 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00005920 my %co = %{$commitlist->[$i]};
Jakub Narebski581860e2006-08-14 02:09:08 +02005921 if (!%co) {
5922 next;
5923 }
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00005924 my $commit = $co{'id'};
Jakub Narebski581860e2006-08-14 02:09:08 +02005925
5926 my $ref = format_ref_marker($refs, $commit);
5927
5928 if ($alternate) {
5929 print "<tr class=\"dark\">\n";
5930 } else {
5931 print "<tr class=\"light\">\n";
5932 }
5933 $alternate ^= 1;
5934 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02005935 # shortlog: format_author_html('td', \%co, 10)
5936 format_author_html('td', \%co, 15, 3) . "<td>";
Jakub Narebski581860e2006-08-14 02:09:08 +02005937 # originally git_history used chop_str($co{'title'}, 50)
Jakub Narebski952c65f2006-08-22 16:52:50 +02005938 print format_subject_html($co{'title'}, $co{'title_short'},
5939 href(action=>"commit", hash=>$commit), $ref);
Jakub Narebski581860e2006-08-14 02:09:08 +02005940 print "</td>\n" .
5941 "<td class=\"link\">" .
Luben Tuikov6d81c5a2006-09-28 17:21:07 -07005942 $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype) . " | " .
5943 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
Jakub Narebski581860e2006-08-14 02:09:08 +02005944
5945 if ($ftype eq 'blob') {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005946 my $blob_current = $file_hash;
Jakub Narebski581860e2006-08-14 02:09:08 +02005947 my $blob_parent = git_get_hash_by_path($commit, $file_name);
5948 if (defined $blob_current && defined $blob_parent &&
5949 $blob_current ne $blob_parent) {
5950 print " | " .
Jakub Narebski420e92f2006-08-24 23:53:54 +02005951 $cgi->a({-href => href(action=>"blobdiff",
5952 hash=>$blob_current, hash_parent=>$blob_parent,
5953 hash_base=>$hash_base, hash_parent_base=>$commit,
5954 file_name=>$file_name)},
Jakub Narebski581860e2006-08-14 02:09:08 +02005955 "diff to current");
5956 }
5957 }
5958 print "</td>\n" .
5959 "</tr>\n";
5960 }
5961 if (defined $extra) {
5962 print "<tr>\n" .
5963 "<td colspan=\"4\">$extra</td>\n" .
5964 "</tr>\n";
5965 }
5966 print "</table>\n";
5967}
5968
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005969sub git_tags_body {
5970 # uses global variable $project
5971 my ($taglist, $from, $to, $extra) = @_;
5972 $from = 0 unless defined $from;
5973 $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
5974
Jakub Narebski591ebf62007-11-19 14:16:11 +01005975 print "<table class=\"tags\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07005976 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005977 for (my $i = $from; $i <= $to; $i++) {
5978 my $entry = $taglist->[$i];
5979 my %tag = %$entry;
Jakub Narebskicd146402006-11-02 20:23:11 +01005980 my $comment = $tag{'subject'};
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005981 my $comment_short;
5982 if (defined $comment) {
5983 $comment_short = chop_str($comment, 30, 5);
5984 }
5985 if ($alternate) {
5986 print "<tr class=\"dark\">\n";
5987 } else {
5988 print "<tr class=\"light\">\n";
5989 }
5990 $alternate ^= 1;
Jakub Narebski27dd1a82007-01-03 22:54:29 +01005991 if (defined $tag{'age'}) {
5992 print "<td><i>$tag{'age'}</i></td>\n";
5993 } else {
5994 print "<td></td>\n";
5995 }
5996 print "<td>" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02005997 $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'}),
Jakub Narebski63e42202006-08-22 12:38:59 +02005998 -class => "list name"}, esc_html($tag{'name'})) .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005999 "</td>\n" .
6000 "<td>";
6001 if (defined $comment) {
Jakub Narebski952c65f2006-08-22 16:52:50 +02006002 print format_subject_html($comment, $comment_short,
6003 href(action=>"tag", hash=>$tag{'id'}));
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006004 }
6005 print "</td>\n" .
6006 "<td class=\"selflink\">";
6007 if ($tag{'type'} eq "tag") {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02006008 print $cgi->a({-href => href(action=>"tag", hash=>$tag{'id'})}, "tag");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006009 } else {
6010 print "&nbsp;";
6011 }
6012 print "</td>\n" .
6013 "<td class=\"link\">" . " | " .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02006014 $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'});
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006015 if ($tag{'reftype'} eq "commit") {
Jakub Narebskibf901f82007-12-15 15:40:28 +01006016 print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'fullname'})}, "shortlog") .
6017 " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'fullname'})}, "log");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006018 } elsif ($tag{'reftype'} eq "blob") {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02006019 print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006020 }
6021 print "</td>\n" .
6022 "</tr>";
6023 }
6024 if (defined $extra) {
6025 print "<tr>\n" .
6026 "<td colspan=\"5\">$extra</td>\n" .
6027 "</tr>\n";
6028 }
6029 print "</table>\n";
6030}
6031
6032sub git_heads_body {
6033 # uses global variable $project
Jakub Narebskifd49e562012-02-15 16:36:41 +01006034 my ($headlist, $head_at, $from, $to, $extra) = @_;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006035 $from = 0 unless defined $from;
Jakub Narebski120ddde2006-09-19 14:33:22 +02006036 $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006037
Jakub Narebski591ebf62007-11-19 14:16:11 +01006038 print "<table class=\"heads\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07006039 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006040 for (my $i = $from; $i <= $to; $i++) {
Jakub Narebski120ddde2006-09-19 14:33:22 +02006041 my $entry = $headlist->[$i];
Jakub Narebskicd146402006-11-02 20:23:11 +01006042 my %ref = %$entry;
Jakub Narebskifd49e562012-02-15 16:36:41 +01006043 my $curr = defined $head_at && $ref{'id'} eq $head_at;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006044 if ($alternate) {
6045 print "<tr class=\"dark\">\n";
6046 } else {
6047 print "<tr class=\"light\">\n";
6048 }
6049 $alternate ^= 1;
Jakub Narebskicd146402006-11-02 20:23:11 +01006050 print "<td><i>$ref{'age'}</i></td>\n" .
6051 ($curr ? "<td class=\"current_head\">" : "<td>") .
Jakub Narebskibf901f82007-12-15 15:40:28 +01006052 $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'}),
Jakub Narebskicd146402006-11-02 20:23:11 +01006053 -class => "list name"},esc_html($ref{'name'})) .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006054 "</td>\n" .
6055 "<td class=\"link\">" .
Jakub Narebskibf901f82007-12-15 15:40:28 +01006056 $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'})}, "shortlog") . " | " .
6057 $cgi->a({-href => href(action=>"log", hash=>$ref{'fullname'})}, "log") . " | " .
Giuseppe Bilotta9e70e152010-11-11 13:26:08 +01006058 $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'fullname'})}, "tree") .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006059 "</td>\n" .
6060 "</tr>";
6061 }
6062 if (defined $extra) {
6063 print "<tr>\n" .
6064 "<td colspan=\"3\">$extra</td>\n" .
6065 "</tr>\n";
6066 }
6067 print "</table>\n";
6068}
6069
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006070# Display a single remote block
6071sub git_remote_block {
6072 my ($remote, $rdata, $limit, $head) = @_;
6073
6074 my $heads = $rdata->{'heads'};
6075 my $fetch = $rdata->{'fetch'};
6076 my $push = $rdata->{'push'};
6077
6078 my $urls_table = "<table class=\"projects_list\">\n" ;
6079
6080 if (defined $fetch) {
6081 if ($fetch eq $push) {
6082 $urls_table .= format_repo_url("URL", $fetch);
6083 } else {
6084 $urls_table .= format_repo_url("Fetch URL", $fetch);
6085 $urls_table .= format_repo_url("Push URL", $push) if defined $push;
6086 }
6087 } elsif (defined $push) {
6088 $urls_table .= format_repo_url("Push URL", $push);
6089 } else {
6090 $urls_table .= format_repo_url("", "No remote URL");
6091 }
6092
6093 $urls_table .= "</table>\n";
6094
6095 my $dots;
6096 if (defined $limit && $limit < @$heads) {
6097 $dots = $cgi->a({-href => href(action=>"remotes", hash=>$remote)}, "...");
6098 }
6099
6100 print $urls_table;
6101 git_heads_body($heads, $head, 0, $limit, $dots);
6102}
6103
6104# Display a list of remote names with the respective fetch and push URLs
6105sub git_remotes_list {
6106 my ($remotedata, $limit) = @_;
6107 print "<table class=\"heads\">\n";
6108 my $alternate = 1;
6109 my @remotes = sort keys %$remotedata;
6110
6111 my $limited = $limit && $limit < @remotes;
6112
6113 $#remotes = $limit - 1 if $limited;
6114
6115 while (my $remote = shift @remotes) {
6116 my $rdata = $remotedata->{$remote};
6117 my $fetch = $rdata->{'fetch'};
6118 my $push = $rdata->{'push'};
6119 if ($alternate) {
6120 print "<tr class=\"dark\">\n";
6121 } else {
6122 print "<tr class=\"light\">\n";
6123 }
6124 $alternate ^= 1;
6125 print "<td>" .
6126 $cgi->a({-href=> href(action=>'remotes', hash=>$remote),
6127 -class=> "list name"},esc_html($remote)) .
6128 "</td>";
6129 print "<td class=\"link\">" .
6130 (defined $fetch ? $cgi->a({-href=> $fetch}, "fetch") : "fetch") .
6131 " | " .
6132 (defined $push ? $cgi->a({-href=> $push}, "push") : "push") .
6133 "</td>";
6134
6135 print "</tr>\n";
6136 }
6137
6138 if ($limited) {
6139 print "<tr>\n" .
6140 "<td colspan=\"3\">" .
6141 $cgi->a({-href => href(action=>"remotes")}, "...") .
6142 "</td>\n" . "</tr>\n";
6143 }
6144
6145 print "</table>";
6146}
6147
6148# Display remote heads grouped by remote, unless there are too many
6149# remotes, in which case we only display the remote names
6150sub git_remotes_body {
6151 my ($remotedata, $limit, $head) = @_;
6152 if ($limit and $limit < keys %$remotedata) {
6153 git_remotes_list($remotedata, $limit);
6154 } else {
6155 fill_remote_heads($remotedata);
6156 while (my ($remote, $rdata) = each %$remotedata) {
6157 git_print_section({-class=>"remote", -id=>$remote},
6158 ["remotes", $remote, $remote], sub {
6159 git_remote_block($remote, $rdata, $limit, $head);
6160 });
6161 }
6162 }
6163}
6164
Jakub Narebski16f20722011-06-22 17:28:53 +02006165sub git_search_message {
6166 my %co = @_;
6167
6168 my $greptype;
6169 if ($searchtype eq 'commit') {
6170 $greptype = "--grep=";
6171 } elsif ($searchtype eq 'author') {
6172 $greptype = "--author=";
6173 } elsif ($searchtype eq 'committer') {
6174 $greptype = "--committer=";
6175 }
6176 $greptype .= $searchtext;
6177 my @commitlist = parse_commits($hash, 101, (100 * $page), undef,
6178 $greptype, '--regexp-ignore-case',
6179 $search_use_regexp ? '--extended-regexp' : '--fixed-strings');
6180
6181 my $paging_nav = '';
6182 if ($page > 0) {
6183 $paging_nav .=
Jakub Narebski882541b2011-06-22 17:28:54 +02006184 $cgi->a({-href => href(-replay=>1, page=>undef)},
6185 "first") .
6186 " &sdot; " .
Jakub Narebski16f20722011-06-22 17:28:53 +02006187 $cgi->a({-href => href(-replay=>1, page=>$page-1),
6188 -accesskey => "p", -title => "Alt-p"}, "prev");
6189 } else {
Jakub Narebski882541b2011-06-22 17:28:54 +02006190 $paging_nav .= "first &sdot; prev";
Jakub Narebski16f20722011-06-22 17:28:53 +02006191 }
6192 my $next_link = '';
6193 if ($#commitlist >= 100) {
6194 $next_link =
6195 $cgi->a({-href => href(-replay=>1, page=>$page+1),
6196 -accesskey => "n", -title => "Alt-n"}, "next");
6197 $paging_nav .= " &sdot; $next_link";
6198 } else {
6199 $paging_nav .= " &sdot; next";
6200 }
6201
Jakub Narebski1ae05be2011-06-22 17:28:55 +02006202 git_header_html();
6203
Jakub Narebski16f20722011-06-22 17:28:53 +02006204 git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
6205 git_print_header_div('commit', esc_html($co{'title'}), $hash);
6206 if ($page == 0 && !@commitlist) {
6207 print "<p>No match.</p>\n";
6208 } else {
6209 git_search_grep_body(\@commitlist, 0, 99, $next_link);
6210 }
Jakub Narebski1ae05be2011-06-22 17:28:55 +02006211
6212 git_footer_html();
Jakub Narebski16f20722011-06-22 17:28:53 +02006213}
6214
6215sub git_search_changes {
6216 my %co = @_;
6217
Jakub Narebski1ae05be2011-06-22 17:28:55 +02006218 local $/ = "\n";
6219 open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts,
6220 '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext",
6221 ($search_use_regexp ? '--pickaxe-regex' : ())
6222 or die_error(500, "Open git-log failed");
6223
6224 git_header_html();
6225
Jakub Narebski16f20722011-06-22 17:28:53 +02006226 git_print_page_nav('','', $hash,$co{'tree'},$hash);
6227 git_print_header_div('commit', esc_html($co{'title'}), $hash);
6228
6229 print "<table class=\"pickaxe search\">\n";
6230 my $alternate = 1;
Jakub Narebski16f20722011-06-22 17:28:53 +02006231 undef %co;
6232 my @files;
6233 while (my $line = <$fd>) {
6234 chomp $line;
6235 next unless $line;
6236
6237 my %set = parse_difftree_raw_line($line);
6238 if (defined $set{'commit'}) {
6239 # finish previous commit
6240 if (%co) {
6241 print "</td>\n" .
6242 "<td class=\"link\">" .
Jakub Narebski882541b2011-06-22 17:28:54 +02006243 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})},
6244 "commit") .
Jakub Narebski16f20722011-06-22 17:28:53 +02006245 " | " .
Jakub Narebski882541b2011-06-22 17:28:54 +02006246 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'},
6247 hash_base=>$co{'id'})},
6248 "tree") .
6249 "</td>\n" .
Jakub Narebski16f20722011-06-22 17:28:53 +02006250 "</tr>\n";
6251 }
6252
6253 if ($alternate) {
6254 print "<tr class=\"dark\">\n";
6255 } else {
6256 print "<tr class=\"light\">\n";
6257 }
6258 $alternate ^= 1;
6259 %co = parse_commit($set{'commit'});
6260 my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
6261 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
6262 "<td><i>$author</i></td>\n" .
6263 "<td>" .
6264 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
6265 -class => "list subject"},
6266 chop_and_escape_str($co{'title'}, 50) . "<br/>");
6267 } elsif (defined $set{'to_id'}) {
6268 next if ($set{'to_id'} =~ m/^0{40}$/);
6269
6270 print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
6271 hash=>$set{'to_id'}, file_name=>$set{'to_file'}),
6272 -class => "list"},
6273 "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
6274 "<br/>\n";
6275 }
6276 }
6277 close $fd;
6278
6279 # finish last commit (warning: repetition!)
6280 if (%co) {
6281 print "</td>\n" .
6282 "<td class=\"link\">" .
Jakub Narebski882541b2011-06-22 17:28:54 +02006283 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})},
6284 "commit") .
Jakub Narebski16f20722011-06-22 17:28:53 +02006285 " | " .
Jakub Narebski882541b2011-06-22 17:28:54 +02006286 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'},
6287 hash_base=>$co{'id'})},
6288 "tree") .
6289 "</td>\n" .
Jakub Narebski16f20722011-06-22 17:28:53 +02006290 "</tr>\n";
6291 }
6292
6293 print "</table>\n";
Jakub Narebski1ae05be2011-06-22 17:28:55 +02006294
6295 git_footer_html();
Jakub Narebski16f20722011-06-22 17:28:53 +02006296}
6297
6298sub git_search_files {
6299 my %co = @_;
6300
Jakub Narebski1ae05be2011-06-22 17:28:55 +02006301 local $/ = "\n";
Jakub Narebski8e09fd12012-01-05 21:32:56 +01006302 open my $fd, "-|", git_cmd(), 'grep', '-n', '-z',
Jakub Narebski1ae05be2011-06-22 17:28:55 +02006303 $search_use_regexp ? ('-E', '-i') : '-F',
6304 $searchtext, $co{'tree'}
6305 or die_error(500, "Open git-grep failed");
6306
6307 git_header_html();
6308
Jakub Narebski16f20722011-06-22 17:28:53 +02006309 git_print_page_nav('','', $hash,$co{'tree'},$hash);
6310 git_print_header_div('commit', esc_html($co{'title'}), $hash);
6311
6312 print "<table class=\"grep_search\">\n";
6313 my $alternate = 1;
6314 my $matches = 0;
Jakub Narebski16f20722011-06-22 17:28:53 +02006315 my $lastfile = '';
Jakub Narebskifc8fcd22012-02-15 17:37:06 +01006316 my $file_href;
Jakub Narebski16f20722011-06-22 17:28:53 +02006317 while (my $line = <$fd>) {
6318 chomp $line;
Jakub Narebskifc8fcd22012-02-15 17:37:06 +01006319 my ($file, $lno, $ltext, $binary);
Jakub Narebski16f20722011-06-22 17:28:53 +02006320 last if ($matches++ > 1000);
6321 if ($line =~ /^Binary file (.+) matches$/) {
6322 $file = $1;
6323 $binary = 1;
6324 } else {
Jakub Narebski8e09fd12012-01-05 21:32:56 +01006325 ($file, $lno, $ltext) = split(/\0/, $line, 3);
6326 $file =~ s/^$co{'tree'}://;
Jakub Narebski16f20722011-06-22 17:28:53 +02006327 }
6328 if ($file ne $lastfile) {
6329 $lastfile and print "</td></tr>\n";
6330 if ($alternate++) {
6331 print "<tr class=\"dark\">\n";
6332 } else {
6333 print "<tr class=\"light\">\n";
6334 }
Jakub Narebskiff7f2182012-01-05 21:26:48 +01006335 $file_href = href(action=>"blob", hash_base=>$co{'id'},
6336 file_name=>$file);
Jakub Narebski16f20722011-06-22 17:28:53 +02006337 print "<td class=\"list\">".
Jakub Narebskiff7f2182012-01-05 21:26:48 +01006338 $cgi->a({-href => $file_href, -class => "list"}, esc_path($file));
Jakub Narebski16f20722011-06-22 17:28:53 +02006339 print "</td><td>\n";
6340 $lastfile = $file;
6341 }
6342 if ($binary) {
6343 print "<div class=\"binary\">Binary file</div>\n";
6344 } else {
6345 $ltext = untabify($ltext);
6346 if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) {
6347 $ltext = esc_html($1, -nbsp=>1);
6348 $ltext .= '<span class="match">';
6349 $ltext .= esc_html($2, -nbsp=>1);
6350 $ltext .= '</span>';
6351 $ltext .= esc_html($3, -nbsp=>1);
6352 } else {
6353 $ltext = esc_html($ltext, -nbsp=>1);
6354 }
6355 print "<div class=\"pre\">" .
Jakub Narebskiff7f2182012-01-05 21:26:48 +01006356 $cgi->a({-href => $file_href.'#l'.$lno,
6357 -class => "linenr"}, sprintf('%4i', $lno)) .
6358 ' ' . $ltext . "</div>\n";
Jakub Narebski16f20722011-06-22 17:28:53 +02006359 }
6360 }
6361 if ($lastfile) {
6362 print "</td></tr>\n";
6363 if ($matches > 1000) {
6364 print "<div class=\"diff nodifferences\">Too many matches, listing trimmed</div>\n";
6365 }
6366 } else {
6367 print "<div class=\"diff nodifferences\">No matches found</div>\n";
6368 }
6369 close $fd;
6370
6371 print "</table>\n";
Jakub Narebski1ae05be2011-06-22 17:28:55 +02006372
6373 git_footer_html();
Jakub Narebski16f20722011-06-22 17:28:53 +02006374}
6375
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006376sub git_search_grep_body {
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00006377 my ($commitlist, $from, $to, $extra) = @_;
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006378 $from = 0 unless defined $from;
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00006379 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006380
Jakub Narebski591ebf62007-11-19 14:16:11 +01006381 print "<table class=\"commit_search\">\n";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006382 my $alternate = 1;
6383 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00006384 my %co = %{$commitlist->[$i]};
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006385 if (!%co) {
6386 next;
6387 }
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00006388 my $commit = $co{'id'};
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006389 if ($alternate) {
6390 print "<tr class=\"dark\">\n";
6391 } else {
6392 print "<tr class=\"light\">\n";
6393 }
6394 $alternate ^= 1;
6395 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02006396 format_author_html('td', \%co, 15, 5) .
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006397 "<td>" .
Junio C Hamanobe8b9062008-02-22 17:33:47 +01006398 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
6399 -class => "list subject"},
6400 chop_and_escape_str($co{'title'}, 50) . "<br/>");
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006401 my $comment = $co{'comment'};
6402 foreach my $line (@$comment) {
Jakub Narebski6dfbb302008-03-02 16:57:14 +01006403 if ($line =~ m/^(.*?)($search_regexp)(.*)$/i) {
Junio C Hamanobe8b9062008-02-22 17:33:47 +01006404 my ($lead, $match, $trail) = ($1, $2, $3);
Jakub Narebskib8d97d02008-02-25 21:07:57 +01006405 $match = chop_str($match, 70, 5, 'center');
6406 my $contextlen = int((80 - length($match))/2);
6407 $contextlen = 30 if ($contextlen > 30);
6408 $lead = chop_str($lead, $contextlen, 10, 'left');
6409 $trail = chop_str($trail, $contextlen, 10, 'right');
Junio C Hamanobe8b9062008-02-22 17:33:47 +01006410
6411 $lead = esc_html($lead);
6412 $match = esc_html($match);
6413 $trail = esc_html($trail);
6414
6415 print "$lead<span class=\"match\">$match</span>$trail<br />";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006416 }
6417 }
6418 print "</td>\n" .
6419 "<td class=\"link\">" .
6420 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
6421 " | " .
Denis Chengf1fe8f52007-11-26 20:42:06 +08006422 $cgi->a({-href => href(action=>"commitdiff", hash=>$co{'id'})}, "commitdiff") .
6423 " | " .
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006424 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
6425 print "</td>\n" .
6426 "</tr>\n";
6427 }
6428 if (defined $extra) {
6429 print "<tr>\n" .
6430 "<td colspan=\"3\">$extra</td>\n" .
6431 "</tr>\n";
6432 }
6433 print "</table>\n";
6434}
6435
Jakub Narebski717b8312006-07-31 21:22:15 +02006436## ======================================================================
6437## ======================================================================
6438## actions
6439
Jakub Narebski717b8312006-07-31 21:22:15 +02006440sub git_project_list {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02006441 my $order = $input_params{'order'};
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02006442 if (defined $order && $order !~ m/none|project|descr|owner|age/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006443 die_error(400, "Unknown order parameter");
Jakub Narebski6326b602006-08-01 02:59:12 +02006444 }
6445
Bernhard R. Link19d2d232012-01-30 21:07:37 +01006446 my @list = git_get_projects_list($project_filter, $strict_export);
Jakub Narebski717b8312006-07-31 21:22:15 +02006447 if (!@list) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006448 die_error(404, "No projects found");
Jakub Narebski717b8312006-07-31 21:22:15 +02006449 }
Jakub Narebski6326b602006-08-01 02:59:12 +02006450
Jakub Narebski717b8312006-07-31 21:22:15 +02006451 git_header_html();
John 'Warthog9' Hawley24d4afc2010-01-30 23:30:41 +01006452 if (defined $home_text && -f $home_text) {
Jakub Narebski717b8312006-07-31 21:22:15 +02006453 print "<div class=\"index_include\">\n";
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01006454 insert_file($home_text);
Jakub Narebski717b8312006-07-31 21:22:15 +02006455 print "</div>\n";
6456 }
Jakub Narebskia1e1b2d2012-01-31 01:20:54 +01006457
6458 git_project_search_form($searchtext, $search_use_regexp);
Petr Baudise30496d2006-10-24 05:33:17 +02006459 git_project_list_body(\@list, $order);
6460 git_footer_html();
6461}
6462
6463sub git_forks {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02006464 my $order = $input_params{'order'};
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02006465 if (defined $order && $order !~ m/none|project|descr|owner|age/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006466 die_error(400, "Unknown order parameter");
Jakub Narebski717b8312006-07-31 21:22:15 +02006467 }
Petr Baudise30496d2006-10-24 05:33:17 +02006468
Bernhard R. Link4c7cd172012-01-30 21:05:47 +01006469 my $filter = $project;
6470 $filter =~ s/\.git$//;
6471 my @list = git_get_projects_list($filter);
Petr Baudise30496d2006-10-24 05:33:17 +02006472 if (!@list) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006473 die_error(404, "No forks found");
Jakub Narebski717b8312006-07-31 21:22:15 +02006474 }
Petr Baudise30496d2006-10-24 05:33:17 +02006475
6476 git_header_html();
6477 git_print_page_nav('','');
6478 git_print_header_div('summary', "$project forks");
6479 git_project_list_body(\@list, $order);
Jakub Narebski717b8312006-07-31 21:22:15 +02006480 git_footer_html();
6481}
6482
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02006483sub git_project_index {
Bernhard R. Link19d2d232012-01-30 21:07:37 +01006484 my @projects = git_get_projects_list($project_filter, $strict_export);
Jakub Narebski12b14432011-04-29 19:51:56 +02006485 if (!@projects) {
6486 die_error(404, "No projects found");
6487 }
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02006488
6489 print $cgi->header(
6490 -type => 'text/plain',
6491 -charset => 'utf-8',
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02006492 -content_disposition => 'inline; filename="index.aux"');
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02006493
6494 foreach my $pr (@projects) {
6495 if (!exists $pr->{'owner'}) {
Miklos Vajna76e4f5d2007-07-04 00:11:23 +02006496 $pr->{'owner'} = git_get_project_owner("$pr->{'path'}");
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02006497 }
6498
6499 my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'});
6500 # quote as in CGI::Util::encode, but keep the slash, and use '+' for ' '
6501 $path =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
6502 $owner =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
6503 $path =~ s/ /\+/g;
6504 $owner =~ s/ /\+/g;
6505
6506 print "$path $owner\n";
6507 }
6508}
6509
Kay Sieversede5e102005-08-07 20:23:12 +02006510sub git_summary {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006511 my $descr = git_get_project_description($project) || "none";
Robert Fitzsimonsa979d122006-12-22 19:38:15 +00006512 my %co = parse_commit("HEAD");
Jakub Narebski785cdea2007-05-13 12:39:22 +02006513 my %cd = %co ? parse_date($co{'committer_epoch'}, $co{'committer_tz'}) : ();
Robert Fitzsimonsa979d122006-12-22 19:38:15 +00006514 my $head = $co{'id'};
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01006515 my $remote_heads = gitweb_check_feature('remote_heads');
Kay Sieversede5e102005-08-07 20:23:12 +02006516
Jakub Narebski1e0cf032006-08-14 02:10:06 +02006517 my $owner = git_get_project_owner($project);
Kay Sieversede5e102005-08-07 20:23:12 +02006518
Jakub Narebskicd146402006-11-02 20:23:11 +01006519 my $refs = git_get_references();
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00006520 # These get_*_list functions return one more to allow us to see if
6521 # there are more ...
6522 my @taglist = git_get_tags_list(16);
6523 my @headlist = git_get_heads_list(16);
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006524 my %remotedata = $remote_heads ? git_get_remotes_list() : ();
Petr Baudise30496d2006-10-24 05:33:17 +02006525 my @forklist;
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006526 my $check_forks = gitweb_check_feature('forks');
Junio C Hamano5dd5ed02006-11-07 22:00:45 -08006527
6528 if ($check_forks) {
Jakub Narebski12b14432011-04-29 19:51:56 +02006529 # find forks of a project
Bernhard R. Link4c7cd172012-01-30 21:05:47 +01006530 my $filter = $project;
6531 $filter =~ s/\.git$//;
6532 @forklist = git_get_projects_list($filter);
Jakub Narebski12b14432011-04-29 19:51:56 +02006533 # filter out forks of forks
6534 @forklist = filter_forks_from_projects_list(\@forklist)
6535 if (@forklist);
Petr Baudise30496d2006-10-24 05:33:17 +02006536 }
Jakub Narebski120ddde2006-09-19 14:33:22 +02006537
Kay Sieversede5e102005-08-07 20:23:12 +02006538 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02006539 git_print_page_nav('summary','', $head);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006540
Kay Sievers19806692005-08-07 20:26:27 +02006541 print "<div class=\"title\">&nbsp;</div>\n";
Jakub Narebski591ebf62007-11-19 14:16:11 +01006542 print "<table class=\"projects_list\">\n" .
Kacper Kornet0ebe7822012-04-26 18:45:44 +02006543 "<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n";
Tony Finch860ccc62013-08-20 17:59:44 +01006544 if ($owner and not $omit_owner) {
Kacper Kornet0ebe7822012-04-26 18:45:44 +02006545 print "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
6546 }
Jakub Narebski785cdea2007-05-13 12:39:22 +02006547 if (defined $cd{'rfc2822'}) {
Jakub Narebski256b7b42011-04-28 21:04:07 +02006548 print "<tr id=\"metadata_lchange\"><td>last change</td>" .
6549 "<td>".format_timestamp_html(\%cd)."</td></tr>\n";
Jakub Narebski785cdea2007-05-13 12:39:22 +02006550 }
6551
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02006552 # use per project git URL list in $projectroot/$project/cloneurl
6553 # or make project git URL from git base URL and project name
Jakub Narebski19a87212006-08-15 23:03:17 +02006554 my $url_tag = "URL";
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02006555 my @url_list = git_get_project_url_list($project);
6556 @url_list = map { "$_/$project" } @git_base_url_list unless @url_list;
6557 foreach my $git_url (@url_list) {
6558 next unless $git_url;
Giuseppe Bilotta0e656992010-11-11 13:26:15 +01006559 print format_repo_url($url_tag, $git_url);
Jakub Narebski19a87212006-08-15 23:03:17 +02006560 $url_tag = "";
6561 }
Petr Baudisaed93de2008-10-02 17:13:02 +02006562
6563 # Tag cloud
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006564 my $show_ctags = gitweb_check_feature('ctags');
Petr Baudisaed93de2008-10-02 17:13:02 +02006565 if ($show_ctags) {
6566 my $ctags = git_get_project_ctags($project);
Jakub Narebski0368c492011-04-29 19:51:57 +02006567 if (%$ctags) {
6568 # without ability to add tags, don't show if there are none
6569 my $cloud = git_populate_project_tagcloud($ctags);
6570 print "<tr id=\"metadata_ctags\">" .
6571 "<td>content tags</td>" .
6572 "<td>".git_show_project_tagcloud($cloud, 48)."</td>" .
6573 "</tr>\n";
6574 }
Petr Baudisaed93de2008-10-02 17:13:02 +02006575 }
6576
Jakub Narebski19a87212006-08-15 23:03:17 +02006577 print "</table>\n";
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006578
Matt McCutchen7e1100e2009-02-07 19:00:09 -05006579 # If XSS prevention is on, we don't include README.html.
6580 # TODO: Allow a readme in some safe format.
6581 if (!$prevent_xss && -s "$projectroot/$project/README.html") {
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01006582 print "<div class=\"title\">readme</div>\n" .
6583 "<div class=\"readme\">\n";
6584 insert_file("$projectroot/$project/README.html");
6585 print "\n</div>\n"; # class="readme"
Petr Baudis447ef092006-10-24 05:23:46 +02006586 }
6587
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00006588 # we need to request one more than 16 (0..15) to check if
6589 # those 16 are all
Jakub Narebski785cdea2007-05-13 12:39:22 +02006590 my @commitlist = $head ? parse_commits($head, 17) : ();
6591 if (@commitlist) {
6592 git_print_header_div('shortlog');
6593 git_shortlog_body(\@commitlist, 0, 15, $refs,
6594 $#commitlist <= 15 ? undef :
6595 $cgi->a({-href => href(action=>"shortlog")}, "..."));
6596 }
Kay Sieversede5e102005-08-07 20:23:12 +02006597
Jakub Narebski120ddde2006-09-19 14:33:22 +02006598 if (@taglist) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006599 git_print_header_div('tags');
Jakub Narebski120ddde2006-09-19 14:33:22 +02006600 git_tags_body(\@taglist, 0, 15,
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00006601 $#taglist <= 15 ? undef :
Martin Waitz1c2a4f52006-08-16 00:24:30 +02006602 $cgi->a({-href => href(action=>"tags")}, "..."));
Kay Sieversede5e102005-08-07 20:23:12 +02006603 }
Kay Sievers0db37972005-08-07 20:24:35 +02006604
Jakub Narebski120ddde2006-09-19 14:33:22 +02006605 if (@headlist) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006606 git_print_header_div('heads');
Jakub Narebski120ddde2006-09-19 14:33:22 +02006607 git_heads_body(\@headlist, $head, 0, 15,
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00006608 $#headlist <= 15 ? undef :
Martin Waitz1c2a4f52006-08-16 00:24:30 +02006609 $cgi->a({-href => href(action=>"heads")}, "..."));
Kay Sievers0db37972005-08-07 20:24:35 +02006610 }
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006611
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006612 if (%remotedata) {
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01006613 git_print_header_div('remotes');
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006614 git_remotes_body(\%remotedata, 15, $head);
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01006615 }
6616
Petr Baudise30496d2006-10-24 05:33:17 +02006617 if (@forklist) {
6618 git_print_header_div('forks');
Mike Ralphsonf04f27e2008-09-25 18:48:48 +02006619 git_project_list_body(\@forklist, 'age', 0, 15,
Robert Fitzsimonsaaca9672006-12-22 19:38:12 +00006620 $#forklist <= 15 ? undef :
Petr Baudise30496d2006-10-24 05:33:17 +02006621 $cgi->a({-href => href(action=>"forks")}, "..."),
Mike Ralphsonf04f27e2008-09-25 18:48:48 +02006622 'no_header');
Petr Baudise30496d2006-10-24 05:33:17 +02006623 }
6624
Kay Sieversede5e102005-08-07 20:23:12 +02006625 git_footer_html();
6626}
6627
Kay Sieversd8a20ba2005-08-07 20:28:53 +02006628sub git_tag {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006629 my %tag = parse_tag($hash);
Jakub Narebski198a2a82007-05-12 21:16:34 +02006630
6631 if (! %tag) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006632 die_error(404, "Unknown tag object");
Jakub Narebski198a2a82007-05-12 21:16:34 +02006633 }
6634
Anders Kaseorgd8a94802010-08-27 13:38:16 -04006635 my $head = git_get_head_hash($project);
6636 git_header_html();
6637 git_print_page_nav('','', $head,undef,$head);
Jakub Narebski847e01f2006-08-14 02:05:47 +02006638 git_print_header_div('commit', esc_html($tag{'name'}), $hash);
Kay Sieversd8a20ba2005-08-07 20:28:53 +02006639 print "<div class=\"title_text\">\n" .
Jakub Narebski591ebf62007-11-19 14:16:11 +01006640 "<table class=\"object_header\">\n" .
Kay Sieverse4669df2005-08-08 00:02:39 +02006641 "<tr>\n" .
6642 "<td>object</td>\n" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02006643 "<td>" . $cgi->a({-class => "list", -href => href(action=>$tag{'type'}, hash=>$tag{'object'})},
6644 $tag{'object'}) . "</td>\n" .
6645 "<td class=\"link\">" . $cgi->a({-href => href(action=>$tag{'type'}, hash=>$tag{'object'})},
6646 $tag{'type'}) . "</td>\n" .
Kay Sieverse4669df2005-08-08 00:02:39 +02006647 "</tr>\n";
Kay Sieversd8a20ba2005-08-07 20:28:53 +02006648 if (defined($tag{'author'})) {
Giuseppe Bilottaba924732009-06-30 00:00:50 +02006649 git_print_authorship_rows(\%tag, 'author');
Kay Sieversd8a20ba2005-08-07 20:28:53 +02006650 }
6651 print "</table>\n\n" .
6652 "</div>\n";
6653 print "<div class=\"page_body\">";
6654 my $comment = $tag{'comment'};
6655 foreach my $line (@$comment) {
Junio C Hamano70022432006-11-24 14:04:01 -08006656 chomp $line;
Jakub Narebski793c4002006-11-24 22:25:50 +01006657 print esc_html($line, -nbsp=>1) . "<br/>\n";
Kay Sieversd8a20ba2005-08-07 20:28:53 +02006658 }
6659 print "</div>\n";
6660 git_footer_html();
6661}
6662
Jakub Narebski4af819d2009-09-01 13:39:17 +02006663sub git_blame_common {
6664 my $format = shift || 'porcelain';
Jakub Narebski84d9e2d2012-02-03 13:44:54 +01006665 if ($format eq 'porcelain' && $input_params{'javascript'}) {
Jakub Narebskic4ccf612009-09-01 13:39:19 +02006666 $format = 'incremental';
6667 $action = 'blame_incremental'; # for page title etc
6668 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02006669
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006670 # permissions
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006671 gitweb_check_feature('blame')
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006672 or die_error(403, "Blame view not allowed");
Lea Wiemann074afaa2008-06-19 22:03:21 +02006673
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006674 # error checking
Lea Wiemann074afaa2008-06-19 22:03:21 +02006675 die_error(400, "No file name given") unless $file_name;
Jakub Narebski847e01f2006-08-14 02:05:47 +02006676 $hash_base ||= git_get_head_hash($project);
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006677 die_error(404, "Couldn't find base commit") unless $hash_base;
Jakub Narebski847e01f2006-08-14 02:05:47 +02006678 my %co = parse_commit($hash_base)
Lea Wiemann074afaa2008-06-19 22:03:21 +02006679 or die_error(404, "Commit not found");
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006680 my $ftype = "blob";
Luben Tuikov1f2857e2006-07-23 13:34:55 -07006681 if (!defined $hash) {
6682 $hash = git_get_hash_by_path($hash_base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02006683 or die_error(404, "Error looking up file");
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006684 } else {
6685 $ftype = git_get_type($hash);
6686 if ($ftype !~ "blob") {
6687 die_error(400, "Object is not a blob");
6688 }
Luben Tuikov1f2857e2006-07-23 13:34:55 -07006689 }
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006690
Jakub Narebski4af819d2009-09-01 13:39:17 +02006691 my $fd;
6692 if ($format eq 'incremental') {
6693 # get file contents (as base)
6694 open $fd, "-|", git_cmd(), 'cat-file', 'blob', $hash
6695 or die_error(500, "Open git-cat-file failed");
6696 } elsif ($format eq 'data') {
6697 # run git-blame --incremental
6698 open $fd, "-|", git_cmd(), "blame", "--incremental",
6699 $hash_base, "--", $file_name
6700 or die_error(500, "Open git-blame --incremental failed");
6701 } else {
6702 # run git-blame --porcelain
6703 open $fd, "-|", git_cmd(), "blame", '-p',
6704 $hash_base, '--', $file_name
6705 or die_error(500, "Open git-blame --porcelain failed");
6706 }
Ævar Arnfjörð Bjarmasonfd870042013-08-30 08:37:01 +00006707 binmode $fd, ':utf8';
Jakub Narebski4af819d2009-09-01 13:39:17 +02006708
6709 # incremental blame data returns early
6710 if ($format eq 'data') {
6711 print $cgi->header(
6712 -type=>"text/plain", -charset => "utf-8",
6713 -status=> "200 OK");
6714 local $| = 1; # output autoflush
Jürgen Kreileder57cf4ad2011-12-17 10:22:23 +01006715 while (my $line = <$fd>) {
6716 print to_utf8($line);
6717 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02006718 close $fd
6719 or print "ERROR $!\n";
6720
6721 print 'END';
6722 if (defined $t0 && gitweb_check_feature('timed')) {
6723 print ' '.
Jakub Narebski3962f1d72010-11-09 19:27:54 +01006724 tv_interval($t0, [ gettimeofday() ]).
Jakub Narebski4af819d2009-09-01 13:39:17 +02006725 ' '.$number_of_git_cmds;
6726 }
6727 print "\n";
6728
6729 return;
6730 }
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006731
6732 # page header
Luben Tuikov1f2857e2006-07-23 13:34:55 -07006733 git_header_html();
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02006734 my $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01006735 $cgi->a({-href => href(action=>"blob", -replay=>1)},
Jakub Narebski952c65f2006-08-22 16:52:50 +02006736 "blob") .
Jakub Narebski87e573f2009-12-01 17:54:26 +01006737 " | ";
6738 if ($format eq 'incremental') {
6739 $formats_nav .=
6740 $cgi->a({-href => href(action=>"blame", javascript=>0, -replay=>1)},
6741 "blame") . " (non-incremental)";
6742 } else {
6743 $formats_nav .=
6744 $cgi->a({-href => href(action=>"blame_incremental", -replay=>1)},
6745 "blame") . " (incremental)";
6746 }
6747 $formats_nav .=
Jakub Narebski952c65f2006-08-22 16:52:50 +02006748 " | " .
Jakub Narebskia3823e52007-11-01 13:06:29 +01006749 $cgi->a({-href => href(action=>"history", -replay=>1)},
6750 "history") .
Petr Baudiscae18622006-09-22 03:19:41 +02006751 " | " .
Jakub Narebski4af819d2009-09-01 13:39:17 +02006752 $cgi->a({-href => href(action=>$action, file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02006753 "HEAD");
Jakub Narebski847e01f2006-08-14 02:05:47 +02006754 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
6755 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
Luben Tuikov59fb1c92006-08-17 10:39:29 -07006756 git_print_page_path($file_name, $ftype, $hash_base);
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006757
6758 # page body
Jakub Narebski4af819d2009-09-01 13:39:17 +02006759 if ($format eq 'incremental') {
6760 print "<noscript>\n<div class=\"error\"><center><b>\n".
6761 "This page requires JavaScript to run.\n Use ".
Jakub Narebskic4ccf612009-09-01 13:39:19 +02006762 $cgi->a({-href => href(action=>'blame',javascript=>0,-replay=>1)},
Jakub Narebski4af819d2009-09-01 13:39:17 +02006763 'this page').
6764 " instead.\n".
6765 "</b></center></div>\n</noscript>\n";
6766
6767 print qq!<div id="progress_bar" style="width: 100%; background-color: yellow"></div>\n!;
6768 }
6769
6770 print qq!<div class="page_body">\n!;
6771 print qq!<div id="progress_info">... / ...</div>\n!
6772 if ($format eq 'incremental');
6773 print qq!<table id="blame_table" class="blame" width="100%">\n!.
6774 #qq!<col width="5.5em" /><col width="2.5em" /><col width="*" />\n!.
6775 qq!<thead>\n!.
6776 qq!<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n!.
6777 qq!</thead>\n!.
6778 qq!<tbody>\n!;
6779
Jakub Narebskiaef37682009-07-25 00:44:06 +02006780 my @rev_color = qw(light dark);
Luben Tuikovcc1bf972006-07-23 13:37:53 -07006781 my $num_colors = scalar(@rev_color);
6782 my $current_color = 0;
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006783
Jakub Narebski4af819d2009-09-01 13:39:17 +02006784 if ($format eq 'incremental') {
6785 my $color_class = $rev_color[$current_color];
6786
6787 #contents of a file
6788 my $linenr = 0;
6789 LINE:
6790 while (my $line = <$fd>) {
6791 chomp $line;
6792 $linenr++;
6793
6794 print qq!<tr id="l$linenr" class="$color_class">!.
6795 qq!<td class="sha1"><a href=""> </a></td>!.
6796 qq!<td class="linenr">!.
6797 qq!<a class="linenr" href="">$linenr</a></td>!;
6798 print qq!<td class="pre">! . esc_html($line) . "</td>\n";
6799 print qq!</tr>\n!;
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07006800 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02006801
6802 } else { # porcelain, i.e. ordinary blame
6803 my %metainfo = (); # saves information about commits
6804
6805 # blame data
6806 LINE:
6807 while (my $line = <$fd>) {
6808 chomp $line;
6809 # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
6810 # no <lines in group> for subsequent lines in group of lines
6811 my ($full_rev, $orig_lineno, $lineno, $group_size) =
6812 ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
6813 if (!exists $metainfo{$full_rev}) {
6814 $metainfo{$full_rev} = { 'nprevious' => 0 };
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07006815 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02006816 my $meta = $metainfo{$full_rev};
6817 my $data;
6818 while ($data = <$fd>) {
6819 chomp $data;
6820 last if ($data =~ s/^\t//); # contents of line
6821 if ($data =~ /^(\S+)(?: (.*))?$/) {
6822 $meta->{$1} = $2 unless exists $meta->{$1};
6823 }
6824 if ($data =~ /^previous /) {
6825 $meta->{'nprevious'}++;
Jakub Narebskia36817b2009-07-25 00:44:05 +02006826 }
6827 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02006828 my $short_rev = substr($full_rev, 0, 8);
6829 my $author = $meta->{'author'};
6830 my %date =
6831 parse_date($meta->{'author-time'}, $meta->{'author-tz'});
6832 my $date = $date{'iso-tz'};
6833 if ($group_size) {
6834 $current_color = ($current_color + 1) % $num_colors;
6835 }
6836 my $tr_class = $rev_color[$current_color];
6837 $tr_class .= ' boundary' if (exists $meta->{'boundary'});
6838 $tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0);
6839 $tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1);
6840 print "<tr id=\"l$lineno\" class=\"$tr_class\">\n";
6841 if ($group_size) {
6842 print "<td class=\"sha1\"";
6843 print " title=\"". esc_html($author) . ", $date\"";
6844 print " rowspan=\"$group_size\"" if ($group_size > 1);
6845 print ">";
6846 print $cgi->a({-href => href(action=>"commit",
6847 hash=>$full_rev,
6848 file_name=>$file_name)},
6849 esc_html($short_rev));
6850 if ($group_size >= 2) {
6851 my @author_initials = ($author =~ /\b([[:upper:]])\B/g);
6852 if (@author_initials) {
6853 print "<br />" .
6854 esc_html(join('', @author_initials));
6855 # or join('.', ...)
6856 }
6857 }
6858 print "</td>\n";
6859 }
6860 # 'previous' <sha1 of parent commit> <filename at commit>
6861 if (exists $meta->{'previous'} &&
6862 $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) {
6863 $meta->{'parent'} = $1;
6864 $meta->{'file_parent'} = unquote($2);
6865 }
6866 my $linenr_commit =
6867 exists($meta->{'parent'}) ?
6868 $meta->{'parent'} : $full_rev;
6869 my $linenr_filename =
6870 exists($meta->{'file_parent'}) ?
6871 $meta->{'file_parent'} : unquote($meta->{'filename'});
6872 my $blamed = href(action => 'blame',
6873 file_name => $linenr_filename,
6874 hash_base => $linenr_commit);
6875 print "<td class=\"linenr\">";
6876 print $cgi->a({ -href => "$blamed#l$orig_lineno",
6877 -class => "linenr" },
6878 esc_html($lineno));
6879 print "</td>";
6880 print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
6881 print "</tr>\n";
6882 } # end while
6883
Luben Tuikov1f2857e2006-07-23 13:34:55 -07006884 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02006885
6886 # footer
6887 print "</tbody>\n".
6888 "</table>\n"; # class="blame"
6889 print "</div>\n"; # class="blame_body"
Jakub Narebski952c65f2006-08-22 16:52:50 +02006890 close $fd
6891 or print "Reading blob failed\n";
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006892
Luben Tuikov1f2857e2006-07-23 13:34:55 -07006893 git_footer_html();
6894}
6895
Jakub Narebski4af819d2009-09-01 13:39:17 +02006896sub git_blame {
6897 git_blame_common();
6898}
6899
6900sub git_blame_incremental {
6901 git_blame_common('incremental');
6902}
6903
6904sub git_blame_data {
6905 git_blame_common('data');
6906}
6907
Kay Sieversede5e102005-08-07 20:23:12 +02006908sub git_tags {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006909 my $head = git_get_head_hash($project);
Kay Sieversede5e102005-08-07 20:23:12 +02006910 git_header_html();
Giuseppe Bilotta11e7bec2010-11-11 13:26:12 +01006911 git_print_page_nav('','', $head,undef,$head,format_ref_views('tags'));
Jakub Narebski847e01f2006-08-14 02:05:47 +02006912 git_print_header_div('summary', $project);
Jakub Narebski27fb8c42006-07-30 20:32:01 +02006913
Jakub Narebskicd146402006-11-02 20:23:11 +01006914 my @tagslist = git_get_tags_list();
6915 if (@tagslist) {
6916 git_tags_body(\@tagslist);
Kay Sieversede5e102005-08-07 20:23:12 +02006917 }
Kay Sieversede5e102005-08-07 20:23:12 +02006918 git_footer_html();
6919}
6920
Kay Sieversd8f1c5c2005-10-04 01:12:47 +02006921sub git_heads {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006922 my $head = git_get_head_hash($project);
Kay Sievers0db37972005-08-07 20:24:35 +02006923 git_header_html();
Giuseppe Bilotta11e7bec2010-11-11 13:26:12 +01006924 git_print_page_nav('','', $head,undef,$head,format_ref_views('heads'));
Jakub Narebski847e01f2006-08-14 02:05:47 +02006925 git_print_header_div('summary', $project);
Jakub Narebski27fb8c42006-07-30 20:32:01 +02006926
Jakub Narebskicd146402006-11-02 20:23:11 +01006927 my @headslist = git_get_heads_list();
6928 if (@headslist) {
6929 git_heads_body(\@headslist, $head);
Kay Sievers0db37972005-08-07 20:24:35 +02006930 }
Kay Sievers0db37972005-08-07 20:24:35 +02006931 git_footer_html();
6932}
6933
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006934# used both for single remote view and for list of all the remotes
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01006935sub git_remotes {
6936 gitweb_check_feature('remote_heads')
6937 or die_error(403, "Remote heads view is disabled");
6938
6939 my $head = git_get_head_hash($project);
Giuseppe Bilottabb607762010-11-11 13:26:14 +01006940 my $remote = $input_params{'hash'};
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01006941
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006942 my $remotedata = git_get_remotes_list($remote);
6943 die_error(500, "Unable to get remote information") unless defined $remotedata;
Giuseppe Bilottabb607762010-11-11 13:26:14 +01006944
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006945 unless (%$remotedata) {
6946 die_error(404, defined $remote ?
6947 "Remote $remote not found" :
6948 "No remotes found");
Giuseppe Bilottabb607762010-11-11 13:26:14 +01006949 }
6950
6951 git_header_html(undef, undef, -action_extra => $remote);
6952 git_print_page_nav('', '', $head, undef, $head,
6953 format_ref_views($remote ? '' : 'remotes'));
6954
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006955 fill_remote_heads($remotedata);
Giuseppe Bilottabb607762010-11-11 13:26:14 +01006956 if (defined $remote) {
6957 git_print_header_div('remotes', "$remote remote for $project");
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006958 git_remote_block($remote, $remotedata->{$remote}, undef, $head);
Giuseppe Bilottabb607762010-11-11 13:26:14 +01006959 } else {
6960 git_print_header_div('summary', "$project remotes");
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006961 git_remotes_body($remotedata, undef, $head);
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01006962 }
Giuseppe Bilottabb607762010-11-11 13:26:14 +01006963
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01006964 git_footer_html();
6965}
6966
Luben Tuikov930cf7d2006-07-09 20:18:57 -07006967sub git_blob_plain {
Jakub Narebski7f718e82008-06-03 16:47:10 +02006968 my $type = shift;
Jakub Narebskif2e73302006-08-26 19:14:25 +02006969 my $expires;
Jakub Narebskif2e73302006-08-26 19:14:25 +02006970
Luben Tuikovcff07712006-07-23 13:28:55 -07006971 if (!defined $hash) {
Jakub Narebski5be01bc2006-07-29 22:43:40 +02006972 if (defined $file_name) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006973 my $base = $hash_base || git_get_head_hash($project);
Jakub Narebski5be01bc2006-07-29 22:43:40 +02006974 $hash = git_get_hash_by_path($base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02006975 or die_error(404, "Cannot find file");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02006976 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006977 die_error(400, "No file name defined");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02006978 }
Martin Waitz800764c2006-09-16 23:09:02 +02006979 } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
6980 # blobs defined by non-textual hash id's can be cached
6981 $expires = "+1d";
Jakub Narebski5be01bc2006-07-29 22:43:40 +02006982 }
Martin Waitz800764c2006-09-16 23:09:02 +02006983
Dennis Stosberg25691fb2006-08-28 17:49:58 +02006984 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
Lea Wiemann074afaa2008-06-19 22:03:21 +02006985 or die_error(500, "Open git-cat-file blob '$hash' failed");
Luben Tuikov930cf7d2006-07-09 20:18:57 -07006986
Jakub Narebski7f718e82008-06-03 16:47:10 +02006987 # content-type (can include charset)
6988 $type = blob_contenttype($fd, $file_name, $type);
Luben Tuikov930cf7d2006-07-09 20:18:57 -07006989
Jakub Narebski7f718e82008-06-03 16:47:10 +02006990 # "save as" filename, even when no $file_name is given
Luben Tuikov930cf7d2006-07-09 20:18:57 -07006991 my $save_as = "$hash";
6992 if (defined $file_name) {
6993 $save_as = $file_name;
6994 } elsif ($type =~ m/^text\//) {
6995 $save_as .= '.txt';
6996 }
6997
Matt McCutchen7e1100e2009-02-07 19:00:09 -05006998 # With XSS prevention on, blobs of all types except a few known safe
6999 # ones are served with "Content-Disposition: attachment" to make sure
7000 # they don't run in our security domain. For certain image types,
7001 # blob view writes an <img> tag referring to blob_plain view, and we
7002 # want to be sure not to break that by serving the image as an
7003 # attachment (though Firefox 3 doesn't seem to care).
7004 my $sandbox = $prevent_xss &&
Jakub Narebski86afbd02011-06-30 11:39:20 +02007005 $type !~ m!^(?:text/[a-z]+|image/(?:gif|png|jpeg))(?:[ ;]|$)!;
7006
7007 # serve text/* as text/plain
7008 if ($prevent_xss &&
Jakub Narebskie8c35312011-06-30 11:39:21 +02007009 ($type =~ m!^text/[a-z]+\b(.*)$! ||
7010 ($type =~ m!^[a-z]+/[a-z]\+xml\b(.*)$! && -T $fd))) {
Jakub Narebski86afbd02011-06-30 11:39:20 +02007011 my $rest = $1;
7012 $rest = defined $rest ? $rest : '';
7013 $type = "text/plain$rest";
7014 }
Matt McCutchen7e1100e2009-02-07 19:00:09 -05007015
Jakub Narebskif2e73302006-08-26 19:14:25 +02007016 print $cgi->header(
Jakub Narebski7f718e82008-06-03 16:47:10 +02007017 -type => $type,
7018 -expires => $expires,
Matt McCutchen7e1100e2009-02-07 19:00:09 -05007019 -content_disposition =>
7020 ($sandbox ? 'attachment' : 'inline')
7021 . '; filename="' . $save_as . '"');
Jakub Narebski34122b52009-05-11 03:29:40 +02007022 local $/ = undef;
Luben Tuikov930cf7d2006-07-09 20:18:57 -07007023 binmode STDOUT, ':raw';
7024 print <$fd>;
7025 binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
Luben Tuikov930cf7d2006-07-09 20:18:57 -07007026 close $fd;
7027}
7028
Kay Sievers09bd7892005-08-07 20:21:23 +02007029sub git_blob {
Jakub Narebskif2e73302006-08-26 19:14:25 +02007030 my $expires;
Jakub Narebskif2e73302006-08-26 19:14:25 +02007031
Luben Tuikovcff07712006-07-23 13:28:55 -07007032 if (!defined $hash) {
Jakub Narebski5be01bc2006-07-29 22:43:40 +02007033 if (defined $file_name) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02007034 my $base = $hash_base || git_get_head_hash($project);
Jakub Narebski5be01bc2006-07-29 22:43:40 +02007035 $hash = git_get_hash_by_path($base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02007036 or die_error(404, "Cannot find file");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02007037 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007038 die_error(400, "No file name defined");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02007039 }
Martin Waitz800764c2006-09-16 23:09:02 +02007040 } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
7041 # blobs defined by non-textual hash id's can be cached
7042 $expires = "+1d";
Jakub Narebski5be01bc2006-07-29 22:43:40 +02007043 }
Martin Waitz800764c2006-09-16 23:09:02 +02007044
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08007045 my $have_blame = gitweb_check_feature('blame');
Dennis Stosberg25691fb2006-08-28 17:49:58 +02007046 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
Lea Wiemann074afaa2008-06-19 22:03:21 +02007047 or die_error(500, "Couldn't cat $file_name, $hash");
Jakub Narebski847e01f2006-08-14 02:05:47 +02007048 my $mimetype = blob_mimetype($fd, $file_name);
Johannes Schindelinb331fe52010-04-27 21:34:44 +02007049 # use 'blob_plain' (aka 'raw') view for files that cannot be displayed
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01007050 if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)! && -B $fd) {
Luben Tuikov930cf7d2006-07-09 20:18:57 -07007051 close $fd;
7052 return git_blob_plain($mimetype);
7053 }
Jakub Narebski5a4cf332006-12-04 23:47:22 +01007054 # we can have blame only for text/* mimetype
7055 $have_blame &&= ($mimetype =~ m!^text/!);
7056
Jakub Narebski592ea412010-04-27 21:34:45 +02007057 my $highlight = gitweb_check_feature('highlight');
7058 my $syntax = guess_file_syntax($highlight, $mimetype, $file_name);
7059 $fd = run_highlighter($fd, $highlight, $syntax)
7060 if $syntax;
Johannes Schindelinb331fe52010-04-27 21:34:44 +02007061
Jakub Narebskif2e73302006-08-26 19:14:25 +02007062 git_header_html(undef, $expires);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02007063 my $formats_nav = '';
Jakub Narebski847e01f2006-08-14 02:05:47 +02007064 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
Kay Sievers93129442005-10-17 03:27:54 +02007065 if (defined $file_name) {
Florian Forster5996ca02006-06-12 10:31:57 +02007066 if ($have_blame) {
Jakub Narebski952c65f2006-08-22 16:52:50 +02007067 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01007068 $cgi->a({-href => href(action=>"blame", -replay=>1)},
Jakub Narebski952c65f2006-08-22 16:52:50 +02007069 "blame") .
7070 " | ";
Florian Forster5996ca02006-06-12 10:31:57 +02007071 }
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02007072 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01007073 $cgi->a({-href => href(action=>"history", -replay=>1)},
Petr Baudiscae18622006-09-22 03:19:41 +02007074 "history") .
7075 " | " .
Jakub Narebskia3823e52007-11-01 13:06:29 +01007076 $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
Petr Baudis35329cc2006-09-22 03:19:50 +02007077 "raw") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02007078 " | " .
7079 $cgi->a({-href => href(action=>"blob",
7080 hash_base=>"HEAD", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02007081 "HEAD");
Kay Sievers93129442005-10-17 03:27:54 +02007082 } else {
Jakub Narebski952c65f2006-08-22 16:52:50 +02007083 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01007084 $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
7085 "raw");
Kay Sievers93129442005-10-17 03:27:54 +02007086 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02007087 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
7088 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
Kay Sievers09bd7892005-08-07 20:21:23 +02007089 } else {
7090 print "<div class=\"page_nav\">\n" .
7091 "<br/><br/></div>\n" .
Jakub Narebski3017ed62010-12-15 00:34:01 +01007092 "<div class=\"title\">".esc_html($hash)."</div>\n";
Kay Sievers09bd7892005-08-07 20:21:23 +02007093 }
Luben Tuikov59fb1c92006-08-17 10:39:29 -07007094 git_print_page_path($file_name, "blob", $hash_base);
Kay Sieversc07ad4b2005-08-07 20:22:44 +02007095 print "<div class=\"page_body\">\n";
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01007096 if ($mimetype =~ m!^image/!) {
Jakub Narebski3017ed62010-12-15 00:34:01 +01007097 print qq!<img type="!.esc_attr($mimetype).qq!"!;
Jakub Narebski5a4cf332006-12-04 23:47:22 +01007098 if ($file_name) {
Jakub Narebski3017ed62010-12-15 00:34:01 +01007099 print qq! alt="!.esc_attr($file_name).qq!" title="!.esc_attr($file_name).qq!"!;
Jakub Narebski5a4cf332006-12-04 23:47:22 +01007100 }
7101 print qq! src="! .
7102 href(action=>"blob_plain", hash=>$hash,
7103 hash_base=>$hash_base, file_name=>$file_name) .
7104 qq!" />\n!;
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01007105 } else {
7106 my $nr;
7107 while (my $line = <$fd>) {
7108 chomp $line;
7109 $nr++;
7110 $line = untabify($line);
Jakub Narebski592ea412010-04-27 21:34:45 +02007111 printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!,
Jakub Narebski08667862011-09-16 14:41:57 +02007112 $nr, esc_attr(href(-replay => 1)), $nr, $nr,
7113 $syntax ? sanitize($line) : esc_html($line, -nbsp=>1);
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01007114 }
Kay Sievers161332a2005-08-07 19:49:46 +02007115 }
Jakub Narebski952c65f2006-08-22 16:52:50 +02007116 close $fd
7117 or print "Reading blob failed.\n";
Kay Sieversfbb592a2005-08-07 20:12:11 +02007118 print "</div>";
Kay Sievers12a88f22005-08-07 20:02:47 +02007119 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02007120}
7121
7122sub git_tree {
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07007123 if (!defined $hash_base) {
7124 $hash_base = "HEAD";
7125 }
Kay Sieversb87d78d2005-08-07 20:21:04 +02007126 if (!defined $hash) {
Kay Sievers09bd7892005-08-07 20:21:23 +02007127 if (defined $file_name) {
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07007128 $hash = git_get_hash_by_path($hash_base, $file_name, "tree");
7129 } else {
7130 $hash = $hash_base;
Kay Sievers10dba282005-08-07 20:25:27 +02007131 }
Kay Sieverse925f382005-08-07 20:23:35 +02007132 }
Jakub Narebski2d7a3532008-10-02 16:50:04 +02007133 die_error(404, "No such tree") unless defined($hash);
Jakub Narebski34122b52009-05-11 03:29:40 +02007134
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02007135 my $show_sizes = gitweb_check_feature('show-sizes');
7136 my $have_blame = gitweb_check_feature('blame');
7137
Jakub Narebski34122b52009-05-11 03:29:40 +02007138 my @entries = ();
7139 {
7140 local $/ = "\0";
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02007141 open my $fd, "-|", git_cmd(), "ls-tree", '-z',
7142 ($show_sizes ? '-l' : ()), @extra_options, $hash
Jakub Narebski34122b52009-05-11 03:29:40 +02007143 or die_error(500, "Open git-ls-tree failed");
7144 @entries = map { chomp; $_ } <$fd>;
7145 close $fd
7146 or die_error(404, "Reading tree failed");
7147 }
Kay Sieversd63577d2005-08-07 20:18:13 +02007148
Jakub Narebski847e01f2006-08-14 02:05:47 +02007149 my $refs = git_get_references();
7150 my $ref = format_ref_marker($refs, $hash_base);
Kay Sievers12a88f22005-08-07 20:02:47 +02007151 git_header_html();
Jakub Narebski300454f2006-10-21 17:53:09 +02007152 my $basedir = '';
Jakub Narebski847e01f2006-08-14 02:05:47 +02007153 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
Petr Baudiscae18622006-09-22 03:19:41 +02007154 my @views_nav = ();
7155 if (defined $file_name) {
7156 push @views_nav,
Jakub Narebskia3823e52007-11-01 13:06:29 +01007157 $cgi->a({-href => href(action=>"history", -replay=>1)},
Petr Baudiscae18622006-09-22 03:19:41 +02007158 "history"),
7159 $cgi->a({-href => href(action=>"tree",
7160 hash_base=>"HEAD", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02007161 "HEAD"),
Petr Baudiscae18622006-09-22 03:19:41 +02007162 }
Matt McCutchena3c8ab32007-07-22 01:30:27 +02007163 my $snapshot_links = format_snapshot_links($hash);
7164 if (defined $snapshot_links) {
Petr Baudiscae18622006-09-22 03:19:41 +02007165 # FIXME: Should be available when we have no hash base as well.
Matt McCutchena3c8ab32007-07-22 01:30:27 +02007166 push @views_nav, $snapshot_links;
Petr Baudiscae18622006-09-22 03:19:41 +02007167 }
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02007168 git_print_page_nav('tree','', $hash_base, undef, undef,
7169 join(' | ', @views_nav));
Jakub Narebski847e01f2006-08-14 02:05:47 +02007170 git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
Kay Sieversd63577d2005-08-07 20:18:13 +02007171 } else {
Jakub Narebskifa702002006-08-31 00:35:07 +02007172 undef $hash_base;
Kay Sieversd63577d2005-08-07 20:18:13 +02007173 print "<div class=\"page_nav\">\n";
7174 print "<br/><br/></div>\n";
Jakub Narebski3017ed62010-12-15 00:34:01 +01007175 print "<div class=\"title\">".esc_html($hash)."</div>\n";
Kay Sieversd63577d2005-08-07 20:18:13 +02007176 }
Kay Sievers09bd7892005-08-07 20:21:23 +02007177 if (defined $file_name) {
Jakub Narebski300454f2006-10-21 17:53:09 +02007178 $basedir = $file_name;
7179 if ($basedir ne '' && substr($basedir, -1) ne '/') {
7180 $basedir .= '/';
7181 }
Jakub Narebski2d7a3532008-10-02 16:50:04 +02007182 git_print_page_path($file_name, 'tree', $hash_base);
Kay Sievers09bd7892005-08-07 20:21:23 +02007183 }
Kay Sieversfbb592a2005-08-07 20:12:11 +02007184 print "<div class=\"page_body\">\n";
Jakub Narebski591ebf62007-11-19 14:16:11 +01007185 print "<table class=\"tree\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07007186 my $alternate = 1;
Jakub Narebskib6b7fc72006-10-21 17:54:44 +02007187 # '..' (top directory) link if possible
7188 if (defined $hash_base &&
7189 defined $file_name && $file_name =~ m![^/]+$!) {
7190 if ($alternate) {
7191 print "<tr class=\"dark\">\n";
7192 } else {
7193 print "<tr class=\"light\">\n";
7194 }
7195 $alternate ^= 1;
7196
7197 my $up = $file_name;
7198 $up =~ s!/?[^/]+$!!;
7199 undef $up unless $up;
7200 # based on git_print_tree_entry
7201 print '<td class="mode">' . mode_str('040000') . "</td>\n";
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02007202 print '<td class="size">&nbsp;</td>'."\n" if $show_sizes;
Jakub Narebskib6b7fc72006-10-21 17:54:44 +02007203 print '<td class="list">';
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02007204 print $cgi->a({-href => href(action=>"tree",
7205 hash_base=>$hash_base,
Jakub Narebskib6b7fc72006-10-21 17:54:44 +02007206 file_name=>$up)},
7207 "..");
7208 print "</td>\n";
7209 print "<td class=\"link\"></td>\n";
7210
7211 print "</tr>\n";
7212 }
Kay Sievers161332a2005-08-07 19:49:46 +02007213 foreach my $line (@entries) {
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02007214 my %t = parse_ls_tree_line($line, -z => 1, -l => $show_sizes);
Jakub Narebskicb849b42006-08-31 00:32:15 +02007215
Kay Sieversbddec012005-08-07 20:25:42 +02007216 if ($alternate) {
Kay Sieversc994d622005-08-07 20:27:18 +02007217 print "<tr class=\"dark\">\n";
Kay Sieversbddec012005-08-07 20:25:42 +02007218 } else {
Kay Sieversc994d622005-08-07 20:27:18 +02007219 print "<tr class=\"light\">\n";
Kay Sieversbddec012005-08-07 20:25:42 +02007220 }
7221 $alternate ^= 1;
Jakub Narebskicb849b42006-08-31 00:32:15 +02007222
Jakub Narebski300454f2006-10-21 17:53:09 +02007223 git_print_tree_entry(\%t, $basedir, $hash_base, $have_blame);
Jakub Narebskifa702002006-08-31 00:35:07 +02007224
Kay Sievers42f7eb92005-08-07 20:21:46 +02007225 print "</tr>\n";
Kay Sievers161332a2005-08-07 19:49:46 +02007226 }
Kay Sievers42f7eb92005-08-07 20:21:46 +02007227 print "</table>\n" .
7228 "</div>";
Kay Sievers12a88f22005-08-07 20:02:47 +02007229 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02007230}
7231
Krzesimir Nowake3747472013-12-11 12:54:44 +01007232sub sanitize_for_filename {
7233 my $name = shift;
7234
7235 $name =~ s!/!-!g;
7236 $name =~ s/[^[:alnum:]_.-]//g;
7237
7238 return $name;
7239}
7240
Mark Radab6292752009-11-07 16:13:29 +01007241sub snapshot_name {
7242 my ($project, $hash) = @_;
7243
7244 # path/to/project.git -> project
7245 # path/to/project/.git -> project
7246 my $name = to_utf8($project);
7247 $name =~ s,([^/])/*\.git$,$1,;
Krzesimir Nowake3747472013-12-11 12:54:44 +01007248 $name = sanitize_for_filename(basename($name));
Mark Radab6292752009-11-07 16:13:29 +01007249
7250 my $ver = $hash;
7251 if ($hash =~ /^[0-9a-fA-F]+$/) {
7252 # shorten SHA-1 hash
7253 my $full_hash = git_get_full_hash($project, $hash);
7254 if ($full_hash =~ /^$hash/ && length($hash) > 7) {
7255 $ver = git_get_short_hash($project, $hash);
7256 }
7257 } elsif ($hash =~ m!^refs/tags/(.*)$!) {
7258 # tags don't need shortened SHA-1 hash
7259 $ver = $1;
7260 } else {
7261 # branches and other need shortened SHA-1 hash
Krzesimir Nowak8d646a92013-12-11 12:54:43 +01007262 my $strip_refs = join '|', map { quotemeta } get_branch_refs();
7263 if ($hash =~ m!^refs/($strip_refs|remotes)/(.*)$!) {
Krzesimir Nowake3747472013-12-11 12:54:44 +01007264 my $ref_dir = (defined $1) ? $1 : '';
7265 $ver = $2;
7266
7267 $ref_dir = sanitize_for_filename($ref_dir);
7268 # for refs neither in heads nor remotes we want to
7269 # add a ref dir to archive name
7270 if ($ref_dir ne '' and $ref_dir ne 'heads' and $ref_dir ne 'remotes') {
7271 $ver = $ref_dir . '-' . $ver;
7272 }
Mark Radab6292752009-11-07 16:13:29 +01007273 }
7274 $ver .= '-' . git_get_short_hash($project, $hash);
7275 }
Krzesimir Nowake3747472013-12-11 12:54:44 +01007276 # special case of sanitization for filename - we change
7277 # slashes to dots instead of dashes
Mark Radab6292752009-11-07 16:13:29 +01007278 # in case of hierarchical branch names
7279 $ver =~ s!/!.!g;
Krzesimir Nowake3747472013-12-11 12:54:44 +01007280 $ver =~ s/[^[:alnum:]_.-]//g;
Mark Radab6292752009-11-07 16:13:29 +01007281
7282 # name = project-version_string
7283 $name = "$name-$ver";
7284
7285 return wantarray ? ($name, $name) : $name;
7286}
7287
W. Trevor Kingb7d565e2012-03-29 08:45:48 -04007288sub exit_if_unmodified_since {
7289 my ($latest_epoch) = @_;
7290 our $cgi;
7291
7292 my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
7293 if (defined $if_modified) {
7294 my $since;
7295 if (eval { require HTTP::Date; 1; }) {
7296 $since = HTTP::Date::str2time($if_modified);
7297 } elsif (eval { require Time::ParseDate; 1; }) {
7298 $since = Time::ParseDate::parsedate($if_modified, GMT => 1);
7299 }
7300 if (defined $since && $latest_epoch <= $since) {
7301 my %latest_date = parse_date($latest_epoch);
7302 print $cgi->header(
7303 -last_modified => $latest_date{'rfc2822'},
7304 -status => '304 Not Modified');
7305 goto DONE_GITWEB;
7306 }
7307 }
7308}
7309
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05307310sub git_snapshot {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02007311 my $format = $input_params{'snapshot_format'};
Giuseppe Bilotta5e166842008-11-02 10:21:37 +01007312 if (!@snapshot_fmts) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007313 die_error(403, "Snapshots not allowed");
Jakub Narebski3473e7d2007-07-25 01:19:58 +02007314 }
7315 # default to first supported snapshot format
Giuseppe Bilotta5e166842008-11-02 10:21:37 +01007316 $format ||= $snapshot_fmts[0];
Jakub Narebski3473e7d2007-07-25 01:19:58 +02007317 if ($format !~ m/^[a-z0-9]+$/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007318 die_error(400, "Invalid snapshot format parameter");
Jakub Narebski3473e7d2007-07-25 01:19:58 +02007319 } elsif (!exists($known_snapshot_formats{$format})) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007320 die_error(400, "Unknown snapshot format");
Mark A Rada1bfd3632009-08-06 10:25:39 -04007321 } elsif ($known_snapshot_formats{$format}{'disabled'}) {
7322 die_error(403, "Snapshot format not allowed");
Mark Rada34b31a82009-08-25 00:59:48 -04007323 } elsif (!grep($_ eq $format, @snapshot_fmts)) {
7324 die_error(403, "Unsupported snapshot format");
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05307325 }
7326
Mark Radafdb0c362009-09-26 13:46:08 -04007327 my $type = git_get_type("$hash^{}");
7328 if (!$type) {
7329 die_error(404, 'Object does not exist');
7330 } elsif ($type eq 'blob') {
7331 die_error(400, 'Object is not a tree-ish');
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05307332 }
7333
Mark Radab6292752009-11-07 16:13:29 +01007334 my ($name, $prefix) = snapshot_name($project, $hash);
7335 my $filename = "$name$known_snapshot_formats{$format}{'suffix'}";
W. Trevor King8745db62012-03-29 08:45:49 -04007336
7337 my %co = parse_commit($hash);
7338 exit_if_unmodified_since($co{'committer_epoch'}) if %co;
7339
Mark Radab6292752009-11-07 16:13:29 +01007340 my $cmd = quote_command(
Lea Wiemann516381d2008-06-17 23:46:35 +02007341 git_cmd(), 'archive',
7342 "--format=$known_snapshot_formats{$format}{'format'}",
Mark Radab6292752009-11-07 16:13:29 +01007343 "--prefix=$prefix/", $hash);
Matt McCutchena3c8ab32007-07-22 01:30:27 +02007344 if (exists $known_snapshot_formats{$format}{'compressor'}) {
Lea Wiemann516381d2008-06-17 23:46:35 +02007345 $cmd .= ' | ' . quote_command(@{$known_snapshot_formats{$format}{'compressor'}});
Mark Levedahl072570e2007-05-20 11:46:46 -04007346 }
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05307347
Mark Radab6292752009-11-07 16:13:29 +01007348 $filename =~ s/(["\\])/\\$1/g;
W. Trevor King8745db62012-03-29 08:45:49 -04007349 my %latest_date;
7350 if (%co) {
7351 %latest_date = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
7352 }
7353
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02007354 print $cgi->header(
Matt McCutchena3c8ab32007-07-22 01:30:27 +02007355 -type => $known_snapshot_formats{$format}{'type'},
Mark Radab6292752009-11-07 16:13:29 +01007356 -content_disposition => 'inline; filename="' . $filename . '"',
W. Trevor King8745db62012-03-29 08:45:49 -04007357 %co ? (-last_modified => $latest_date{'rfc2822'}) : (),
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02007358 -status => '200 OK');
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05307359
Mark Levedahl072570e2007-05-20 11:46:46 -04007360 open my $fd, "-|", $cmd
Lea Wiemann074afaa2008-06-19 22:03:21 +02007361 or die_error(500, "Execute git-archive failed");
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05307362 binmode STDOUT, ':raw';
7363 print <$fd>;
7364 binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
7365 close $fd;
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05307366}
7367
Jakub Narebski15f0b112009-11-13 02:02:13 +01007368sub git_log_generic {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007369 my ($fmt_name, $body_subr, $base, $parent, $file_name, $file_hash) = @_;
Jakub Narebski15f0b112009-11-13 02:02:13 +01007370
Jakub Narebski847e01f2006-08-14 02:05:47 +02007371 my $head = git_get_head_hash($project);
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007372 if (!defined $base) {
7373 $base = $head;
Kay Sievers0db37972005-08-07 20:24:35 +02007374 }
Kay Sieversea4a6df2005-08-07 20:26:49 +02007375 if (!defined $page) {
7376 $page = 0;
Kay Sieversb87d78d2005-08-07 20:21:04 +02007377 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02007378 my $refs = git_get_references();
Kay Sieversea4a6df2005-08-07 20:26:49 +02007379
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007380 my $commit_hash = $base;
7381 if (defined $parent) {
7382 $commit_hash = "$parent..$base";
Jakub Narebski15f0b112009-11-13 02:02:13 +01007383 }
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007384 my @commitlist =
7385 parse_commits($commit_hash, 101, (100 * $page),
7386 defined $file_name ? ($file_name, "--full-history") : ());
Kay Sieversea4a6df2005-08-07 20:26:49 +02007387
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007388 my $ftype;
7389 if (!defined $file_hash && defined $file_name) {
7390 # some commits could have deleted file in question,
7391 # and not have it in tree, but one of them has to have it
7392 for (my $i = 0; $i < @commitlist; $i++) {
7393 $file_hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name);
7394 last if defined $file_hash;
7395 }
7396 }
7397 if (defined $file_hash) {
7398 $ftype = git_get_type($file_hash);
7399 }
7400 if (defined $file_name && !defined $ftype) {
7401 die_error(500, "Unknown type of object");
7402 }
7403 my %co;
7404 if (defined $file_name) {
7405 %co = parse_commit($base)
7406 or die_error(404, "Unknown commit object");
7407 }
7408
7409
7410 my $paging_nav = format_paging_nav($fmt_name, $page, $#commitlist >= 100);
Jakub Narebski15f0b112009-11-13 02:02:13 +01007411 my $next_link = '';
Jakub Narebski42671ca2009-11-13 02:02:12 +01007412 if ($#commitlist >= 100) {
7413 $next_link =
7414 $cgi->a({-href => href(-replay=>1, page=>$page+1),
7415 -accesskey => "n", -title => "Alt-n"}, "next");
7416 }
Jakub Narebski15f0b112009-11-13 02:02:13 +01007417 my $patch_max = gitweb_get_feature('patches');
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007418 if ($patch_max && !defined $file_name) {
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01007419 if ($patch_max < 0 || @commitlist <= $patch_max) {
7420 $paging_nav .= " &sdot; " .
7421 $cgi->a({-href => href(action=>"patches", -replay=>1)},
7422 "patches");
7423 }
7424 }
7425
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02007426 git_header_html();
Jakub Narebski15f0b112009-11-13 02:02:13 +01007427 git_print_page_nav($fmt_name,'', $hash,$hash,$hash, $paging_nav);
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007428 if (defined $file_name) {
7429 git_print_header_div('commit', esc_html($co{'title'}), $base);
7430 } else {
7431 git_print_header_div('summary', $project)
7432 }
7433 git_print_page_path($file_name, $ftype, $hash_base)
7434 if (defined $file_name);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02007435
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007436 $body_subr->(\@commitlist, 0, 99, $refs, $next_link,
7437 $file_name, $file_hash, $ftype);
Jakub Narebski42671ca2009-11-13 02:02:12 +01007438
Kay Sievers034df392005-08-07 20:20:07 +02007439 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02007440}
7441
Jakub Narebski15f0b112009-11-13 02:02:13 +01007442sub git_log {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007443 git_log_generic('log', \&git_log_body,
7444 $hash, $hash_parent);
Jakub Narebski15f0b112009-11-13 02:02:13 +01007445}
7446
Kay Sievers09bd7892005-08-07 20:21:23 +02007447sub git_commit {
Jakub Narebski9954f772006-11-18 23:35:41 +01007448 $hash ||= $hash_base || "HEAD";
Lea Wiemann074afaa2008-06-19 22:03:21 +02007449 my %co = parse_commit($hash)
7450 or die_error(404, "Unknown commit object");
Kay Sievers161332a2005-08-07 19:49:46 +02007451
Jakub Narebskic9d193d2006-12-15 21:57:16 +01007452 my $parent = $co{'parent'};
7453 my $parents = $co{'parents'}; # listref
7454
7455 # we need to prepare $formats_nav before any parameter munging
7456 my $formats_nav;
7457 if (!defined $parent) {
7458 # --root commitdiff
7459 $formats_nav .= '(initial)';
7460 } elsif (@$parents == 1) {
7461 # single parent commit
7462 $formats_nav .=
7463 '(parent: ' .
7464 $cgi->a({-href => href(action=>"commit",
7465 hash=>$parent)},
7466 esc_html(substr($parent, 0, 7))) .
7467 ')';
7468 } else {
7469 # merge commit
7470 $formats_nav .=
7471 '(merge: ' .
7472 join(' ', map {
Jakub Narebskif9308a12007-03-23 21:04:31 +01007473 $cgi->a({-href => href(action=>"commit",
Jakub Narebskic9d193d2006-12-15 21:57:16 +01007474 hash=>$_)},
7475 esc_html(substr($_, 0, 7)));
7476 } @$parents ) .
7477 ')';
7478 }
Jakub Narebski1655c982009-10-09 14:26:44 +02007479 if (gitweb_check_feature('patches') && @$parents <= 1) {
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01007480 $formats_nav .= " | " .
7481 $cgi->a({-href => href(action=>"patch", -replay=>1)},
7482 "patch");
7483 }
Jakub Narebskic9d193d2006-12-15 21:57:16 +01007484
Kay Sieversd8a20ba2005-08-07 20:28:53 +02007485 if (!defined $parent) {
Jakub Narebskib9182982006-07-30 18:28:34 -07007486 $parent = "--root";
Kay Sievers6191f8e2005-08-07 20:19:56 +02007487 }
Jakub Narebski549ab4a2006-12-15 17:53:45 +01007488 my @difftree;
Jakub Narebski208ecb22007-05-07 01:10:08 +02007489 open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
7490 @diff_opts,
7491 (@$parents <= 1 ? $parent : '-c'),
7492 $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02007493 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski208ecb22007-05-07 01:10:08 +02007494 @difftree = map { chomp; $_ } <$fd>;
Lea Wiemann074afaa2008-06-19 22:03:21 +02007495 close $fd or die_error(404, "Reading git-diff-tree failed");
Kay Sievers11044292005-10-19 03:18:45 +02007496
7497 # non-textual hash id's can be cached
7498 my $expires;
7499 if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
7500 $expires = "+1d";
7501 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02007502 my $refs = git_get_references();
7503 my $ref = format_ref_marker($refs, $co{'id'});
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05307504
Jakub Narebski594e2122006-07-31 02:21:52 +02007505 git_header_html(undef, $expires);
Petr Baudisa1441542006-10-06 18:59:33 +02007506 git_print_page_nav('commit', '',
Jakub Narebski952c65f2006-08-22 16:52:50 +02007507 $hash, $co{'tree'}, $hash,
Jakub Narebskic9d193d2006-12-15 21:57:16 +01007508 $formats_nav);
Luben Tuikov4f7b34c2006-07-23 13:36:32 -07007509
Kay Sieversb87d78d2005-08-07 20:21:04 +02007510 if (defined $co{'parent'}) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02007511 git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);
Kay Sieversb87d78d2005-08-07 20:21:04 +02007512 } else {
Jakub Narebski847e01f2006-08-14 02:05:47 +02007513 git_print_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash);
Kay Sieversb87d78d2005-08-07 20:21:04 +02007514 }
Kay Sievers6191f8e2005-08-07 20:19:56 +02007515 print "<div class=\"title_text\">\n" .
Jakub Narebski591ebf62007-11-19 14:16:11 +01007516 "<table class=\"object_header\">\n";
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02007517 git_print_authorship_rows(\%co);
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00007518 print "<tr><td>commit</td><td class=\"sha1\">$co{'id'}</td></tr>\n";
Kay Sieversbddec012005-08-07 20:25:42 +02007519 print "<tr>" .
7520 "<td>tree</td>" .
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00007521 "<td class=\"sha1\">" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02007522 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash),
7523 class => "list"}, $co{'tree'}) .
Kay Sievers19806692005-08-07 20:26:27 +02007524 "</td>" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02007525 "<td class=\"link\">" .
7526 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)},
7527 "tree");
Matt McCutchena3c8ab32007-07-22 01:30:27 +02007528 my $snapshot_links = format_snapshot_links($hash);
7529 if (defined $snapshot_links) {
7530 print " | " . $snapshot_links;
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05307531 }
7532 print "</td>" .
Kay Sieversbddec012005-08-07 20:25:42 +02007533 "</tr>\n";
Jakub Narebski549ab4a2006-12-15 17:53:45 +01007534
Kay Sievers3e029292005-08-07 20:05:15 +02007535 foreach my $par (@$parents) {
Kay Sieversbddec012005-08-07 20:25:42 +02007536 print "<tr>" .
7537 "<td>parent</td>" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02007538 "<td class=\"sha1\">" .
7539 $cgi->a({-href => href(action=>"commit", hash=>$par),
7540 class => "list"}, $par) .
7541 "</td>" .
Kay Sieversbddec012005-08-07 20:25:42 +02007542 "<td class=\"link\">" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02007543 $cgi->a({-href => href(action=>"commit", hash=>$par)}, "commit") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02007544 " | " .
Jakub Narebskif2e60942006-09-03 23:43:03 +02007545 $cgi->a({-href => href(action=>"commitdiff", hash=>$hash, hash_parent=>$par)}, "diff") .
Kay Sieversbddec012005-08-07 20:25:42 +02007546 "</td>" .
7547 "</tr>\n";
Kay Sievers3e029292005-08-07 20:05:15 +02007548 }
Jakub Narebski7a9b4c52006-06-21 09:48:02 +02007549 print "</table>".
Kay Sieversb87d78d2005-08-07 20:21:04 +02007550 "</div>\n";
Jakub Narebskid16d0932006-08-17 11:21:23 +02007551
Kay Sieversfbb592a2005-08-07 20:12:11 +02007552 print "<div class=\"page_body\">\n";
Jakub Narebskid16d0932006-08-17 11:21:23 +02007553 git_print_log($co{'comment'});
Kay Sievers927dcec2005-08-07 20:18:44 +02007554 print "</div>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02007555
Jakub Narebski208ecb22007-05-07 01:10:08 +02007556 git_difftree_body(\@difftree, $hash, @$parents);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02007557
Kay Sievers12a88f22005-08-07 20:02:47 +02007558 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02007559}
7560
Jakub Narebskica946012006-12-10 13:25:47 +01007561sub git_object {
7562 # object is defined by:
7563 # - hash or hash_base alone
7564 # - hash_base and file_name
7565 my $type;
7566
7567 # - hash or hash_base alone
7568 if ($hash || ($hash_base && !defined $file_name)) {
7569 my $object_id = $hash || $hash_base;
7570
Lea Wiemann516381d2008-06-17 23:46:35 +02007571 open my $fd, "-|", quote_command(
7572 git_cmd(), 'cat-file', '-t', $object_id) . ' 2> /dev/null'
Lea Wiemann074afaa2008-06-19 22:03:21 +02007573 or die_error(404, "Object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01007574 $type = <$fd>;
7575 chomp $type;
7576 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02007577 or die_error(404, "Object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01007578
7579 # - hash_base and file_name
7580 } elsif ($hash_base && defined $file_name) {
7581 $file_name =~ s,/+$,,;
7582
7583 system(git_cmd(), "cat-file", '-e', $hash_base) == 0
Lea Wiemann074afaa2008-06-19 22:03:21 +02007584 or die_error(404, "Base object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01007585
Stefano Lattarini41ccfdd2013-04-12 00:36:10 +02007586 # here errors should not happen
Jakub Narebskica946012006-12-10 13:25:47 +01007587 open my $fd, "-|", git_cmd(), "ls-tree", $hash_base, "--", $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02007588 or die_error(500, "Open git-ls-tree failed");
Jakub Narebskica946012006-12-10 13:25:47 +01007589 my $line = <$fd>;
7590 close $fd;
7591
7592 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
7593 unless ($line && $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007594 die_error(404, "File or directory for given base does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01007595 }
7596 $type = $2;
7597 $hash = $3;
7598 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007599 die_error(400, "Not enough information to find object");
Jakub Narebskica946012006-12-10 13:25:47 +01007600 }
7601
7602 print $cgi->redirect(-uri => href(action=>$type, -full=>1,
7603 hash=>$hash, hash_base=>$hash_base,
7604 file_name=>$file_name),
7605 -status => '302 Found');
7606}
7607
Kay Sievers09bd7892005-08-07 20:21:23 +02007608sub git_blobdiff {
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007609 my $format = shift || 'html';
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01007610 my $diff_style = $input_params{'diff_style'} || 'inline';
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007611
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007612 my $fd;
7613 my @difftree;
7614 my %diffinfo;
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007615 my $expires;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007616
7617 # preparing $fd and %diffinfo for git_patchset_body
7618 # new style URI
7619 if (defined $hash_base && defined $hash_parent_base) {
7620 if (defined $file_name) {
7621 # read raw output
Jakub Narebski45bd0c82006-10-30 22:29:06 +01007622 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
7623 $hash_parent_base, $hash_base,
Jakub Narebski5ae917a2007-03-30 23:41:26 +02007624 "--", (defined $file_parent ? $file_parent : ()), $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02007625 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007626 @difftree = map { chomp; $_ } <$fd>;
7627 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02007628 or die_error(404, "Reading git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007629 @difftree
Lea Wiemann074afaa2008-06-19 22:03:21 +02007630 or die_error(404, "Blob diff not found");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007631
Jakub Narebski0aea3372006-08-27 23:45:26 +02007632 } elsif (defined $hash &&
7633 $hash =~ /[0-9a-fA-F]{40}/) {
7634 # try to find filename from $hash
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007635
7636 # read filtered raw output
Jakub Narebski45bd0c82006-10-30 22:29:06 +01007637 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
7638 $hash_parent_base, $hash_base, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02007639 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007640 @difftree =
7641 # ':100644 100644 03b21826... 3b93d5e7... M ls-files.c'
7642 # $hash == to_id
7643 grep { /^:[0-7]{6} [0-7]{6} [0-9a-fA-F]{40} $hash/ }
7644 map { chomp; $_ } <$fd>;
7645 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02007646 or die_error(404, "Reading git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007647 @difftree
Lea Wiemann074afaa2008-06-19 22:03:21 +02007648 or die_error(404, "Blob diff not found");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007649
7650 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007651 die_error(400, "Missing one of the blob diff parameters");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007652 }
7653
7654 if (@difftree > 1) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007655 die_error(400, "Ambiguous blob diff specification");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007656 }
7657
7658 %diffinfo = parse_difftree_raw_line($difftree[0]);
Jakub Narebski9d301452007-11-01 12:38:08 +01007659 $file_parent ||= $diffinfo{'from_file'} || $file_name;
7660 $file_name ||= $diffinfo{'to_file'};
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007661
7662 $hash_parent ||= $diffinfo{'from_id'};
7663 $hash ||= $diffinfo{'to_id'};
7664
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007665 # non-textual hash id's can be cached
7666 if ($hash_base =~ m/^[0-9a-fA-F]{40}$/ &&
7667 $hash_parent_base =~ m/^[0-9a-fA-F]{40}$/) {
7668 $expires = '+1d';
7669 }
7670
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007671 # open patch output
Dennis Stosberg25691fb2006-08-28 17:49:58 +02007672 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebski957d6ea2007-04-05 13:45:41 +02007673 '-p', ($format eq 'html' ? "--full-index" : ()),
7674 $hash_parent_base, $hash_base,
Jakub Narebski5ae917a2007-03-30 23:41:26 +02007675 "--", (defined $file_parent ? $file_parent : ()), $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02007676 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007677 }
7678
Junio C Hamanob54dc9f2008-12-16 19:42:02 -08007679 # old/legacy style URI -- not generated anymore since 1.4.3.
7680 if (!%diffinfo) {
7681 die_error('404 Not Found', "Missing one of the blob diff parameters")
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007682 }
7683
7684 # header
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007685 if ($format eq 'html') {
7686 my $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01007687 $cgi->a({-href => href(action=>"blobdiff_plain", -replay=>1)},
Petr Baudis35329cc2006-09-22 03:19:50 +02007688 "raw");
Kato Kazuyoshi6ae683c2011-10-31 00:36:27 +01007689 $formats_nav .= diff_style_nav($diff_style);
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007690 git_header_html(undef, $expires);
7691 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
7692 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
7693 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
7694 } else {
7695 print "<div class=\"page_nav\"><br/>$formats_nav<br/></div>\n";
Jakub Narebski3017ed62010-12-15 00:34:01 +01007696 print "<div class=\"title\">".esc_html("$hash vs $hash_parent")."</div>\n";
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007697 }
7698 if (defined $file_name) {
7699 git_print_page_path($file_name, "blob", $hash_base);
7700 } else {
7701 print "<div class=\"page_path\"></div>\n";
7702 }
7703
7704 } elsif ($format eq 'plain') {
7705 print $cgi->header(
7706 -type => 'text/plain',
7707 -charset => 'utf-8',
7708 -expires => $expires,
Luben Tuikova2a3bf72006-09-28 16:51:43 -07007709 -content_disposition => 'inline; filename="' . "$file_name" . '.patch"');
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007710
7711 print "X-Git-Url: " . $cgi->self_url() . "\n\n";
7712
Kay Sievers09bd7892005-08-07 20:21:23 +02007713 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007714 die_error(400, "Unknown blobdiff format");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007715 }
7716
7717 # patch
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007718 if ($format eq 'html') {
7719 print "<div class=\"page_body\">\n";
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007720
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01007721 git_patchset_body($fd, $diff_style,
7722 [ \%diffinfo ], $hash_base, $hash_parent_base);
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007723 close $fd;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007724
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007725 print "</div>\n"; # class="page_body"
7726 git_footer_html();
7727
7728 } else {
7729 while (my $line = <$fd>) {
Jakub Narebski403d0902006-11-08 11:48:56 +01007730 $line =~ s!a/($hash|$hash_parent)!'a/'.esc_path($diffinfo{'from_file'})!eg;
7731 $line =~ s!b/($hash|$hash_parent)!'b/'.esc_path($diffinfo{'to_file'})!eg;
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007732
7733 print $line;
7734
7735 last if $line =~ m!^\+\+\+!;
7736 }
7737 local $/ = undef;
7738 print <$fd>;
7739 close $fd;
7740 }
Kay Sievers09bd7892005-08-07 20:21:23 +02007741}
7742
Kay Sievers19806692005-08-07 20:26:27 +02007743sub git_blobdiff_plain {
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007744 git_blobdiff('plain');
Kay Sievers19806692005-08-07 20:26:27 +02007745}
7746
Kato Kazuyoshi6ae683c2011-10-31 00:36:27 +01007747# assumes that it is added as later part of already existing navigation,
7748# so it returns "| foo | bar" rather than just "foo | bar"
7749sub diff_style_nav {
7750 my ($diff_style, $is_combined) = @_;
7751 $diff_style ||= 'inline';
7752
7753 return "" if ($is_combined);
7754
7755 my @styles = (inline => 'inline', 'sidebyside' => 'side by side');
7756 my %styles = @styles;
7757 @styles =
7758 @styles[ map { $_ * 2 } 0..$#styles/2 ];
7759
7760 return join '',
7761 map { " | ".$_ }
7762 map {
7763 $_ eq $diff_style ? $styles{$_} :
7764 $cgi->a({-href => href(-replay=>1, diff_style => $_)}, $styles{$_})
7765 } @styles;
7766}
7767
Kay Sievers09bd7892005-08-07 20:21:23 +02007768sub git_commitdiff {
Giuseppe Bilotta20209852008-12-18 08:13:17 +01007769 my %params = @_;
7770 my $format = $params{-format} || 'html';
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01007771 my $diff_style = $input_params{'diff_style'} || 'inline';
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01007772
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01007773 my ($patch_max) = gitweb_get_feature('patches');
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01007774 if ($format eq 'patch') {
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01007775 die_error(403, "Patch view not allowed") unless $patch_max;
7776 }
7777
Jakub Narebski9954f772006-11-18 23:35:41 +01007778 $hash ||= $hash_base || "HEAD";
Lea Wiemann074afaa2008-06-19 22:03:21 +02007779 my %co = parse_commit($hash)
7780 or die_error(404, "Unknown commit object");
Jakub Narebski151602d2006-10-23 00:37:56 +02007781
Jakub Narebskicd030c32007-06-08 13:33:28 +02007782 # choose format for commitdiff for merge
7783 if (! defined $hash_parent && @{$co{'parents'}} > 1) {
7784 $hash_parent = '--cc';
7785 }
7786 # we need to prepare $formats_nav before almost any parameter munging
Jakub Narebski151602d2006-10-23 00:37:56 +02007787 my $formats_nav;
7788 if ($format eq 'html') {
7789 $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01007790 $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
Jakub Narebski151602d2006-10-23 00:37:56 +02007791 "raw");
Jakub Narebski1655c982009-10-09 14:26:44 +02007792 if ($patch_max && @{$co{'parents'}} <= 1) {
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01007793 $formats_nav .= " | " .
7794 $cgi->a({-href => href(action=>"patch", -replay=>1)},
7795 "patch");
7796 }
Kato Kazuyoshi6ae683c2011-10-31 00:36:27 +01007797 $formats_nav .= diff_style_nav($diff_style, @{$co{'parents'}} > 1);
Jakub Narebski151602d2006-10-23 00:37:56 +02007798
Jakub Narebskicd030c32007-06-08 13:33:28 +02007799 if (defined $hash_parent &&
7800 $hash_parent ne '-c' && $hash_parent ne '--cc') {
Jakub Narebski151602d2006-10-23 00:37:56 +02007801 # commitdiff with two commits given
7802 my $hash_parent_short = $hash_parent;
7803 if ($hash_parent =~ m/^[0-9a-fA-F]{40}$/) {
7804 $hash_parent_short = substr($hash_parent, 0, 7);
7805 }
7806 $formats_nav .=
Jakub Narebskiada3e1f2007-06-08 13:26:31 +02007807 ' (from';
7808 for (my $i = 0; $i < @{$co{'parents'}}; $i++) {
7809 if ($co{'parents'}[$i] eq $hash_parent) {
7810 $formats_nav .= ' parent ' . ($i+1);
7811 last;
7812 }
7813 }
7814 $formats_nav .= ': ' .
Jakub Narebskid0e6e292011-10-31 00:36:26 +01007815 $cgi->a({-href => href(-replay=>1,
7816 hash=>$hash_parent, hash_base=>undef)},
Jakub Narebski151602d2006-10-23 00:37:56 +02007817 esc_html($hash_parent_short)) .
7818 ')';
7819 } elsif (!$co{'parent'}) {
7820 # --root commitdiff
7821 $formats_nav .= ' (initial)';
7822 } elsif (scalar @{$co{'parents'}} == 1) {
7823 # single parent commit
7824 $formats_nav .=
7825 ' (parent: ' .
Jakub Narebskid0e6e292011-10-31 00:36:26 +01007826 $cgi->a({-href => href(-replay=>1,
7827 hash=>$co{'parent'}, hash_base=>undef)},
Jakub Narebski151602d2006-10-23 00:37:56 +02007828 esc_html(substr($co{'parent'}, 0, 7))) .
7829 ')';
7830 } else {
7831 # merge commit
Jakub Narebskicd030c32007-06-08 13:33:28 +02007832 if ($hash_parent eq '--cc') {
7833 $formats_nav .= ' | ' .
Jakub Narebskid0e6e292011-10-31 00:36:26 +01007834 $cgi->a({-href => href(-replay=>1,
Jakub Narebskicd030c32007-06-08 13:33:28 +02007835 hash=>$hash, hash_parent=>'-c')},
7836 'combined');
7837 } else { # $hash_parent eq '-c'
7838 $formats_nav .= ' | ' .
Jakub Narebskid0e6e292011-10-31 00:36:26 +01007839 $cgi->a({-href => href(-replay=>1,
Jakub Narebskicd030c32007-06-08 13:33:28 +02007840 hash=>$hash, hash_parent=>'--cc')},
7841 'compact');
7842 }
Jakub Narebski151602d2006-10-23 00:37:56 +02007843 $formats_nav .=
7844 ' (merge: ' .
7845 join(' ', map {
Jakub Narebskid0e6e292011-10-31 00:36:26 +01007846 $cgi->a({-href => href(-replay=>1,
7847 hash=>$_, hash_base=>undef)},
Jakub Narebski151602d2006-10-23 00:37:56 +02007848 esc_html(substr($_, 0, 7)));
7849 } @{$co{'parents'}} ) .
7850 ')';
7851 }
7852 }
7853
Jakub Narebskifb1dde42007-05-07 01:10:07 +02007854 my $hash_parent_param = $hash_parent;
Jakub Narebskicd030c32007-06-08 13:33:28 +02007855 if (!defined $hash_parent_param) {
7856 # --cc for multiple parents, --root for parentless
Jakub Narebskifb1dde42007-05-07 01:10:07 +02007857 $hash_parent_param =
Jakub Narebskicd030c32007-06-08 13:33:28 +02007858 @{$co{'parents'}} > 1 ? '--cc' : $co{'parent'} || '--root';
Kay Sieversbddec012005-08-07 20:25:42 +02007859 }
Jakub Narebskieee08902006-08-24 00:15:14 +02007860
7861 # read commitdiff
7862 my $fd;
7863 my @difftree;
Jakub Narebskieee08902006-08-24 00:15:14 +02007864 if ($format eq 'html') {
Dennis Stosberg25691fb2006-08-28 17:49:58 +02007865 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebski45bd0c82006-10-30 22:29:06 +01007866 "--no-commit-id", "--patch-with-raw", "--full-index",
Jakub Narebskifb1dde42007-05-07 01:10:07 +02007867 $hash_parent_param, $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02007868 or die_error(500, "Open git-diff-tree failed");
Jakub Narebskieee08902006-08-24 00:15:14 +02007869
Jakub Narebski04408c32006-11-18 23:35:38 +01007870 while (my $line = <$fd>) {
7871 chomp $line;
Jakub Narebskieee08902006-08-24 00:15:14 +02007872 # empty line ends raw part of diff-tree output
7873 last unless $line;
Jakub Narebski493e01d2007-05-07 01:10:06 +02007874 push @difftree, scalar parse_difftree_raw_line($line);
Jakub Narebskieee08902006-08-24 00:15:14 +02007875 }
Jakub Narebskieee08902006-08-24 00:15:14 +02007876
Jakub Narebskieee08902006-08-24 00:15:14 +02007877 } elsif ($format eq 'plain') {
Dennis Stosberg25691fb2006-08-28 17:49:58 +02007878 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebskifb1dde42007-05-07 01:10:07 +02007879 '-p', $hash_parent_param, $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02007880 or die_error(500, "Open git-diff-tree failed");
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01007881 } elsif ($format eq 'patch') {
7882 # For commit ranges, we limit the output to the number of
7883 # patches specified in the 'patches' feature.
7884 # For single commits, we limit the output to a single patch,
7885 # diverging from the git-format-patch default.
7886 my @commit_spec = ();
7887 if ($hash_parent) {
7888 if ($patch_max > 0) {
7889 push @commit_spec, "-$patch_max";
7890 }
7891 push @commit_spec, '-n', "$hash_parent..$hash";
7892 } else {
Giuseppe Bilottaa3411f82008-12-18 08:13:18 +01007893 if ($params{-single}) {
7894 push @commit_spec, '-1';
7895 } else {
7896 if ($patch_max > 0) {
7897 push @commit_spec, "-$patch_max";
7898 }
7899 push @commit_spec, "-n";
7900 }
7901 push @commit_spec, '--root', $hash;
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01007902 }
Pavan Kumar Sunkara04794fd2010-05-10 18:41:35 +02007903 open $fd, "-|", git_cmd(), "format-patch", @diff_opts,
7904 '--encoding=utf8', '--stdout', @commit_spec
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01007905 or die_error(500, "Open git-format-patch failed");
Jakub Narebskieee08902006-08-24 00:15:14 +02007906 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007907 die_error(400, "Unknown commitdiff format");
Jakub Narebskieee08902006-08-24 00:15:14 +02007908 }
Kay Sievers4c02e3c2005-08-07 19:52:52 +02007909
Kay Sievers11044292005-10-19 03:18:45 +02007910 # non-textual hash id's can be cached
7911 my $expires;
7912 if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
7913 $expires = "+1d";
7914 }
Kay Sievers09bd7892005-08-07 20:21:23 +02007915
Jakub Narebskieee08902006-08-24 00:15:14 +02007916 # write commit message
7917 if ($format eq 'html') {
7918 my $refs = git_get_references();
7919 my $ref = format_ref_marker($refs, $co{'id'});
Kay Sievers19806692005-08-07 20:26:27 +02007920
Jakub Narebskieee08902006-08-24 00:15:14 +02007921 git_header_html(undef, $expires);
7922 git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
7923 git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
Giuseppe Bilottaf88bafa2009-06-30 00:00:49 +02007924 print "<div class=\"title_text\">\n" .
7925 "<table class=\"object_header\">\n";
7926 git_print_authorship_rows(\%co);
7927 print "</table>".
7928 "</div>\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02007929 print "<div class=\"page_body\">\n";
Jakub Narebski82560982006-10-24 13:55:33 +02007930 if (@{$co{'comment'}} > 1) {
7931 print "<div class=\"log\">\n";
7932 git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1);
7933 print "</div>\n"; # class="log"
7934 }
Kay Sievers1b1cd422005-08-07 20:28:01 +02007935
Jakub Narebskieee08902006-08-24 00:15:14 +02007936 } elsif ($format eq 'plain') {
7937 my $refs = git_get_references("tags");
Jakub Narebskiedf735a2006-08-24 19:45:30 +02007938 my $tagname = git_get_rev_name_tags($hash);
Jakub Narebskieee08902006-08-24 00:15:14 +02007939 my $filename = basename($project) . "-$hash.patch";
7940
7941 print $cgi->header(
7942 -type => 'text/plain',
7943 -charset => 'utf-8',
7944 -expires => $expires,
Luben Tuikova2a3bf72006-09-28 16:51:43 -07007945 -content_disposition => 'inline; filename="' . "$filename" . '"');
Jakub Narebskieee08902006-08-24 00:15:14 +02007946 my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
Yasushi SHOJI77202242008-01-29 21:16:02 +09007947 print "From: " . to_utf8($co{'author'}) . "\n";
7948 print "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n";
7949 print "Subject: " . to_utf8($co{'title'}) . "\n";
7950
Jakub Narebskiedf735a2006-08-24 19:45:30 +02007951 print "X-Git-Tag: $tagname\n" if $tagname;
Jakub Narebskieee08902006-08-24 00:15:14 +02007952 print "X-Git-Url: " . $cgi->self_url() . "\n\n";
Jakub Narebskiedf735a2006-08-24 19:45:30 +02007953
Jakub Narebskieee08902006-08-24 00:15:14 +02007954 foreach my $line (@{$co{'comment'}}) {
Yasushi SHOJI77202242008-01-29 21:16:02 +09007955 print to_utf8($line) . "\n";
Kay Sievers19806692005-08-07 20:26:27 +02007956 }
Jakub Narebskieee08902006-08-24 00:15:14 +02007957 print "---\n\n";
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01007958 } elsif ($format eq 'patch') {
7959 my $filename = basename($project) . "-$hash.patch";
7960
7961 print $cgi->header(
7962 -type => 'text/plain',
7963 -charset => 'utf-8',
7964 -expires => $expires,
7965 -content_disposition => 'inline; filename="' . "$filename" . '"');
Kay Sievers19806692005-08-07 20:26:27 +02007966 }
Jakub Narebskieee08902006-08-24 00:15:14 +02007967
7968 # write patch
7969 if ($format eq 'html') {
Jakub Narebskicd030c32007-06-08 13:33:28 +02007970 my $use_parents = !defined $hash_parent ||
7971 $hash_parent eq '-c' || $hash_parent eq '--cc';
7972 git_difftree_body(\@difftree, $hash,
7973 $use_parents ? @{$co{'parents'}} : $hash_parent);
Jakub Narebskib4657e72006-08-28 14:48:14 +02007974 print "<br/>\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02007975
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01007976 git_patchset_body($fd, $diff_style,
7977 \@difftree, $hash,
Jakub Narebskicd030c32007-06-08 13:33:28 +02007978 $use_parents ? @{$co{'parents'}} : $hash_parent);
Jakub Narebski157e43b2006-08-24 19:34:36 +02007979 close $fd;
Jakub Narebskieee08902006-08-24 00:15:14 +02007980 print "</div>\n"; # class="page_body"
7981 git_footer_html();
7982
7983 } elsif ($format eq 'plain') {
7984 local $/ = undef;
7985 print <$fd>;
7986 close $fd
7987 or print "Reading git-diff-tree failed\n";
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01007988 } elsif ($format eq 'patch') {
7989 local $/ = undef;
7990 print <$fd>;
7991 close $fd
7992 or print "Reading git-format-patch failed\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02007993 }
7994}
7995
7996sub git_commitdiff_plain {
Giuseppe Bilotta20209852008-12-18 08:13:17 +01007997 git_commitdiff(-format => 'plain');
Kay Sievers19806692005-08-07 20:26:27 +02007998}
7999
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01008000# format-patch-style patches
8001sub git_patch {
Jakub Narebski1655c982009-10-09 14:26:44 +02008002 git_commitdiff(-format => 'patch', -single => 1);
Giuseppe Bilottaa3411f82008-12-18 08:13:18 +01008003}
8004
8005sub git_patches {
Giuseppe Bilotta20209852008-12-18 08:13:17 +01008006 git_commitdiff(-format => 'patch');
Kay Sievers09bd7892005-08-07 20:21:23 +02008007}
8008
8009sub git_history {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01008010 git_log_generic('history', \&git_history_body,
8011 $hash_base, $hash_parent_base,
8012 $file_name, $hash);
Kay Sievers161332a2005-08-07 19:49:46 +02008013}
Kay Sievers19806692005-08-07 20:26:27 +02008014
8015sub git_search {
Jakub Narebskie0ca3642011-06-22 17:28:52 +02008016 $searchtype ||= 'commit';
8017
8018 # check if appropriate features are enabled
8019 gitweb_check_feature('search')
8020 or die_error(403, "Search is disabled");
8021 if ($searchtype eq 'pickaxe') {
8022 # pickaxe may take all resources of your box and run for several minutes
8023 # with every query - so decide by yourself how public you make this feature
8024 gitweb_check_feature('pickaxe')
8025 or die_error(403, "Pickaxe search is disabled");
8026 }
8027 if ($searchtype eq 'grep') {
8028 # grep search might be potentially CPU-intensive, too
8029 gitweb_check_feature('grep')
8030 or die_error(403, "Grep search is disabled");
8031 }
8032
Kay Sievers19806692005-08-07 20:26:27 +02008033 if (!defined $searchtext) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02008034 die_error(400, "Text field is empty");
Kay Sievers19806692005-08-07 20:26:27 +02008035 }
8036 if (!defined $hash) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02008037 $hash = git_get_head_hash($project);
Kay Sievers19806692005-08-07 20:26:27 +02008038 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02008039 my %co = parse_commit($hash);
Kay Sievers19806692005-08-07 20:26:27 +02008040 if (!%co) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02008041 die_error(404, "Unknown commit object");
Kay Sievers19806692005-08-07 20:26:27 +02008042 }
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00008043 if (!defined $page) {
8044 $page = 0;
8045 }
Jakub Narebski04f7a942006-09-11 00:29:27 +02008046
Jakub Narebski16f20722011-06-22 17:28:53 +02008047 if ($searchtype eq 'commit' ||
8048 $searchtype eq 'author' ||
8049 $searchtype eq 'committer') {
8050 git_search_message(%co);
8051 } elsif ($searchtype eq 'pickaxe') {
8052 git_search_changes(%co);
8053 } elsif ($searchtype eq 'grep') {
8054 git_search_files(%co);
Jakub Narebski1ae05be2011-06-22 17:28:55 +02008055 } else {
8056 die_error(400, "Unknown search type");
Kay Sieversc994d622005-08-07 20:27:18 +02008057 }
Kay Sievers19806692005-08-07 20:26:27 +02008058}
8059
Petr Baudis88ad7292006-10-24 05:15:46 +02008060sub git_search_help {
8061 git_header_html();
8062 git_print_page_nav('','', $hash,$hash,$hash);
8063 print <<EOT;
Petr Baudis0e559912008-02-26 13:22:08 +01008064<p><strong>Pattern</strong> is by default a normal string that is matched precisely (but without
8065regard to case, except in the case of pickaxe). However, when you check the <em>re</em> checkbox,
8066the pattern entered is recognized as the POSIX extended
8067<a href="http://en.wikipedia.org/wiki/Regular_expression">regular expression</a> (also case
8068insensitive).</p>
Petr Baudis88ad7292006-10-24 05:15:46 +02008069<dl>
8070<dt><b>commit</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01008071<dd>The commit messages and authorship information will be scanned for the given pattern.</dd>
Petr Baudise7738552007-05-17 04:31:12 +02008072EOT
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08008073 my $have_grep = gitweb_check_feature('grep');
Petr Baudise7738552007-05-17 04:31:12 +02008074 if ($have_grep) {
8075 print <<EOT;
8076<dt><b>grep</b></dt>
8077<dd>All files in the currently selected tree (HEAD unless you are explicitly browsing
Petr Baudis0e559912008-02-26 13:22:08 +01008078 a different one) are searched for the given pattern. On large trees, this search can take
8079a while and put some strain on the server, so please use it with some consideration. Note that
8080due to git-grep peculiarity, currently if regexp mode is turned off, the matches are
8081case-sensitive.</dd>
Petr Baudise7738552007-05-17 04:31:12 +02008082EOT
8083 }
8084 print <<EOT;
Petr Baudis88ad7292006-10-24 05:15:46 +02008085<dt><b>author</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01008086<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 +02008087<dt><b>committer</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01008088<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 +02008089EOT
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08008090 my $have_pickaxe = gitweb_check_feature('pickaxe');
Petr Baudis88ad7292006-10-24 05:15:46 +02008091 if ($have_pickaxe) {
8092 print <<EOT;
8093<dt><b>pickaxe</b></dt>
8094<dd>All commits that caused the string to appear or disappear from any file (changes that
8095added, removed or "modified" the string) will be listed. This search can take a while and
Petr Baudis0e559912008-02-26 13:22:08 +01008096takes a lot of strain on the server, so please use it wisely. Note that since you may be
8097interested even in changes just changing the case as well, this search is case sensitive.</dd>
Petr Baudis88ad7292006-10-24 05:15:46 +02008098EOT
8099 }
8100 print "</dl>\n";
8101 git_footer_html();
8102}
8103
Kay Sievers19806692005-08-07 20:26:27 +02008104sub git_shortlog {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01008105 git_log_generic('shortlog', \&git_shortlog_body,
8106 $hash, $hash_parent);
Kay Sievers19806692005-08-07 20:26:27 +02008107}
Jakub Narebski717b8312006-07-31 21:22:15 +02008108
8109## ......................................................................
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008110## feeds (RSS, Atom; OPML)
Jakub Narebski717b8312006-07-31 21:22:15 +02008111
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008112sub git_feed {
8113 my $format = shift || 'atom';
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08008114 my $have_blame = gitweb_check_feature('blame');
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008115
8116 # Atom: http://www.atomenabled.org/developers/syndication/
8117 # RSS: http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
8118 if ($format ne 'rss' && $format ne 'atom') {
Lea Wiemann074afaa2008-06-19 22:03:21 +02008119 die_error(400, "Unknown web feed format");
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008120 }
8121
8122 # log/feed of current (HEAD) branch, log of given branch, history of file/directory
8123 my $head = $hash || 'HEAD';
Jakub Narebski311e5522008-02-26 13:22:06 +01008124 my @commitlist = parse_commits($head, 150, 0, $file_name);
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008125
8126 my %latest_commit;
8127 my %latest_date;
8128 my $content_type = "application/$format+xml";
8129 if (defined $cgi->http('HTTP_ACCEPT') &&
8130 $cgi->Accept('text/xml') > $cgi->Accept($content_type)) {
8131 # browser (feed reader) prefers text/xml
8132 $content_type = 'text/xml';
8133 }
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00008134 if (defined($commitlist[0])) {
8135 %latest_commit = %{$commitlist[0]};
Giuseppe Bilottacd956c72009-01-26 12:50:16 +01008136 my $latest_epoch = $latest_commit{'committer_epoch'};
W. Trevor Kingb7d565e2012-03-29 08:45:48 -04008137 exit_if_unmodified_since($latest_epoch);
Dylan Alex Simondebf29d2012-10-11 16:40:35 -04008138 %latest_date = parse_date($latest_epoch, $latest_commit{'committer_tz'});
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008139 }
W. Trevor Kingb7d565e2012-03-29 08:45:48 -04008140 print $cgi->header(
8141 -type => $content_type,
8142 -charset => 'utf-8',
8143 %latest_date ? (-last_modified => $latest_date{'rfc2822'}) : (),
8144 -status => '200 OK');
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008145
8146 # Optimization: skip generating the body if client asks only
8147 # for Last-Modified date.
8148 return if ($cgi->request_method() eq 'HEAD');
8149
8150 # header variables
8151 my $title = "$site_name - $project/$action";
8152 my $feed_type = 'log';
8153 if (defined $hash) {
8154 $title .= " - '$hash'";
8155 $feed_type = 'branch log';
8156 if (defined $file_name) {
8157 $title .= " :: $file_name";
8158 $feed_type = 'history';
8159 }
8160 } elsif (defined $file_name) {
8161 $title .= " - $file_name";
8162 $feed_type = 'history';
8163 }
8164 $title .= " $feed_type";
Jeff King0f0ecf62012-11-12 16:34:28 -05008165 $title = esc_html($title);
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008166 my $descr = git_get_project_description($project);
8167 if (defined $descr) {
8168 $descr = esc_html($descr);
8169 } else {
8170 $descr = "$project " .
8171 ($format eq 'rss' ? 'RSS' : 'Atom') .
8172 " feed";
8173 }
8174 my $owner = git_get_project_owner($project);
8175 $owner = esc_html($owner);
8176
8177 #header
8178 my $alt_url;
8179 if (defined $file_name) {
8180 $alt_url = href(-full=>1, action=>"history", hash=>$hash, file_name=>$file_name);
8181 } elsif (defined $hash) {
8182 $alt_url = href(-full=>1, action=>"log", hash=>$hash);
8183 } else {
8184 $alt_url = href(-full=>1, action=>"summary");
8185 }
8186 print qq!<?xml version="1.0" encoding="utf-8"?>\n!;
8187 if ($format eq 'rss') {
8188 print <<XML;
Jakub Narebski59b9f612006-08-22 23:42:53 +02008189<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
8190<channel>
Jakub Narebski59b9f612006-08-22 23:42:53 +02008191XML
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008192 print "<title>$title</title>\n" .
8193 "<link>$alt_url</link>\n" .
8194 "<description>$descr</description>\n" .
Giuseppe Bilotta3ac109a2009-01-26 12:50:13 +01008195 "<language>en</language>\n" .
8196 # project owner is responsible for 'editorial' content
8197 "<managingEditor>$owner</managingEditor>\n";
Giuseppe Bilotta1ba68ce2009-01-26 12:50:11 +01008198 if (defined $logo || defined $favicon) {
8199 # prefer the logo to the favicon, since RSS
8200 # doesn't allow both
8201 my $img = esc_url($logo || $favicon);
8202 print "<image>\n" .
8203 "<url>$img</url>\n" .
8204 "<title>$title</title>\n" .
8205 "<link>$alt_url</link>\n" .
8206 "</image>\n";
8207 }
Giuseppe Bilotta0cf31282009-01-26 12:50:14 +01008208 if (%latest_date) {
8209 print "<pubDate>$latest_date{'rfc2822'}</pubDate>\n";
8210 print "<lastBuildDate>$latest_date{'rfc2822'}</lastBuildDate>\n";
8211 }
Giuseppe Bilottaad59a7a2009-01-26 12:50:12 +01008212 print "<generator>gitweb v.$version/$git_version</generator>\n";
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008213 } elsif ($format eq 'atom') {
8214 print <<XML;
8215<feed xmlns="http://www.w3.org/2005/Atom">
8216XML
8217 print "<title>$title</title>\n" .
8218 "<subtitle>$descr</subtitle>\n" .
8219 '<link rel="alternate" type="text/html" href="' .
8220 $alt_url . '" />' . "\n" .
8221 '<link rel="self" type="' . $content_type . '" href="' .
8222 $cgi->self_url() . '" />' . "\n" .
8223 "<id>" . href(-full=>1) . "</id>\n" .
8224 # use project owner for feed author
8225 "<author><name>$owner</name></author>\n";
8226 if (defined $favicon) {
8227 print "<icon>" . esc_url($favicon) . "</icon>\n";
8228 }
Jonathan Nieder9d9f5e72010-09-03 19:44:39 -05008229 if (defined $logo) {
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008230 # not twice as wide as tall: 72 x 27 pixels
Jakub Narebskie1147262006-12-04 14:09:43 +01008231 print "<logo>" . esc_url($logo) . "</logo>\n";
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008232 }
8233 if (! %latest_date) {
8234 # dummy date to keep the feed valid until commits trickle in:
8235 print "<updated>1970-01-01T00:00:00Z</updated>\n";
8236 } else {
8237 print "<updated>$latest_date{'iso-8601'}</updated>\n";
8238 }
Giuseppe Bilottaad59a7a2009-01-26 12:50:12 +01008239 print "<generator version='$version/$git_version'>gitweb</generator>\n";
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008240 }
Jakub Narebski717b8312006-07-31 21:22:15 +02008241
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008242 # contents
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00008243 for (my $i = 0; $i <= $#commitlist; $i++) {
8244 my %co = %{$commitlist[$i]};
8245 my $commit = $co{'id'};
Jakub Narebski717b8312006-07-31 21:22:15 +02008246 # we read 150, we always show 30 and the ones more recent than 48 hours
Jakub Narebski91fd2bf2006-11-25 15:54:34 +01008247 if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02008248 last;
8249 }
Jakub Narebski6368d9f2011-03-19 23:53:55 +01008250 my %cd = parse_date($co{'author_epoch'}, $co{'author_tz'});
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008251
8252 # get list of changed files
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00008253 open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebskic906b182007-05-19 02:47:51 +02008254 $co{'parent'} || "--root",
8255 $co{'id'}, "--", (defined $file_name ? $file_name : ())
Jakub Narebski6bcf4b42006-08-27 23:49:36 +02008256 or next;
Jakub Narebski717b8312006-07-31 21:22:15 +02008257 my @difftree = map { chomp; $_ } <$fd>;
Jakub Narebski6bcf4b42006-08-27 23:49:36 +02008258 close $fd
8259 or next;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008260
8261 # print element (entry, item)
Florian La Rochee62a6412008-02-03 12:38:46 +01008262 my $co_url = href(-full=>1, action=>"commitdiff", hash=>$commit);
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008263 if ($format eq 'rss') {
8264 print "<item>\n" .
8265 "<title>" . esc_html($co{'title'}) . "</title>\n" .
8266 "<author>" . esc_html($co{'author'}) . "</author>\n" .
8267 "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
8268 "<guid isPermaLink=\"true\">$co_url</guid>\n" .
8269 "<link>$co_url</link>\n" .
8270 "<description>" . esc_html($co{'title'}) . "</description>\n" .
8271 "<content:encoded>" .
8272 "<![CDATA[\n";
8273 } elsif ($format eq 'atom') {
8274 print "<entry>\n" .
8275 "<title type=\"html\">" . esc_html($co{'title'}) . "</title>\n" .
8276 "<updated>$cd{'iso-8601'}</updated>\n" .
Jakub Narebskiab23c192006-11-25 15:54:33 +01008277 "<author>\n" .
8278 " <name>" . esc_html($co{'author_name'}) . "</name>\n";
8279 if ($co{'author_email'}) {
8280 print " <email>" . esc_html($co{'author_email'}) . "</email>\n";
8281 }
8282 print "</author>\n" .
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008283 # use committer for contributor
Jakub Narebskiab23c192006-11-25 15:54:33 +01008284 "<contributor>\n" .
8285 " <name>" . esc_html($co{'committer_name'}) . "</name>\n";
8286 if ($co{'committer_email'}) {
8287 print " <email>" . esc_html($co{'committer_email'}) . "</email>\n";
8288 }
8289 print "</contributor>\n" .
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008290 "<published>$cd{'iso-8601'}</published>\n" .
8291 "<link rel=\"alternate\" type=\"text/html\" href=\"$co_url\" />\n" .
8292 "<id>$co_url</id>\n" .
8293 "<content type=\"xhtml\" xml:base=\"" . esc_url($my_url) . "\">\n" .
8294 "<div xmlns=\"http://www.w3.org/1999/xhtml\">\n";
8295 }
Jakub Narebski717b8312006-07-31 21:22:15 +02008296 my $comment = $co{'comment'};
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008297 print "<pre>\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02008298 foreach my $line (@$comment) {
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008299 $line = esc_html($line);
8300 print "$line\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02008301 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008302 print "</pre><ul>\n";
8303 foreach my $difftree_line (@difftree) {
8304 my %difftree = parse_difftree_raw_line($difftree_line);
8305 next if !$difftree{'from_id'};
8306
8307 my $file = $difftree{'file'} || $difftree{'to_file'};
8308
8309 print "<li>" .
8310 "[" .
8311 $cgi->a({-href => href(-full=>1, action=>"blobdiff",
8312 hash=>$difftree{'to_id'}, hash_parent=>$difftree{'from_id'},
8313 hash_base=>$co{'id'}, hash_parent_base=>$co{'parent'},
8314 file_name=>$file, file_parent=>$difftree{'from_file'}),
8315 -title => "diff"}, 'D');
8316 if ($have_blame) {
8317 print $cgi->a({-href => href(-full=>1, action=>"blame",
8318 file_name=>$file, hash_base=>$commit),
8319 -title => "blame"}, 'B');
Jakub Narebski717b8312006-07-31 21:22:15 +02008320 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008321 # if this is not a feed of a file history
8322 if (!defined $file_name || $file_name ne $file) {
8323 print $cgi->a({-href => href(-full=>1, action=>"history",
8324 file_name=>$file, hash=>$commit),
8325 -title => "history"}, 'H');
8326 }
8327 $file = esc_path($file);
8328 print "] ".
8329 "$file</li>\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02008330 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008331 if ($format eq 'rss') {
8332 print "</ul>]]>\n" .
8333 "</content:encoded>\n" .
8334 "</item>\n";
8335 } elsif ($format eq 'atom') {
8336 print "</ul>\n</div>\n" .
8337 "</content>\n" .
8338 "</entry>\n";
8339 }
Jakub Narebski717b8312006-07-31 21:22:15 +02008340 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008341
8342 # end of feed
8343 if ($format eq 'rss') {
8344 print "</channel>\n</rss>\n";
Jakub Narebski3278fbc2009-05-11 19:37:28 +02008345 } elsif ($format eq 'atom') {
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008346 print "</feed>\n";
8347 }
8348}
8349
8350sub git_rss {
8351 git_feed('rss');
8352}
8353
8354sub git_atom {
8355 git_feed('atom');
Jakub Narebski717b8312006-07-31 21:22:15 +02008356}
8357
8358sub git_opml {
Bernhard R. Link19d2d232012-01-30 21:07:37 +01008359 my @list = git_get_projects_list($project_filter, $strict_export);
Jakub Narebski12b14432011-04-29 19:51:56 +02008360 if (!@list) {
8361 die_error(404, "No projects found");
8362 }
Jakub Narebski717b8312006-07-31 21:22:15 +02008363
Giuseppe Bilottaae357852009-01-02 13:49:30 +01008364 print $cgi->header(
8365 -type => 'text/xml',
8366 -charset => 'utf-8',
8367 -content_disposition => 'inline; filename="opml.xml"');
8368
Jürgen Kreileder5d791052011-12-17 10:22:22 +01008369 my $title = esc_html($site_name);
Bernhard R. Link19d2d232012-01-30 21:07:37 +01008370 my $filter = " within subdirectory ";
8371 if (defined $project_filter) {
8372 $filter .= esc_html($project_filter);
8373 } else {
8374 $filter = "";
8375 }
Jakub Narebski59b9f612006-08-22 23:42:53 +02008376 print <<XML;
8377<?xml version="1.0" encoding="utf-8"?>
8378<opml version="1.0">
8379<head>
Bernhard R. Link19d2d232012-01-30 21:07:37 +01008380 <title>$title OPML Export$filter</title>
Jakub Narebski59b9f612006-08-22 23:42:53 +02008381</head>
8382<body>
8383<outline text="git RSS feeds">
8384XML
Jakub Narebski717b8312006-07-31 21:22:15 +02008385
8386 foreach my $pr (@list) {
8387 my %proj = %$pr;
Jakub Narebski847e01f2006-08-14 02:05:47 +02008388 my $head = git_get_head_hash($proj{'path'});
Jakub Narebski717b8312006-07-31 21:22:15 +02008389 if (!defined $head) {
8390 next;
8391 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02008392 $git_dir = "$projectroot/$proj{'path'}";
Jakub Narebski847e01f2006-08-14 02:05:47 +02008393 my %co = parse_commit($head);
Jakub Narebski717b8312006-07-31 21:22:15 +02008394 if (!%co) {
8395 next;
8396 }
8397
8398 my $path = esc_html(chop_str($proj{'path'}, 25, 5));
Giuseppe Bilottadf63fbb2009-01-02 13:15:28 +01008399 my $rss = href('project' => $proj{'path'}, 'action' => 'rss', -full => 1);
8400 my $html = href('project' => $proj{'path'}, 'action' => 'summary', -full => 1);
Jakub Narebski717b8312006-07-31 21:22:15 +02008401 print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";
8402 }
Jakub Narebski59b9f612006-08-22 23:42:53 +02008403 print <<XML;
8404</outline>
8405</body>
8406</opml>
8407XML
Jakub Narebski717b8312006-07-31 21:22:15 +02008408}