blob: d5b3f04e118d1b86bf009a163bc9e7ade060b017 [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) {
57 if ($my_url =~ s,\Q$path_info\E$,, &&
58 $my_uri =~ s,\Q$path_info\E$,, &&
59 defined $ENV{'SCRIPT_NAME'}) {
60 $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
61 }
Giuseppe Bilotta81d3fe92009-02-15 10:18:36 +010062 }
Jakub Narebskic2394fe2010-05-07 14:54:04 +020063
64 # target of the home link on top of all pages
65 our $home_link = $my_uri || "/";
Giuseppe Bilottab65910f2008-09-29 15:07:42 +020066}
67
Alp Tokere130dda2006-07-12 23:55:10 +010068# core git executable to use
69# this can just be "git" if your webserver has a sensible PATH
Junio C Hamano06c084d2006-08-02 13:50:20 -070070our $GIT = "++GIT_BINDIR++/git";
Jakub Narebski3f7f27102006-06-21 09:48:04 +020071
Kay Sieversb87d78d2005-08-07 20:21:04 +020072# absolute fs-path which will be prepended to the project path
Dennis Stosberg4a87b432006-06-21 15:07:08 +020073#our $projectroot = "/pub/scm";
Junio C Hamano06c084d2006-08-02 13:50:20 -070074our $projectroot = "++GITWEB_PROJECTROOT++";
Kay Sieversb87d78d2005-08-07 20:21:04 +020075
Luke Luca5e9492007-10-16 20:45:25 -070076# fs traversing limit for getting project list
77# the number is relative to the projectroot
78our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++";
79
Yasushi SHOJI2de21fa2006-08-15 07:50:49 +090080# string of the home link on top of all pages
81our $home_link_str = "++GITWEB_HOME_LINK_STR++";
82
Alp Toker49da1da2006-07-11 21:10:26 +010083# name of your site or organization to appear in page titles
84# replace this with something more descriptive for clearer bookmarks
Petr Baudis8be28902006-10-24 05:18:39 +020085our $site_name = "++GITWEB_SITENAME++"
86 || ($ENV{'SERVER_NAME'} || "Untitled") . " Git";
Alp Toker49da1da2006-07-11 21:10:26 +010087
Lénaïc Huardc1355b72011-10-21 09:09:29 +020088# html snippet to include in the <head> section of each page
89our $site_html_head_string = "++GITWEB_SITE_HTML_HEAD_STRING++";
Alan Chandlerb2d34762006-10-03 13:49:03 +010090# filename of html text to include at top of each page
91our $site_header = "++GITWEB_SITE_HEADER++";
Kay Sievers8ab1da22005-08-07 20:22:53 +020092# html text to include at home page
Junio C Hamano06c084d2006-08-02 13:50:20 -070093our $home_text = "++GITWEB_HOMETEXT++";
Alan Chandlerb2d34762006-10-03 13:49:03 +010094# filename of html text to include at bottom of each page
95our $site_footer = "++GITWEB_SITE_FOOTER++";
Kay Sievers8ab1da22005-08-07 20:22:53 +020096
Alan Chandlerb2d34762006-10-03 13:49:03 +010097# URI of stylesheets
98our @stylesheets = ("++GITWEB_CSS++");
Petr Baudis887a6122006-10-26 14:41:25 +020099# URI of a single stylesheet, which can be overridden in GITWEB_CONFIG.
100our $stylesheet = undef;
Jakub Narebski9a7a62f2006-10-06 12:31:05 +0200101# URI of GIT logo (72x27 size)
Junio C Hamano06c084d2006-08-02 13:50:20 -0700102our $logo = "++GITWEB_LOGO++";
Jakub Narebski0b5deba2006-09-04 20:32:13 +0200103# URI of GIT favicon, assumed to be image/png type
104our $favicon = "++GITWEB_FAVICON++";
Jakub Narebski4af819d2009-09-01 13:39:17 +0200105# URI of gitweb.js (JavaScript code for gitweb)
106our $javascript = "++GITWEB_JS++";
Jakub Narebskiaedd9422006-06-17 11:23:56 +0200107
Jakub Narebski9a7a62f2006-10-06 12:31:05 +0200108# URI and label (title) of GIT logo link
109#our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/";
110#our $logo_label = "git documentation";
Wincent Colaiuta69fb8282009-07-12 14:31:28 +0200111our $logo_url = "http://git-scm.com/";
Jakub Narebski9a7a62f2006-10-06 12:31:05 +0200112our $logo_label = "git homepage";
Junio C Hamano51a7c662006-09-23 12:36:01 -0700113
Kay Sievers09bd7892005-08-07 20:21:23 +0200114# source of projects list
Junio C Hamano06c084d2006-08-02 13:50:20 -0700115our $projects_list = "++GITWEB_LIST++";
Kay Sieversb87d78d2005-08-07 20:21:04 +0200116
Michael Hendricks55feb122007-07-04 18:36:48 -0600117# the width (in characters) of the projects list "Description" column
118our $projects_list_description_width = 25;
119
Sebastien Ceveyd940c902011-04-29 19:52:01 +0200120# group projects by category on the projects list
121# (enabled if this variable evaluates to true)
122our $projects_list_group_categories = 0;
123
124# default category if none specified
125# (leave the empty string for no category)
126our $project_list_default_category = "";
127
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +0200128# default order of projects list
129# valid values are none, project, descr, owner, and age
130our $default_projects_order = "project";
131
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +0200132# show repository only if this file exists
133# (only effective if this variable evaluates to true)
134our $export_ok = "++GITWEB_EXPORT_OK++";
135
Alexander Gavrilovdd7f5f12008-11-06 01:36:23 +0300136# show repository only if this subroutine returns true
137# when given the path to the project, for example:
138# sub { return -e "$_[0]/git-daemon-export-ok"; }
139our $export_auth_hook = undef;
140
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +0200141# only allow viewing of repositories also shown on the overview page
142our $strict_export = "++GITWEB_STRICT_EXPORT++";
143
Jakub Narebski19a87212006-08-15 23:03:17 +0200144# list of git base URLs used for URL to where fetch project from,
145# i.e. full URL is "$git_base_url/$project"
Jakub Narebskid6b7e0b2006-10-26 12:26:44 +0200146our @git_base_url_list = grep { $_ ne '' } ("++GITWEB_BASE_URL++");
Jakub Narebski19a87212006-08-15 23:03:17 +0200147
Jakub Narebskif5aa79d2006-06-17 13:32:15 +0200148# default blob_plain mimetype and default charset for text/plain blob
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200149our $default_blob_plain_mimetype = 'text/plain';
150our $default_text_plain_charset = undef;
Jakub Narebskif5aa79d2006-06-17 13:32:15 +0200151
Petr Baudis2d007372006-06-18 00:01:06 +0200152# file to use for guessing MIME types before trying /etc/mime.types
153# (relative to the current git repository)
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200154our $mimetypes_file = undef;
Petr Baudis2d007372006-06-18 00:01:06 +0200155
Martin Koegler00f429a2007-06-03 17:42:44 +0200156# assume this charset if line contains non-UTF-8 characters;
157# it should be valid encoding (see Encoding::Supported(3pm) for list),
158# for which encoding all byte sequences are valid, for example
159# 'iso-8859-1' aka 'latin1' (it is decoded without checking, so it
160# could be even 'utf-8' for the old behavior)
161our $fallback_encoding = 'latin1';
162
Jakub Narebski69a9b412007-07-20 02:15:09 +0200163# rename detection options for git-diff and git-diff-tree
164# - default is '-M', with the cost proportional to
165# (number of removed files) * (number of new files).
166# - more costly is '-C' (which implies '-M'), with the cost proportional to
167# (number of changed files + number of removed files) * (number of new files)
168# - even more costly is '-C', '--find-copies-harder' with cost
169# (number of files in the original tree) * (number of new files)
170# - one might want to include '-B' option, e.g. '-B', '-M'
171our @diff_opts = ('-M'); # taken from git_commit
172
Matt McCutchen7e1100e2009-02-07 19:00:09 -0500173# Disables features that would allow repository owners to inject script into
174# the gitweb domain.
175our $prevent_xss = 0;
176
Christopher Wilson7ce896b2010-09-21 00:25:19 -0700177# Path to the highlight executable to use (must be the one from
178# http://www.andre-simon.de due to assumptions about parameters and output).
179# Useful if highlight is not installed on your webserver's PATH.
180# [Default: highlight]
181our $highlight_bin = "++HIGHLIGHT_BIN++";
182
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200183# information about snapshot formats that gitweb is capable of serving
184our %known_snapshot_formats = (
185 # name => {
186 # 'display' => display name,
187 # 'type' => mime type,
188 # 'suffix' => filename suffix,
189 # 'format' => --format for git-archive,
190 # 'compressor' => [compressor command and arguments]
Mark A Rada1bfd3632009-08-06 10:25:39 -0400191 # (array reference, optional)
192 # 'disabled' => boolean (optional)}
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200193 #
194 'tgz' => {
195 'display' => 'tar.gz',
196 'type' => 'application/x-gzip',
197 'suffix' => '.tar.gz',
198 'format' => 'tar',
Fraser Tweedale0c8c3852011-04-26 11:32:00 +1000199 'compressor' => ['gzip', '-n']},
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200200
201 'tbz2' => {
202 'display' => 'tar.bz2',
203 'type' => 'application/x-bzip2',
204 'suffix' => '.tar.bz2',
205 'format' => 'tar',
206 'compressor' => ['bzip2']},
207
Mark A Radacbdefb52009-08-06 10:28:25 -0400208 'txz' => {
209 'display' => 'tar.xz',
210 'type' => 'application/x-xz',
211 'suffix' => '.tar.xz',
212 'format' => 'tar',
213 'compressor' => ['xz'],
214 'disabled' => 1},
215
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200216 'zip' => {
217 'display' => 'zip',
218 'type' => 'application/x-zip',
219 'suffix' => '.zip',
220 'format' => 'zip'},
221);
222
223# Aliases so we understand old gitweb.snapshot values in repository
224# configuration.
225our %known_snapshot_format_aliases = (
226 'gzip' => 'tgz',
227 'bzip2' => 'tbz2',
Mark A Radacbdefb52009-08-06 10:28:25 -0400228 'xz' => 'txz',
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200229
230 # backward compatibility: legacy gitweb config support
231 'x-gzip' => undef, 'gz' => undef,
232 'x-bzip2' => undef, 'bz2' => undef,
233 'x-zip' => undef, '' => undef,
234);
235
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200236# Pixel sizes for icons and avatars. If the default font sizes or lineheights
237# are changed, it may be appropriate to change these values too via
238# $GITWEB_CONFIG.
239our %avatar_size = (
240 'default' => 16,
241 'double' => 32
242);
243
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +0100244# Used to set the maximum load that we will still respond to gitweb queries.
245# If server load exceed this value then return "503 server busy" error.
246# If gitweb cannot determined server load, it is taken to be 0.
247# Leave it undefined (or set to 'undef') to turn off load checking.
248our $maxload = 300;
249
Alejandro R. Sedeño61bf1262010-07-28 14:40:53 -0400250# configuration for 'highlight' (http://www.andre-simon.de/)
251# match by basename
252our %highlight_basename = (
253 #'Program' => 'py',
254 #'Library' => 'py',
255 'SConstruct' => 'py', # SCons equivalent of Makefile
256 'Makefile' => 'make',
257);
258# match by extension
259our %highlight_ext = (
260 # main extensions, defining name of syntax;
261 # see files in /usr/share/highlight/langDefs/ directory
262 map { $_ => $_ }
Sylvain Rabot3ce19eb2010-12-30 22:20:28 +0100263 qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl sql make),
Alejandro R. Sedeño61bf1262010-07-28 14:40:53 -0400264 # alternate extensions, see /etc/highlight/filetypes.conf
265 'h' => 'c',
Sylvain Rabot3ce19eb2010-12-30 22:20:28 +0100266 map { $_ => 'sh' } qw(bash zsh ksh),
Alejandro R. Sedeño61bf1262010-07-28 14:40:53 -0400267 map { $_ => 'cpp' } qw(cxx c++ cc),
Sylvain Rabot3ce19eb2010-12-30 22:20:28 +0100268 map { $_ => 'php' } qw(php3 php4 php5 phps),
Alejandro R. Sedeño61bf1262010-07-28 14:40:53 -0400269 map { $_ => 'pl' } qw(perl pm), # perhaps also 'cgi'
Sylvain Rabot3ce19eb2010-12-30 22:20:28 +0100270 map { $_ => 'make'} qw(mak mk),
Alejandro R. Sedeño61bf1262010-07-28 14:40:53 -0400271 map { $_ => 'xml' } qw(xhtml html htm),
272);
273
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530274# You define site-wide feature defaults here; override them with
275# $GITWEB_CONFIG as necessary.
Jakub Narebski952c65f2006-08-22 16:52:50 +0200276our %feature = (
Jakub Narebski17848fc2006-08-26 19:14:22 +0200277 # feature => {
278 # 'sub' => feature-sub (subroutine),
279 # 'override' => allow-override (boolean),
280 # 'default' => [ default options...] (array reference)}
281 #
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200282 # if feature is overridable (it means that allow-override has true value),
Jakub Narebski17848fc2006-08-26 19:14:22 +0200283 # then feature-sub will be called with default options as parameters;
284 # return value of feature-sub indicates if to enable specified feature
285 #
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200286 # if there is no 'sub' key (no feature-sub), then feature cannot be
Ralf Wildenhues22e5e582010-08-22 13:12:12 +0200287 # overridden
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200288 #
Giuseppe Bilottaff3c0ff2008-12-02 14:57:28 -0800289 # use gitweb_get_feature(<feature>) to retrieve the <feature> value
290 # (an array) or gitweb_check_feature(<feature>) to check if <feature>
291 # is enabled
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530292
Petr Baudis45a3b122006-10-07 15:17:47 +0200293 # Enable the 'blame' blob view, showing the last commit that modified
294 # each line in the file. This can be very CPU-intensive.
295
296 # To enable system wide have in $GITWEB_CONFIG
297 # $feature{'blame'}{'default'} = [1];
298 # To have project specific config enable override in $GITWEB_CONFIG
299 # $feature{'blame'}{'override'} = 1;
300 # and in project config gitweb.blame = 0|1;
Jakub Narebski952c65f2006-08-22 16:52:50 +0200301 'blame' => {
Matt Kraaicdad8172008-12-15 22:16:19 -0800302 'sub' => sub { feature_bool('blame', @_) },
Jakub Narebski952c65f2006-08-22 16:52:50 +0200303 'override' => 0,
304 'default' => [0]},
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530305
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200306 # Enable the 'snapshot' link, providing a compressed archive of any
Petr Baudis45a3b122006-10-07 15:17:47 +0200307 # tree. This can potentially generate high traffic if you have large
308 # project.
309
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200310 # Value is a list of formats defined in %known_snapshot_formats that
311 # you wish to offer.
Petr Baudis45a3b122006-10-07 15:17:47 +0200312 # To disable system wide have in $GITWEB_CONFIG
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200313 # $feature{'snapshot'}{'default'} = [];
Petr Baudis45a3b122006-10-07 15:17:47 +0200314 # To have project specific config enable override in $GITWEB_CONFIG
Uwe Zeisbergerbbee1d92006-12-08 12:44:31 +0100315 # $feature{'snapshot'}{'override'} = 1;
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200316 # and in project config, a comma-separated list of formats or "none"
317 # to disable. Example: gitweb.snapshot = tbz2,zip;
Jakub Narebski952c65f2006-08-22 16:52:50 +0200318 'snapshot' => {
319 'sub' => \&feature_snapshot,
320 'override' => 0,
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200321 'default' => ['tgz']},
Jakub Narebski04f7a942006-09-11 00:29:27 +0200322
Robert Fitzsimons6be93512006-12-23 03:35:16 +0000323 # Enable text search, which will list the commits which match author,
324 # committer or commit text to a given string. Enabled by default.
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200325 # Project specific override is not supported.
Jakub Narebskie0ca3642011-06-22 17:28:52 +0200326 #
327 # Note that this controls all search features, which means that if
328 # it is disabled, then 'grep' and 'pickaxe' search would also be
329 # disabled.
Robert Fitzsimons6be93512006-12-23 03:35:16 +0000330 'search' => {
331 'override' => 0,
332 'default' => [1]},
333
Petr Baudise7738552007-05-17 04:31:12 +0200334 # Enable grep search, which will list the files in currently selected
335 # tree containing the given string. Enabled by default. This can be
336 # potentially CPU-intensive, of course.
Jakub Narebskia598ded2011-06-21 08:41:16 +0200337 # Note that you need to have 'search' feature enabled too.
Petr Baudise7738552007-05-17 04:31:12 +0200338
339 # To enable system wide have in $GITWEB_CONFIG
340 # $feature{'grep'}{'default'} = [1];
341 # To have project specific config enable override in $GITWEB_CONFIG
342 # $feature{'grep'}{'override'} = 1;
343 # and in project config gitweb.grep = 0|1;
344 'grep' => {
Matt Kraaicdad8172008-12-15 22:16:19 -0800345 'sub' => sub { feature_bool('grep', @_) },
Petr Baudise7738552007-05-17 04:31:12 +0200346 'override' => 0,
347 'default' => [1]},
348
Petr Baudis45a3b122006-10-07 15:17:47 +0200349 # Enable the pickaxe search, which will list the commits that modified
350 # a given string in a file. This can be practical and quite faster
351 # alternative to 'blame', but still potentially CPU-intensive.
Jakub Narebskia598ded2011-06-21 08:41:16 +0200352 # Note that you need to have 'search' feature enabled too.
Petr Baudis45a3b122006-10-07 15:17:47 +0200353
354 # To enable system wide have in $GITWEB_CONFIG
355 # $feature{'pickaxe'}{'default'} = [1];
356 # To have project specific config enable override in $GITWEB_CONFIG
357 # $feature{'pickaxe'}{'override'} = 1;
358 # and in project config gitweb.pickaxe = 0|1;
Jakub Narebski04f7a942006-09-11 00:29:27 +0200359 'pickaxe' => {
Matt Kraaicdad8172008-12-15 22:16:19 -0800360 'sub' => sub { feature_bool('pickaxe', @_) },
Jakub Narebski04f7a942006-09-11 00:29:27 +0200361 'override' => 0,
362 'default' => [1]},
Martin Waitz9e756902006-10-01 23:57:48 +0200363
Jakub Narebskie4b48ea2009-09-07 14:40:00 +0200364 # Enable showing size of blobs in a 'tree' view, in a separate
365 # column, similar to what 'ls -l' does. This cost a bit of IO.
366
367 # To disable system wide have in $GITWEB_CONFIG
368 # $feature{'show-sizes'}{'default'} = [0];
369 # To have project specific config enable override in $GITWEB_CONFIG
370 # $feature{'show-sizes'}{'override'} = 1;
371 # and in project config gitweb.showsizes = 0|1;
372 'show-sizes' => {
373 'sub' => sub { feature_bool('showsizes', @_) },
374 'override' => 0,
375 'default' => [1]},
376
Petr Baudis45a3b122006-10-07 15:17:47 +0200377 # Make gitweb use an alternative format of the URLs which can be
378 # more readable and natural-looking: project name is embedded
379 # directly in the path and the query string contains other
380 # auxiliary information. All gitweb installations recognize
381 # URL in either format; this configures in which formats gitweb
382 # generates links.
383
384 # To enable system wide have in $GITWEB_CONFIG
385 # $feature{'pathinfo'}{'default'} = [1];
386 # Project specific override is not supported.
387
388 # Note that you will need to change the default location of CSS,
389 # favicon, logo and possibly other files to an absolute URL. Also,
390 # if gitweb.cgi serves as your indexfile, you will need to force
391 # $my_uri to contain the script name in your $GITWEB_CONFIG.
Martin Waitz9e756902006-10-01 23:57:48 +0200392 'pathinfo' => {
393 'override' => 0,
394 'default' => [0]},
Petr Baudise30496d2006-10-24 05:33:17 +0200395
396 # Make gitweb consider projects in project root subdirectories
397 # to be forks of existing projects. Given project $projname.git,
398 # projects matching $projname/*.git will not be shown in the main
399 # projects list, instead a '+' mark will be added to $projname
400 # there and a 'forks' view will be enabled for the project, listing
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +0200401 # all the forks. If project list is taken from a file, forks have
402 # to be listed after the main project.
Petr Baudise30496d2006-10-24 05:33:17 +0200403
404 # To enable system wide have in $GITWEB_CONFIG
405 # $feature{'forks'}{'default'} = [1];
406 # Project specific override is not supported.
407 'forks' => {
408 'override' => 0,
409 'default' => [0]},
Petr Baudisd627f682008-10-02 16:36:52 +0200410
411 # Insert custom links to the action bar of all project pages.
412 # This enables you mainly to link to third-party scripts integrating
413 # into gitweb; e.g. git-browser for graphical history representation
414 # or custom web-based repository administration interface.
415
416 # The 'default' value consists of a list of triplets in the form
417 # (label, link, position) where position is the label after which
Jakub Narebski2b11e052008-10-12 00:02:32 +0200418 # to insert the link and link is a format string where %n expands
Petr Baudisd627f682008-10-02 16:36:52 +0200419 # to the project name, %f to the project path within the filesystem,
420 # %h to the current hash (h gitweb parameter) and %b to the current
Jakub Narebski2b11e052008-10-12 00:02:32 +0200421 # hash base (hb gitweb parameter); %% expands to %.
Petr Baudisd627f682008-10-02 16:36:52 +0200422
423 # To enable system wide have in $GITWEB_CONFIG e.g.
424 # $feature{'actions'}{'default'} = [('graphiclog',
425 # '/git-browser/by-commit.html?r=%n', 'summary')];
426 # Project specific override is not supported.
427 'actions' => {
428 'override' => 0,
429 'default' => []},
Shawn O. Pearce3e3d4ee2008-10-03 07:41:25 -0700430
Jakub Narebski0368c492011-04-29 19:51:57 +0200431 # Allow gitweb scan project content tags of project repository,
432 # and display the popular Web 2.0-ish "tag cloud" near the projects
433 # list. Note that this is something COMPLETELY different from the
434 # normal Git tags.
Petr Baudisaed93de2008-10-02 17:13:02 +0200435
436 # gitweb by itself can show existing tags, but it does not handle
Jakub Narebski0368c492011-04-29 19:51:57 +0200437 # tagging itself; you need to do it externally, outside gitweb.
438 # The format is described in git_get_project_ctags() subroutine.
Petr Baudisaed93de2008-10-02 17:13:02 +0200439 # You may want to install the HTML::TagCloud Perl module to get
440 # a pretty tag cloud instead of just a list of tags.
441
442 # To enable system wide have in $GITWEB_CONFIG
Jakub Narebski0368c492011-04-29 19:51:57 +0200443 # $feature{'ctags'}{'default'} = [1];
Petr Baudisaed93de2008-10-02 17:13:02 +0200444 # Project specific override is not supported.
Jakub Narebski0368c492011-04-29 19:51:57 +0200445
446 # In the future whether ctags editing is enabled might depend
447 # on the value, but using 1 should always mean no editing of ctags.
Petr Baudisaed93de2008-10-02 17:13:02 +0200448 'ctags' => {
449 'override' => 0,
450 'default' => [0]},
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +0100451
452 # The maximum number of patches in a patchset generated in patch
453 # view. Set this to 0 or undef to disable patch view, or to a
454 # negative number to remove any limit.
455
456 # To disable system wide have in $GITWEB_CONFIG
457 # $feature{'patches'}{'default'} = [0];
458 # To have project specific config enable override in $GITWEB_CONFIG
459 # $feature{'patches'}{'override'} = 1;
460 # and in project config gitweb.patches = 0|n;
461 # where n is the maximum number of patches allowed in a patchset.
462 'patches' => {
463 'sub' => \&feature_patches,
464 'override' => 0,
465 'default' => [16]},
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200466
467 # Avatar support. When this feature is enabled, views such as
468 # shortlog or commit will display an avatar associated with
469 # the email of the committer(s) and/or author(s).
470
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +0200471 # Currently available providers are gravatar and picon.
472 # If an unknown provider is specified, the feature is disabled.
473
474 # Gravatar depends on Digest::MD5.
475 # Picon currently relies on the indiana.edu database.
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200476
477 # To enable system wide have in $GITWEB_CONFIG
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +0200478 # $feature{'avatar'}{'default'} = ['<provider>'];
479 # where <provider> is either gravatar or picon.
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200480 # To have project specific config enable override in $GITWEB_CONFIG
481 # $feature{'avatar'}{'override'} = 1;
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +0200482 # and in project config gitweb.avatar = <provider>;
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200483 'avatar' => {
484 'sub' => \&feature_avatar,
485 'override' => 0,
486 'default' => ['']},
Jakub Narebskiaa7dd052009-09-01 13:39:16 +0200487
488 # Enable displaying how much time and how many git commands
489 # it took to generate and display page. Disabled by default.
490 # Project specific override is not supported.
491 'timed' => {
492 'override' => 0,
493 'default' => [0]},
Jakub Narebskie627e502009-11-26 21:12:15 +0100494
495 # Enable turning some links into links to actions which require
496 # JavaScript to run (like 'blame_incremental'). Not enabled by
497 # default. Project specific override is currently not supported.
498 'javascript-actions' => {
499 'override' => 0,
500 'default' => [0]},
Johannes Schindelinb331fe52010-04-27 21:34:44 +0200501
Jakub Narebski2e987f92011-04-28 21:04:11 +0200502 # Enable and configure ability to change common timezone for dates
503 # in gitweb output via JavaScript. Enabled by default.
504 # Project specific override is not supported.
505 'javascript-timezone' => {
506 'override' => 0,
507 'default' => [
508 'local', # default timezone: 'utc', 'local', or '(-|+)HHMM' format,
509 # or undef to turn off this feature
510 'gitweb_tz', # name of cookie where to store selected timezone
511 'datetime', # CSS class used to mark up dates for manipulation
512 ]},
513
Johannes Schindelinb331fe52010-04-27 21:34:44 +0200514 # Syntax highlighting support. This is based on Daniel Svensson's
515 # and Sham Chukoury's work in gitweb-xmms2.git.
Jakub Narebski592ea412010-04-27 21:34:45 +0200516 # It requires the 'highlight' program present in $PATH,
517 # and therefore is disabled by default.
Johannes Schindelinb331fe52010-04-27 21:34:44 +0200518
519 # To enable system wide have in $GITWEB_CONFIG
520 # $feature{'highlight'}{'default'} = [1];
521
522 'highlight' => {
523 'sub' => sub { feature_bool('highlight', @_) },
524 'override' => 0,
525 'default' => [0]},
Giuseppe Bilotta60efa242010-11-11 13:26:09 +0100526
527 # Enable displaying of remote heads in the heads list
528
529 # To enable system wide have in $GITWEB_CONFIG
530 # $feature{'remote_heads'}{'default'} = [1];
531 # To have project specific config enable override in $GITWEB_CONFIG
532 # $feature{'remote_heads'}{'override'} = 1;
533 # and in project config gitweb.remote_heads = 0|1;
534 'remote_heads' => {
535 'sub' => sub { feature_bool('remote_heads', @_) },
536 'override' => 0,
537 'default' => [0]},
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530538);
539
Junio C Hamanoa7c5a282008-11-29 13:02:08 -0800540sub gitweb_get_feature {
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530541 my ($name) = @_;
Jakub Narebskidd1ad5f2006-09-26 01:56:17 +0200542 return unless exists $feature{$name};
Jakub Narebski952c65f2006-08-22 16:52:50 +0200543 my ($sub, $override, @defaults) = (
544 $feature{$name}{'sub'},
545 $feature{$name}{'override'},
546 @{$feature{$name}{'default'}});
Jakub Narebski9be36142010-03-01 22:51:34 +0100547 # project specific override is possible only if we have project
548 our $git_dir; # global variable, declared later
549 if (!$override || !defined $git_dir) {
550 return @defaults;
551 }
Martin Waitza9455912006-10-03 20:07:43 +0200552 if (!defined $sub) {
Nanako Shiraishi93197892009-08-28 12:18:49 +0900553 warn "feature $name is not overridable";
Martin Waitza9455912006-10-03 20:07:43 +0200554 return @defaults;
555 }
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530556 return $sub->(@defaults);
557}
558
Giuseppe Bilotta25b27902008-11-29 13:07:29 -0800559# A wrapper to check if a given feature is enabled.
560# With this, you can say
561#
562# my $bool_feat = gitweb_check_feature('bool_feat');
563# gitweb_check_feature('bool_feat') or somecode;
564#
565# instead of
566#
567# my ($bool_feat) = gitweb_get_feature('bool_feat');
568# (gitweb_get_feature('bool_feat'))[0] or somecode;
569#
570sub gitweb_check_feature {
571 return (gitweb_get_feature(@_))[0];
572}
573
574
Matt Kraaicdad8172008-12-15 22:16:19 -0800575sub feature_bool {
576 my $key = shift;
577 my ($val) = git_get_project_config($key, '--bool');
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530578
Marcel M. Carydf5d10a2009-02-18 14:09:41 +0100579 if (!defined $val) {
580 return ($_[0]);
581 } elsif ($val eq 'true') {
Matt Kraaicdad8172008-12-15 22:16:19 -0800582 return (1);
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530583 } elsif ($val eq 'false') {
Matt Kraaicdad8172008-12-15 22:16:19 -0800584 return (0);
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530585 }
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530586}
587
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530588sub feature_snapshot {
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200589 my (@fmts) = @_;
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530590
591 my ($val) = git_get_project_config('snapshot');
592
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200593 if ($val) {
594 @fmts = ($val eq 'none' ? () : split /\s*[,\s]\s*/, $val);
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530595 }
596
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200597 return @fmts;
Luben Tuikovde9272f2006-09-28 16:49:21 -0700598}
599
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +0100600sub feature_patches {
601 my @val = (git_get_project_config('patches', '--int'));
602
603 if (@val) {
604 return @val;
605 }
606
607 return ($_[0]);
608}
609
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200610sub feature_avatar {
611 my @val = (git_get_project_config('avatar'));
612
613 return @val ? @val : @_;
614}
615
Junio C Hamano2172ce42006-10-03 02:30:47 -0700616# checking HEAD file with -e is fragile if the repository was
617# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
618# and then pruned.
619sub check_head_link {
620 my ($dir) = @_;
621 my $headfile = "$dir/HEAD";
622 return ((-e $headfile) ||
623 (-l $headfile && readlink($headfile) =~ /^refs\/heads\//));
624}
625
626sub check_export_ok {
627 my ($dir) = @_;
628 return (check_head_link($dir) &&
Alexander Gavrilovdd7f5f12008-11-06 01:36:23 +0300629 (!$export_ok || -e "$dir/$export_ok") &&
630 (!$export_auth_hook || $export_auth_hook->($dir)));
Junio C Hamano2172ce42006-10-03 02:30:47 -0700631}
632
Jakub Narebskia7817852007-07-22 23:41:20 +0200633# process alternate names for backward compatibility
634# filter out unsupported (unknown) snapshot formats
635sub filter_snapshot_fmts {
636 my @fmts = @_;
637
638 @fmts = map {
639 exists $known_snapshot_format_aliases{$_} ?
640 $known_snapshot_format_aliases{$_} : $_} @fmts;
Jakub Narebski68cedb12009-05-10 02:40:37 +0200641 @fmts = grep {
Mark A Rada1bfd3632009-08-06 10:25:39 -0400642 exists $known_snapshot_formats{$_} &&
643 !$known_snapshot_formats{$_}{'disabled'}} @fmts;
Jakub Narebskia7817852007-07-22 23:41:20 +0200644}
645
Jakub Narebskida4b2432010-11-25 19:43:59 +0100646# If it is set to code reference, it is code that it is to be run once per
647# request, allowing updating configurations that change with each request,
648# while running other code in config file only once.
649#
650# Otherwise, if it is false then gitweb would process config file only once;
651# if it is true then gitweb config would be run for each request.
652our $per_request_config = 1;
653
Jakub Narebskif612a712011-05-25 18:35:26 +0200654# read and parse gitweb config file given by its parameter.
655# returns true on success, false on recoverable error, allowing
656# to chain this subroutine, using first file that exists.
657# dies on errors during parsing config file, as it is unrecoverable.
658sub read_config_file {
659 my $filename = shift;
660 return unless defined $filename;
661 # die if there are errors parsing config file
662 if (-e $filename) {
663 do $filename;
664 die $@ if $@;
665 return 1;
666 }
667 return;
668}
669
Jakub Narebski131d6af2011-07-25 00:29:18 +0200670our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM, $GITWEB_CONFIG_COMMON);
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200671sub evaluate_gitweb_config {
672 our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
673 our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
Jakub Narebski131d6af2011-07-25 00:29:18 +0200674 our $GITWEB_CONFIG_COMMON = $ENV{'GITWEB_CONFIG_COMMON'} || "++GITWEB_CONFIG_COMMON++";
Jakub Narebskif612a712011-05-25 18:35:26 +0200675
Jakub Narebski131d6af2011-07-25 00:29:18 +0200676 # Protect agains duplications of file names, to not read config twice.
677 # Only one of $GITWEB_CONFIG and $GITWEB_CONFIG_SYSTEM is used, so
678 # there possibility of duplication of filename there doesn't matter.
679 $GITWEB_CONFIG = "" if ($GITWEB_CONFIG eq $GITWEB_CONFIG_COMMON);
680 $GITWEB_CONFIG_SYSTEM = "" if ($GITWEB_CONFIG_SYSTEM eq $GITWEB_CONFIG_COMMON);
681
682 # Common system-wide settings for convenience.
683 # Those settings can be ovverriden by GITWEB_CONFIG or GITWEB_CONFIG_SYSTEM.
684 read_config_file($GITWEB_CONFIG_COMMON);
685
686 # Use first config file that exists. This means use the per-instance
687 # GITWEB_CONFIG if exists, otherwise use GITWEB_SYSTEM_CONFIG.
688 read_config_file($GITWEB_CONFIG) and return;
Jakub Narebskif612a712011-05-25 18:35:26 +0200689 read_config_file($GITWEB_CONFIG_SYSTEM);
Gerrit Pape17a8b252008-03-26 18:11:19 +0000690}
Jeff Kingc8d138a2006-08-02 15:23:34 -0400691
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +0100692# Get loadavg of system, to compare against $maxload.
693# Currently it requires '/proc/loadavg' present to get loadavg;
694# if it is not present it returns 0, which means no load checking.
695sub get_loadavg {
696 if( -e '/proc/loadavg' ){
697 open my $fd, '<', '/proc/loadavg'
698 or return 0;
699 my @load = split(/\s+/, scalar <$fd>);
700 close $fd;
701
702 # The first three columns measure CPU and IO utilization of the last one,
703 # five, and 10 minute periods. The fourth column shows the number of
704 # currently running processes and the total number of processes in the m/n
705 # format. The last column displays the last process ID used.
706 return $load[0] || 0;
707 }
708 # additional checks for load average should go here for things that don't export
709 # /proc/loadavg
710
711 return 0;
712}
713
Jeff Kingc8d138a2006-08-02 15:23:34 -0400714# version of the core git binary
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200715our $git_version;
716sub evaluate_git_version {
717 our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
718 $number_of_git_cmds++;
719}
Jeff Kingc8d138a2006-08-02 15:23:34 -0400720
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200721sub check_loadavg {
722 if (defined $maxload && get_loadavg() > $maxload) {
723 die_error(503, "The load average on the server is too high");
724 }
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +0100725}
726
Jakub Narebski154b4d72006-08-05 12:55:20 +0200727# ======================================================================
Kay Sievers09bd7892005-08-07 20:21:23 +0200728# input validation and dispatch
Kay Sieversb87d78d2005-08-07 20:21:04 +0200729
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200730# input parameters can be collected from a variety of sources (presently, CGI
731# and PATH_INFO), so we define an %input_params hash that collects them all
732# together during validation: this allows subsequent uses (e.g. href()) to be
733# agnostic of the parameter origin
Kay Sievers6191f8e2005-08-07 20:19:56 +0200734
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300735our %input_params = ();
Martin Waitz5c95fab2006-08-17 00:28:38 +0200736
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200737# input parameters are stored with the long parameter name as key. This will
738# also be used in the href subroutine to convert parameters to their CGI
739# equivalent, and since the href() usage is the most frequent one, we store
740# the name -> CGI key mapping here, instead of the reverse.
741#
742# XXX: Warning: If you touch this, check the search form for updating,
743# too.
Jakub Narebski24d06932006-09-26 01:57:02 +0200744
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300745our @cgi_param_mapping = (
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200746 project => "p",
747 action => "a",
748 file_name => "f",
749 file_parent => "fp",
750 hash => "h",
751 hash_parent => "hp",
752 hash_base => "hb",
753 hash_parent_base => "hpb",
754 page => "pg",
755 order => "o",
756 searchtext => "s",
757 searchtype => "st",
758 snapshot_format => "sf",
759 extra_options => "opt",
760 search_use_regexp => "sr",
Jakub Narebski0368c492011-04-29 19:51:57 +0200761 ctag => "by_tag",
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +0100762 diff_style => "ds",
Bernhard R. Link19d2d232012-01-30 21:07:37 +0100763 project_filter => "pf",
Jakub Narebskic4ccf612009-09-01 13:39:19 +0200764 # this must be last entry (for manipulation from JavaScript)
765 javascript => "js"
Miklos Vajna868bc062007-07-12 20:39:27 +0200766);
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300767our %cgi_param_mapping = @cgi_param_mapping;
Miklos Vajna868bc062007-07-12 20:39:27 +0200768
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200769# we will also need to know the possible actions, for validation
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300770our %actions = (
Rafael Garcia-Suarez3a5b9192008-06-06 09:53:32 +0200771 "blame" => \&git_blame,
Jakub Narebski4af819d2009-09-01 13:39:17 +0200772 "blame_incremental" => \&git_blame_incremental,
773 "blame_data" => \&git_blame_data,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200774 "blobdiff" => \&git_blobdiff,
775 "blobdiff_plain" => \&git_blobdiff_plain,
776 "blob" => \&git_blob,
777 "blob_plain" => \&git_blob_plain,
778 "commitdiff" => \&git_commitdiff,
779 "commitdiff_plain" => \&git_commitdiff_plain,
780 "commit" => \&git_commit,
Petr Baudise30496d2006-10-24 05:33:17 +0200781 "forks" => \&git_forks,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200782 "heads" => \&git_heads,
783 "history" => \&git_history,
784 "log" => \&git_log,
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +0100785 "patch" => \&git_patch,
Giuseppe Bilottaa3411f82008-12-18 08:13:18 +0100786 "patches" => \&git_patches,
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +0100787 "remotes" => \&git_remotes,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200788 "rss" => \&git_rss,
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +0100789 "atom" => \&git_atom,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200790 "search" => \&git_search,
Petr Baudis88ad7292006-10-24 05:15:46 +0200791 "search_help" => \&git_search_help,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200792 "shortlog" => \&git_shortlog,
793 "summary" => \&git_summary,
794 "tag" => \&git_tag,
795 "tags" => \&git_tags,
796 "tree" => \&git_tree,
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +0530797 "snapshot" => \&git_snapshot,
Jakub Narebskica946012006-12-10 13:25:47 +0100798 "object" => \&git_object,
Jakub Narebski77a153f2006-08-22 16:59:20 +0200799 # those below don't need $project
800 "opml" => \&git_opml,
801 "project_list" => \&git_project_list,
Jakub Narebskifc2b2be2006-09-15 04:56:03 +0200802 "project_index" => \&git_project_index,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200803);
804
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200805# finally, we have the hash of allowed extra_options for the commands that
806# allow them
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300807our %allowed_options = (
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200808 "--no-merges" => [ qw(rss atom log shortlog history) ],
809);
810
811# fill %input_params with the CGI parameters. All values except for 'opt'
812# should be single values, but opt can be an array. We should probably
813# build an array of parameters that can be multi-valued, but since for the time
814# being it's only this one, we just single it out
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200815sub evaluate_query_params {
816 our $cgi;
817
818 while (my ($name, $symbol) = each %cgi_param_mapping) {
819 if ($symbol eq 'opt') {
Jakub Narebski84d9e2d2012-02-03 13:44:54 +0100820 $input_params{$name} = [ map { decode_utf8($_) } $cgi->param($symbol) ];
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200821 } else {
Jakub Narebski84d9e2d2012-02-03 13:44:54 +0100822 $input_params{$name} = decode_utf8($cgi->param($symbol));
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200823 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200824 }
825}
826
827# now read PATH_INFO and update the parameter list for missing parameters
828sub evaluate_path_info {
829 return if defined $input_params{'project'};
830 return if !$path_info;
831 $path_info =~ s,^/+,,;
832 return if !$path_info;
833
834 # find which part of PATH_INFO is project
835 my $project = $path_info;
836 $project =~ s,/+$,,;
837 while ($project && !check_head_link("$projectroot/$project")) {
838 $project =~ s,/*[^/]*$,,;
839 }
840 return unless $project;
841 $input_params{'project'} = $project;
842
843 # do not change any parameters if an action is given using the query string
844 return if $input_params{'action'};
845 $path_info =~ s,^\Q$project\E/*,,;
846
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200847 # next, check if we have an action
848 my $action = $path_info;
849 $action =~ s,/.*$,,;
850 if (exists $actions{$action}) {
851 $path_info =~ s,^$action/*,,;
852 $input_params{'action'} = $action;
853 }
854
855 # list of actions that want hash_base instead of hash, but can have no
856 # pathname (f) parameter
857 my @wants_base = (
858 'tree',
859 'history',
860 );
861
Jakub Narebski7e00dc52010-10-13 13:33:48 +0200862 # we want to catch, among others
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200863 # [$hash_parent_base[:$file_parent]..]$hash_parent[:$file_name]
864 my ($parentrefname, $parentpathname, $refname, $pathname) =
Jakub Narebski7e00dc52010-10-13 13:33:48 +0200865 ($path_info =~ /^(?:(.+?)(?::(.+))?\.\.)?([^:]+?)?(?::(.+))?$/);
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200866
867 # first, analyze the 'current' part
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200868 if (defined $pathname) {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200869 # we got "branch:filename" or "branch:dir/"
870 # we could use git_get_type(branch:pathname), but:
871 # - it needs $git_dir
872 # - it does a git() call
873 # - the convention of terminating directories with a slash
874 # makes it superfluous
875 # - embedding the action in the PATH_INFO would make it even
876 # more superfluous
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200877 $pathname =~ s,^/+,,;
878 if (!$pathname || substr($pathname, -1) eq "/") {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200879 $input_params{'action'} ||= "tree";
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200880 $pathname =~ s,/$,,;
881 } else {
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200882 # the default action depends on whether we had parent info
883 # or not
884 if ($parentrefname) {
885 $input_params{'action'} ||= "blobdiff_plain";
886 } else {
887 $input_params{'action'} ||= "blob_plain";
888 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200889 }
890 $input_params{'hash_base'} ||= $refname;
891 $input_params{'file_name'} ||= $pathname;
892 } elsif (defined $refname) {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200893 # we got "branch". In this case we have to choose if we have to
894 # set hash or hash_base.
895 #
896 # Most of the actions without a pathname only want hash to be
897 # set, except for the ones specified in @wants_base that want
898 # hash_base instead. It should also be noted that hand-crafted
899 # links having 'history' as an action and no pathname or hash
900 # set will fail, but that happens regardless of PATH_INFO.
Jakub Narebskid0af3732010-10-13 13:35:20 +0200901 if (defined $parentrefname) {
902 # if there is parent let the default be 'shortlog' action
903 # (for http://git.example.com/repo.git/A..B links); if there
904 # is no parent, dispatch will detect type of object and set
905 # action appropriately if required (if action is not set)
906 $input_params{'action'} ||= "shortlog";
907 }
908 if ($input_params{'action'} &&
909 grep { $_ eq $input_params{'action'} } @wants_base) {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200910 $input_params{'hash_base'} ||= $refname;
911 } else {
912 $input_params{'hash'} ||= $refname;
913 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200914 }
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200915
916 # next, handle the 'parent' part, if present
917 if (defined $parentrefname) {
918 # a missing pathspec defaults to the 'current' filename, allowing e.g.
919 # someproject/blobdiff/oldrev..newrev:/filename
920 if ($parentpathname) {
921 $parentpathname =~ s,^/+,,;
922 $parentpathname =~ s,/$,,;
923 $input_params{'file_parent'} ||= $parentpathname;
924 } else {
925 $input_params{'file_parent'} ||= $input_params{'file_name'};
926 }
927 # we assume that hash_parent_base is wanted if a path was specified,
928 # or if the action wants hash_base instead of hash
929 if (defined $input_params{'file_parent'} ||
930 grep { $_ eq $input_params{'action'} } @wants_base) {
931 $input_params{'hash_parent_base'} ||= $parentrefname;
932 } else {
933 $input_params{'hash_parent'} ||= $parentrefname;
934 }
935 }
Giuseppe Bilotta1ec2fb52008-11-02 10:21:38 +0100936
937 # for the snapshot action, we allow URLs in the form
938 # $project/snapshot/$hash.ext
939 # where .ext determines the snapshot and gets removed from the
940 # passed $refname to provide the $hash.
941 #
942 # To be able to tell that $refname includes the format extension, we
943 # require the following two conditions to be satisfied:
944 # - the hash input parameter MUST have been set from the $refname part
945 # of the URL (i.e. they must be equal)
946 # - the snapshot format MUST NOT have been defined already (e.g. from
947 # CGI parameter sf)
948 # It's also useless to try any matching unless $refname has a dot,
949 # so we check for that too
950 if (defined $input_params{'action'} &&
951 $input_params{'action'} eq 'snapshot' &&
952 defined $refname && index($refname, '.') != -1 &&
953 $refname eq $input_params{'hash'} &&
954 !defined $input_params{'snapshot_format'}) {
955 # We loop over the known snapshot formats, checking for
956 # extensions. Allowed extensions are both the defined suffix
957 # (which includes the initial dot already) and the snapshot
958 # format key itself, with a prepended dot
Holger Weißccb4b532009-03-31 18:16:36 +0200959 while (my ($fmt, $opt) = each %known_snapshot_formats) {
Giuseppe Bilotta1ec2fb52008-11-02 10:21:38 +0100960 my $hash = $refname;
Jakub Narebski095e9142009-05-11 19:42:47 +0200961 unless ($hash =~ s/(\Q$opt->{'suffix'}\E|\Q.$fmt\E)$//) {
962 next;
963 }
964 my $sfx = $1;
Giuseppe Bilotta1ec2fb52008-11-02 10:21:38 +0100965 # a valid suffix was found, so set the snapshot format
966 # and reset the hash parameter
967 $input_params{'snapshot_format'} = $fmt;
968 $input_params{'hash'} = $hash;
969 # we also set the format suffix to the one requested
970 # in the URL: this way a request for e.g. .tgz returns
971 # a .tgz instead of a .tar.gz
972 $known_snapshot_formats{$fmt}{'suffix'} = $sfx;
973 last;
974 }
975 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200976}
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200977
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200978our ($action, $project, $file_name, $file_parent, $hash, $hash_parent, $hash_base,
979 $hash_parent_base, @extra_options, $page, $searchtype, $search_use_regexp,
Bernhard R. Link19d2d232012-01-30 21:07:37 +0100980 $searchtext, $search_regexp, $project_filter);
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200981sub evaluate_and_validate_params {
982 our $action = $input_params{'action'};
983 if (defined $action) {
984 if (!validate_action($action)) {
985 die_error(400, "Invalid action parameter");
986 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200987 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200988
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200989 # parameters which are pathnames
990 our $project = $input_params{'project'};
991 if (defined $project) {
992 if (!validate_project($project)) {
993 undef $project;
994 die_error(404, "No such project");
995 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200996 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200997
Bernhard R. Link19d2d232012-01-30 21:07:37 +0100998 our $project_filter = $input_params{'project_filter'};
999 if (defined $project_filter) {
1000 if (!validate_pathname($project_filter)) {
1001 die_error(404, "Invalid project_filter parameter");
1002 }
1003 }
1004
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001005 our $file_name = $input_params{'file_name'};
1006 if (defined $file_name) {
1007 if (!validate_pathname($file_name)) {
1008 die_error(400, "Invalid file parameter");
1009 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001010 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001011
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001012 our $file_parent = $input_params{'file_parent'};
1013 if (defined $file_parent) {
1014 if (!validate_pathname($file_parent)) {
1015 die_error(400, "Invalid file parent parameter");
1016 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001017 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001018
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001019 # parameters which are refnames
1020 our $hash = $input_params{'hash'};
1021 if (defined $hash) {
1022 if (!validate_refname($hash)) {
1023 die_error(400, "Invalid hash parameter");
1024 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001025 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001026
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001027 our $hash_parent = $input_params{'hash_parent'};
1028 if (defined $hash_parent) {
1029 if (!validate_refname($hash_parent)) {
1030 die_error(400, "Invalid hash parent parameter");
1031 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001032 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001033
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001034 our $hash_base = $input_params{'hash_base'};
1035 if (defined $hash_base) {
1036 if (!validate_refname($hash_base)) {
1037 die_error(400, "Invalid hash base parameter");
1038 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001039 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001040
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001041 our @extra_options = @{$input_params{'extra_options'}};
1042 # @extra_options is always defined, since it can only be (currently) set from
1043 # CGI, and $cgi->param() returns the empty array in array context if the param
1044 # is not set
1045 foreach my $opt (@extra_options) {
1046 if (not exists $allowed_options{$opt}) {
1047 die_error(400, "Invalid option parameter");
1048 }
1049 if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
1050 die_error(400, "Invalid option parameter for this action");
1051 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001052 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001053
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001054 our $hash_parent_base = $input_params{'hash_parent_base'};
1055 if (defined $hash_parent_base) {
1056 if (!validate_refname($hash_parent_base)) {
1057 die_error(400, "Invalid hash parent base parameter");
1058 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001059 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001060
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001061 # other parameters
1062 our $page = $input_params{'page'};
1063 if (defined $page) {
1064 if ($page =~ m/[^0-9]/) {
1065 die_error(400, "Invalid page parameter");
1066 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001067 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001068
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001069 our $searchtype = $input_params{'searchtype'};
1070 if (defined $searchtype) {
1071 if ($searchtype =~ m/[^a-z]/) {
1072 die_error(400, "Invalid searchtype parameter");
1073 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001074 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001075
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001076 our $search_use_regexp = $input_params{'search_use_regexp'};
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001077
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001078 our $searchtext = $input_params{'searchtext'};
1079 our $search_regexp;
1080 if (defined $searchtext) {
1081 if (length($searchtext) < 2) {
1082 die_error(403, "At least two characters are required for search parameter");
1083 }
Jakub Narebski36612e42012-02-28 19:41:47 +01001084 if ($search_use_regexp) {
1085 $search_regexp = $searchtext;
1086 if (!eval { qr/$search_regexp/; 1; }) {
1087 (my $error = $@) =~ s/ at \S+ line \d+.*\n?//;
1088 die_error(400, "Invalid search regexp '$search_regexp'",
1089 esc_html($error));
1090 }
1091 } else {
1092 $search_regexp = quotemeta $searchtext;
1093 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001094 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001095}
1096
1097# path to the current git repository
1098our $git_dir;
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001099sub evaluate_git_dir {
1100 our $git_dir = "$projectroot/$project" if $project;
1101}
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001102
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001103our (@snapshot_fmts, $git_avatar);
1104sub configure_gitweb_features {
1105 # list of supported snapshot formats
1106 our @snapshot_fmts = gitweb_get_feature('snapshot');
1107 @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
Giuseppe Bilotta5e166842008-11-02 10:21:37 +01001108
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001109 # check that the avatar feature is set to a known provider name,
1110 # and for each provider check if the dependencies are satisfied.
1111 # if the provider name is invalid or the dependencies are not met,
1112 # reset $git_avatar to the empty string.
1113 our ($git_avatar) = gitweb_get_feature('avatar');
1114 if ($git_avatar eq 'gravatar') {
1115 $git_avatar = '' unless (eval { require Digest::MD5; 1; });
1116 } elsif ($git_avatar eq 'picon') {
1117 # no dependencies
1118 } else {
1119 $git_avatar = '';
1120 }
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02001121}
1122
Jakub Narebski7a597452010-04-24 16:00:04 +02001123# custom error handler: 'die <message>' is Internal Server Error
1124sub handle_errors_html {
1125 my $msg = shift; # it is already HTML escaped
1126
1127 # to avoid infinite loop where error occurs in die_error,
1128 # change handler to default handler, disabling handle_errors_html
1129 set_message("Error occured when inside die_error:\n$msg");
1130
1131 # you cannot jump out of die_error when called as error handler;
1132 # the subroutine set via CGI::Carp::set_message is called _after_
1133 # HTTP headers are already written, so it cannot write them itself
1134 die_error(undef, undef, $msg, -error_handler => 1, -no_http_header => 1);
1135}
1136set_message(\&handle_errors_html);
1137
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001138# dispatch
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001139sub dispatch {
1140 if (!defined $action) {
1141 if (defined $hash) {
1142 $action = git_get_type($hash);
Jakub Narebski18ab83e2012-01-07 11:47:38 +01001143 $action or die_error(404, "Object does not exist");
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001144 } elsif (defined $hash_base && defined $file_name) {
1145 $action = git_get_type("$hash_base:$file_name");
Jakub Narebski18ab83e2012-01-07 11:47:38 +01001146 $action or die_error(404, "File or directory does not exist");
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001147 } elsif (defined $project) {
1148 $action = 'summary';
1149 } else {
1150 $action = 'project_list';
1151 }
Gerrit Pape7f9778b2007-05-10 07:32:07 +00001152 }
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001153 if (!defined($actions{$action})) {
1154 die_error(400, "Unknown action");
1155 }
1156 if ($action !~ m/^(?:opml|project_list|project_index)$/ &&
1157 !$project) {
1158 die_error(400, "Project needed");
1159 }
1160 $actions{$action}->();
Jakub Narebski77a153f2006-08-22 16:59:20 +02001161}
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001162
Jakub Narebski869d5882010-07-05 20:52:43 +02001163sub reset_timer {
Jakub Narebski3962f1d72010-11-09 19:27:54 +01001164 our $t0 = [ gettimeofday() ]
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001165 if defined $t0;
Jakub Narebski869d5882010-07-05 20:52:43 +02001166 our $number_of_git_cmds = 0;
1167}
1168
Jakub Narebskida4b2432010-11-25 19:43:59 +01001169our $first_request = 1;
Jakub Narebski869d5882010-07-05 20:52:43 +02001170sub run_request {
1171 reset_timer();
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001172
1173 evaluate_uri();
Jakub Narebskida4b2432010-11-25 19:43:59 +01001174 if ($first_request) {
1175 evaluate_gitweb_config();
1176 evaluate_git_version();
1177 }
1178 if ($per_request_config) {
1179 if (ref($per_request_config) eq 'CODE') {
1180 $per_request_config->();
1181 } elsif (!$first_request) {
1182 evaluate_gitweb_config();
1183 }
1184 }
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001185 check_loadavg();
1186
Jonathan Nieder7f425db2010-07-30 22:01:59 -05001187 # $projectroot and $projects_list might be set in gitweb config file
1188 $projects_list ||= $projectroot;
1189
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001190 evaluate_query_params();
1191 evaluate_path_info();
1192 evaluate_and_validate_params();
1193 evaluate_git_dir();
1194
1195 configure_gitweb_features();
1196
1197 dispatch();
Kay Sievers09bd7892005-08-07 20:21:23 +02001198}
Sam Vilaina0446e72010-05-07 14:54:05 +02001199
1200our $is_last_request = sub { 1 };
1201our ($pre_dispatch_hook, $post_dispatch_hook, $pre_listen_hook);
1202our $CGI = 'CGI';
1203our $cgi;
Jakub Narebski45aa9892010-06-05 23:11:18 +02001204sub configure_as_fcgi {
1205 require CGI::Fast;
1206 our $CGI = 'CGI::Fast';
1207
1208 my $request_number = 0;
1209 # let each child service 100 requests
1210 our $is_last_request = sub { ++$request_number > 100 };
Jakub Narebskid04d3d42006-09-19 21:53:22 +02001211}
Sam Vilaina0446e72010-05-07 14:54:05 +02001212sub evaluate_argv {
Jakub Narebski45aa9892010-06-05 23:11:18 +02001213 my $script_name = $ENV{'SCRIPT_NAME'} || $ENV{'SCRIPT_FILENAME'} || __FILE__;
1214 configure_as_fcgi()
1215 if $script_name =~ /\.fcgi$/;
1216
Sam Vilaina0446e72010-05-07 14:54:05 +02001217 return unless (@ARGV);
1218
1219 require Getopt::Long;
1220 Getopt::Long::GetOptions(
Jakub Narebski45aa9892010-06-05 23:11:18 +02001221 'fastcgi|fcgi|f' => \&configure_as_fcgi,
Sam Vilaina0446e72010-05-07 14:54:05 +02001222 'nproc|n=i' => sub {
1223 my ($arg, $val) = @_;
1224 return unless eval { require FCGI::ProcManager; 1; };
1225 my $proc_manager = FCGI::ProcManager->new({
1226 n_processes => $val,
1227 });
1228 our $pre_listen_hook = sub { $proc_manager->pm_manage() };
1229 our $pre_dispatch_hook = sub { $proc_manager->pm_pre_dispatch() };
1230 our $post_dispatch_hook = sub { $proc_manager->pm_post_dispatch() };
1231 },
1232 );
1233}
1234
1235sub run {
1236 evaluate_argv();
Jakub Narebski869d5882010-07-05 20:52:43 +02001237
Jakub Narebskida4b2432010-11-25 19:43:59 +01001238 $first_request = 1;
Sam Vilaina0446e72010-05-07 14:54:05 +02001239 $pre_listen_hook->()
1240 if $pre_listen_hook;
1241
1242 REQUEST:
1243 while ($cgi = $CGI->new()) {
1244 $pre_dispatch_hook->()
1245 if $pre_dispatch_hook;
1246
1247 run_request();
1248
Jakub Narebski0b450102010-08-02 22:21:47 +02001249 $post_dispatch_hook->()
Sam Vilaina0446e72010-05-07 14:54:05 +02001250 if $post_dispatch_hook;
Jakub Narebskida4b2432010-11-25 19:43:59 +01001251 $first_request = 0;
Sam Vilaina0446e72010-05-07 14:54:05 +02001252
1253 last REQUEST if ($is_last_request->());
1254 }
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001255
1256 DONE_GITWEB:
1257 1;
Kay Sievers09bd7892005-08-07 20:21:23 +02001258}
Sam Vilaina0446e72010-05-07 14:54:05 +02001259
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001260run();
Kay Sievers09bd7892005-08-07 20:21:23 +02001261
Jakub Narebski5ed2ec12010-06-13 12:09:32 +02001262if (defined caller) {
1263 # wrapped in a subroutine processing requests,
1264 # e.g. mod_perl with ModPerl::Registry, or PSGI with Plack::App::WrapCGI
1265 return;
1266} else {
1267 # pure CGI script, serving single request
1268 exit;
1269}
Kay Sievers823d5dc2005-08-07 19:57:58 +02001270
Jakub Narebski717b8312006-07-31 21:22:15 +02001271## ======================================================================
Martin Waitz06a9d862006-08-16 00:23:50 +02001272## action links
1273
Jakub Narebski377bee32010-04-24 15:53:19 +02001274# possible values of extra options
1275# -full => 0|1 - use absolute/full URL ($my_uri/$my_url as base)
1276# -replay => 1 - start from a current view (replay with modifications)
1277# -path_info => 0|1 - don't use/use path_info URL (if possible)
Kevin Cernekee5e96a842011-03-18 17:00:16 +01001278# -anchor => ANCHOR - add #ANCHOR to end of URL, implies -replay if used alone
Jakub Narebski74fd8722009-05-07 19:11:29 +02001279sub href {
Jakub Narebski498fe002006-08-22 19:05:25 +02001280 my %params = @_;
Jakub Narebskibd5d1e42006-11-19 15:05:21 +01001281 # default is to use -absolute url() i.e. $my_uri
1282 my $href = $params{-full} ? $my_url : $my_uri;
Jakub Narebski498fe002006-08-22 19:05:25 +02001283
Kevin Cernekee5e96a842011-03-18 17:00:16 +01001284 # implicit -replay, must be first of implicit params
1285 $params{-replay} = 1 if (keys %params == 1 && $params{-anchor});
1286
Jakub Narebskiafa9b622008-02-14 09:22:30 +01001287 $params{'project'} = $project unless exists $params{'project'};
1288
Jakub Narebski1cad2832007-11-01 13:06:27 +01001289 if ($params{-replay}) {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001290 while (my ($name, $symbol) = each %cgi_param_mapping) {
Jakub Narebski1cad2832007-11-01 13:06:27 +01001291 if (!exists $params{$name}) {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001292 $params{$name} = $input_params{$name};
Jakub Narebski1cad2832007-11-01 13:06:27 +01001293 }
1294 }
1295 }
1296
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08001297 my $use_pathinfo = gitweb_check_feature('pathinfo');
Jakub Narebski377bee32010-04-24 15:53:19 +02001298 if (defined $params{'project'} &&
1299 (exists $params{-path_info} ? $params{-path_info} : $use_pathinfo)) {
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001300 # try to put as many parameters as possible in PATH_INFO:
1301 # - project name
1302 # - action
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001303 # - hash_parent or hash_parent_base:/file_parent
Giuseppe Bilotta3550ea72008-10-21 21:34:52 +02001304 # - hash or hash_base:/filename
Giuseppe Bilottac752a0e2008-11-02 10:21:39 +01001305 # - the snapshot_format as an appropriate suffix
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001306
1307 # When the script is the root DirectoryIndex for the domain,
1308 # $href here would be something like http://gitweb.example.com/
1309 # Thus, we strip any trailing / from $href, to spare us double
1310 # slashes in the final URL
1311 $href =~ s,/$,,;
1312
1313 # Then add the project name, if present
Jakub Narebski67976c62010-12-14 16:54:31 +01001314 $href .= "/".esc_path_info($params{'project'});
Martin Waitz9e756902006-10-01 23:57:48 +02001315 delete $params{'project'};
1316
Giuseppe Bilottac752a0e2008-11-02 10:21:39 +01001317 # since we destructively absorb parameters, we keep this
1318 # boolean that remembers if we're handling a snapshot
1319 my $is_snapshot = $params{'action'} eq 'snapshot';
1320
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001321 # Summary just uses the project path URL, any other action is
1322 # added to the URL
1323 if (defined $params{'action'}) {
Jakub Narebski67976c62010-12-14 16:54:31 +01001324 $href .= "/".esc_path_info($params{'action'})
1325 unless $params{'action'} eq 'summary';
Martin Waitz9e756902006-10-01 23:57:48 +02001326 delete $params{'action'};
1327 }
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001328
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001329 # Next, we put hash_parent_base:/file_parent..hash_base:/file_name,
1330 # stripping nonexistent or useless pieces
1331 $href .= "/" if ($params{'hash_base'} || $params{'hash_parent_base'}
1332 || $params{'hash_parent'} || $params{'hash'});
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001333 if (defined $params{'hash_base'}) {
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001334 if (defined $params{'hash_parent_base'}) {
Jakub Narebski67976c62010-12-14 16:54:31 +01001335 $href .= esc_path_info($params{'hash_parent_base'});
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001336 # skip the file_parent if it's the same as the file_name
Giuseppe Bilottab7da7212009-07-31 08:48:49 +02001337 if (defined $params{'file_parent'}) {
1338 if (defined $params{'file_name'} && $params{'file_parent'} eq $params{'file_name'}) {
1339 delete $params{'file_parent'};
1340 } elsif ($params{'file_parent'} !~ /\.\./) {
Jakub Narebski67976c62010-12-14 16:54:31 +01001341 $href .= ":/".esc_path_info($params{'file_parent'});
Giuseppe Bilottab7da7212009-07-31 08:48:49 +02001342 delete $params{'file_parent'};
1343 }
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001344 }
1345 $href .= "..";
1346 delete $params{'hash_parent'};
1347 delete $params{'hash_parent_base'};
1348 } elsif (defined $params{'hash_parent'}) {
Jakub Narebski67976c62010-12-14 16:54:31 +01001349 $href .= esc_path_info($params{'hash_parent'}). "..";
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001350 delete $params{'hash_parent'};
1351 }
1352
Jakub Narebski67976c62010-12-14 16:54:31 +01001353 $href .= esc_path_info($params{'hash_base'});
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001354 if (defined $params{'file_name'} && $params{'file_name'} !~ /\.\./) {
Jakub Narebski67976c62010-12-14 16:54:31 +01001355 $href .= ":/".esc_path_info($params{'file_name'});
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001356 delete $params{'file_name'};
1357 }
1358 delete $params{'hash'};
1359 delete $params{'hash_base'};
1360 } elsif (defined $params{'hash'}) {
Jakub Narebski67976c62010-12-14 16:54:31 +01001361 $href .= esc_path_info($params{'hash'});
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001362 delete $params{'hash'};
1363 }
Giuseppe Bilottac752a0e2008-11-02 10:21:39 +01001364
1365 # If the action was a snapshot, we can absorb the
1366 # snapshot_format parameter too
1367 if ($is_snapshot) {
1368 my $fmt = $params{'snapshot_format'};
1369 # snapshot_format should always be defined when href()
1370 # is called, but just in case some code forgets, we
1371 # fall back to the default
1372 $fmt ||= $snapshot_fmts[0];
1373 $href .= $known_snapshot_formats{$fmt}{'suffix'};
1374 delete $params{'snapshot_format'};
1375 }
Martin Waitz9e756902006-10-01 23:57:48 +02001376 }
1377
1378 # now encode the parameters explicitly
Jakub Narebski498fe002006-08-22 19:05:25 +02001379 my @result = ();
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001380 for (my $i = 0; $i < @cgi_param_mapping; $i += 2) {
1381 my ($name, $symbol) = ($cgi_param_mapping[$i], $cgi_param_mapping[$i+1]);
Jakub Narebski498fe002006-08-22 19:05:25 +02001382 if (defined $params{$name}) {
Jakub Narebskif22cca42007-07-29 01:04:09 +02001383 if (ref($params{$name}) eq "ARRAY") {
1384 foreach my $par (@{$params{$name}}) {
1385 push @result, $symbol . "=" . esc_param($par);
1386 }
1387 } else {
1388 push @result, $symbol . "=" . esc_param($params{$name});
1389 }
Jakub Narebski498fe002006-08-22 19:05:25 +02001390 }
1391 }
Martin Waitz9e756902006-10-01 23:57:48 +02001392 $href .= "?" . join(';', @result) if scalar @result;
1393
Jakub Narebski67976c62010-12-14 16:54:31 +01001394 # final transformation: trailing spaces must be escaped (URI-encoded)
1395 $href =~ s/(\s+)$/CGI::escape($1)/e;
1396
Kevin Cernekee5e96a842011-03-18 17:00:16 +01001397 if ($params{-anchor}) {
1398 $href .= "#".esc_param($params{-anchor});
1399 }
1400
Martin Waitz9e756902006-10-01 23:57:48 +02001401 return $href;
Martin Waitz06a9d862006-08-16 00:23:50 +02001402}
1403
1404
1405## ======================================================================
Jakub Narebski717b8312006-07-31 21:22:15 +02001406## validation, quoting/unquoting and escaping
1407
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001408sub validate_action {
1409 my $input = shift || return undef;
1410 return undef unless exists $actions{$input};
1411 return $input;
1412}
1413
1414sub validate_project {
1415 my $input = shift || return undef;
1416 if (!validate_pathname($input) ||
1417 !(-d "$projectroot/$input") ||
Alexander Gavrilovec26f092008-11-06 01:15:56 +03001418 !check_export_ok("$projectroot/$input") ||
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001419 ($strict_export && !project_in_list($input))) {
1420 return undef;
1421 } else {
1422 return $input;
1423 }
1424}
1425
Jakub Narebski24d06932006-09-26 01:57:02 +02001426sub validate_pathname {
1427 my $input = shift || return undef;
Jakub Narebski717b8312006-07-31 21:22:15 +02001428
Jakub Narebski24d06932006-09-26 01:57:02 +02001429 # no '.' or '..' as elements of path, i.e. no '.' nor '..'
1430 # at the beginning, at the end, and between slashes.
1431 # also this catches doubled slashes
1432 if ($input =~ m!(^|/)(|\.|\.\.)(/|$)!) {
1433 return undef;
1434 }
1435 # no null characters
1436 if ($input =~ m!\0!) {
1437 return undef;
1438 }
1439 return $input;
1440}
1441
1442sub validate_refname {
1443 my $input = shift || return undef;
1444
1445 # textual hashes are O.K.
Jakub Narebski717b8312006-07-31 21:22:15 +02001446 if ($input =~ m/^[0-9a-fA-F]{40}$/) {
1447 return $input;
1448 }
Jakub Narebski24d06932006-09-26 01:57:02 +02001449 # it must be correct pathname
1450 $input = validate_pathname($input)
1451 or return undef;
1452 # restrictions on ref name according to git-check-ref-format
1453 if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001454 return undef;
1455 }
1456 return $input;
1457}
1458
Martin Koegler00f429a2007-06-03 17:42:44 +02001459# decode sequences of octets in utf8 into Perl's internal form,
1460# which is utf-8 with utf8 flag set if needed. gitweb writes out
1461# in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning
1462sub to_utf8 {
1463 my $str = shift;
Jakub Narebski1df48762010-02-07 21:52:25 +01001464 return undef unless defined $str;
Jakub Narebskib13e3ea2011-12-18 23:00:58 +01001465
1466 if (utf8::is_utf8($str) || utf8::decode($str)) {
İsmail Dönmeze5d3de52007-12-04 10:55:41 +02001467 return $str;
Martin Koegler00f429a2007-06-03 17:42:44 +02001468 } else {
1469 return decode($fallback_encoding, $str, Encode::FB_DEFAULT);
1470 }
1471}
1472
Kay Sievers232ff552005-11-24 16:56:55 +01001473# quote unsafe chars, but keep the slash, even when it's not
1474# correct, but quoted slashes look too horrible in bookmarks
1475sub esc_param {
Kay Sievers353347b2005-11-14 05:47:18 +01001476 my $str = shift;
Jakub Narebski1df48762010-02-07 21:52:25 +01001477 return undef unless defined $str;
Giuseppe Bilotta452e2252009-10-13 21:51:36 +02001478 $str =~ s/([^A-Za-z0-9\-_.~()\/:@ ]+)/CGI::escape($1)/eg;
Kay Sieversa9e60b72005-11-14 15:15:12 +01001479 $str =~ s/ /\+/g;
Kay Sievers353347b2005-11-14 05:47:18 +01001480 return $str;
1481}
1482
Jakub Narebski67976c62010-12-14 16:54:31 +01001483# the quoting rules for path_info fragment are slightly different
1484sub esc_path_info {
1485 my $str = shift;
1486 return undef unless defined $str;
1487
1488 # path_info doesn't treat '+' as space (specially), but '?' must be escaped
1489 $str =~ s/([^A-Za-z0-9\-_.~();\/;:@&= +]+)/CGI::escape($1)/eg;
1490
1491 return $str;
1492}
1493
Ralf Wildenhues22e5e582010-08-22 13:12:12 +02001494# quote unsafe chars in whole URL, so some characters cannot be quoted
Jakub Narebskif93bff82006-09-26 01:58:41 +02001495sub esc_url {
1496 my $str = shift;
Jakub Narebski1df48762010-02-07 21:52:25 +01001497 return undef unless defined $str;
Pavan Kumar Sunkara109988f2010-07-15 12:59:01 +05301498 $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&= ]+)/CGI::escape($1)/eg;
Jakub Narebskif93bff82006-09-26 01:58:41 +02001499 $str =~ s/ /\+/g;
1500 return $str;
1501}
1502
Jakub Narebski3017ed62010-12-15 00:34:01 +01001503# quote unsafe characters in HTML attributes
1504sub esc_attr {
1505
1506 # for XHTML conformance escaping '"' to '&quot;' is not enough
1507 return esc_html(@_);
1508}
1509
Kay Sievers232ff552005-11-24 16:56:55 +01001510# replace invalid utf8 character with SUBSTITUTION sequence
Jakub Narebski74fd8722009-05-07 19:11:29 +02001511sub esc_html {
Kay Sievers40c13812005-11-19 17:41:29 +01001512 my $str = shift;
Jakub Narebski6255ef02006-11-01 14:33:21 +01001513 my %opts = @_;
1514
Jakub Narebski1df48762010-02-07 21:52:25 +01001515 return undef unless defined $str;
1516
Martin Koegler00f429a2007-06-03 17:42:44 +02001517 $str = to_utf8($str);
Li Yangc390ae92007-03-06 11:58:56 +08001518 $str = $cgi->escapeHTML($str);
Jakub Narebski6255ef02006-11-01 14:33:21 +01001519 if ($opts{'-nbsp'}) {
1520 $str =~ s/ /&nbsp;/g;
1521 }
Junio C Hamano25ffbb22006-11-08 15:11:10 -08001522 $str =~ s|([[:cntrl:]])|(($1 ne "\t") ? quot_cec($1) : $1)|eg;
Kay Sievers40c13812005-11-19 17:41:29 +01001523 return $str;
1524}
1525
Jakub Narebski391862e2006-11-25 09:43:59 +01001526# quote control characters and escape filename to HTML
1527sub esc_path {
1528 my $str = shift;
1529 my %opts = @_;
1530
Jakub Narebski1df48762010-02-07 21:52:25 +01001531 return undef unless defined $str;
1532
Martin Koegler00f429a2007-06-03 17:42:44 +02001533 $str = to_utf8($str);
Li Yangc390ae92007-03-06 11:58:56 +08001534 $str = $cgi->escapeHTML($str);
Jakub Narebski391862e2006-11-25 09:43:59 +01001535 if ($opts{'-nbsp'}) {
1536 $str =~ s/ /&nbsp;/g;
1537 }
1538 $str =~ s|([[:cntrl:]])|quot_cec($1)|eg;
1539 return $str;
1540}
1541
Jakub Narebski08667862011-09-16 14:41:57 +02001542# Sanitize for use in XHTML + application/xml+xhtm (valid XML 1.0)
1543sub sanitize {
1544 my $str = shift;
1545
1546 return undef unless defined $str;
1547
1548 $str = to_utf8($str);
1549 $str =~ s|([[:cntrl:]])|($1 =~ /[\t\n\r]/ ? $1 : quot_cec($1))|eg;
1550 return $str;
1551}
1552
Jakub Narebski391862e2006-11-25 09:43:59 +01001553# Make control characters "printable", using character escape codes (CEC)
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001554sub quot_cec {
1555 my $cntrl = shift;
Jakub Narebskic84c4832008-02-17 18:48:13 +01001556 my %opts = @_;
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001557 my %es = ( # character escape codes, aka escape sequences
Jakub Narebskic84c4832008-02-17 18:48:13 +01001558 "\t" => '\t', # tab (HT)
1559 "\n" => '\n', # line feed (LF)
1560 "\r" => '\r', # carrige return (CR)
1561 "\f" => '\f', # form feed (FF)
1562 "\b" => '\b', # backspace (BS)
1563 "\a" => '\a', # alarm (bell) (BEL)
1564 "\e" => '\e', # escape (ESC)
1565 "\013" => '\v', # vertical tab (VT)
1566 "\000" => '\0', # nul character (NUL)
1567 );
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001568 my $chr = ( (exists $es{$cntrl})
1569 ? $es{$cntrl}
Petr Baudis25dfd172008-10-01 22:11:54 +02001570 : sprintf('\%2x', ord($cntrl)) );
Jakub Narebskic84c4832008-02-17 18:48:13 +01001571 if ($opts{-nohtml}) {
1572 return $chr;
1573 } else {
1574 return "<span class=\"cntrl\">$chr</span>";
1575 }
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001576}
1577
Jakub Narebski391862e2006-11-25 09:43:59 +01001578# Alternatively use unicode control pictures codepoints,
1579# Unicode "printable representation" (PR)
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001580sub quot_upr {
1581 my $cntrl = shift;
Jakub Narebskic84c4832008-02-17 18:48:13 +01001582 my %opts = @_;
1583
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001584 my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
Jakub Narebskic84c4832008-02-17 18:48:13 +01001585 if ($opts{-nohtml}) {
1586 return $chr;
1587 } else {
1588 return "<span class=\"cntrl\">$chr</span>";
1589 }
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001590}
1591
Kay Sievers232ff552005-11-24 16:56:55 +01001592# git may return quoted and escaped filenames
1593sub unquote {
1594 my $str = shift;
Jakub Narebski403d0902006-11-08 11:48:56 +01001595
1596 sub unq {
1597 my $seq = shift;
1598 my %es = ( # character escape codes, aka escape sequences
1599 't' => "\t", # tab (HT, TAB)
1600 'n' => "\n", # newline (NL)
1601 'r' => "\r", # return (CR)
1602 'f' => "\f", # form feed (FF)
1603 'b' => "\b", # backspace (BS)
1604 'a' => "\a", # alarm (bell) (BEL)
1605 'e' => "\e", # escape (ESC)
1606 'v' => "\013", # vertical tab (VT)
1607 );
1608
1609 if ($seq =~ m/^[0-7]{1,3}$/) {
1610 # octal char sequence
1611 return chr(oct($seq));
1612 } elsif (exists $es{$seq}) {
1613 # C escape sequence, aka character escape code
Jakub Narebskic84c4832008-02-17 18:48:13 +01001614 return $es{$seq};
Jakub Narebski403d0902006-11-08 11:48:56 +01001615 }
1616 # quoted ordinary character
1617 return $seq;
1618 }
1619
Kay Sievers232ff552005-11-24 16:56:55 +01001620 if ($str =~ m/^"(.*)"$/) {
Jakub Narebski403d0902006-11-08 11:48:56 +01001621 # needs unquoting
Kay Sievers232ff552005-11-24 16:56:55 +01001622 $str = $1;
Jakub Narebski403d0902006-11-08 11:48:56 +01001623 $str =~ s/\\([^0-7]|[0-7]{1,3})/unq($1)/eg;
Kay Sievers232ff552005-11-24 16:56:55 +01001624 }
1625 return $str;
1626}
1627
Jakub Narebskif16db172006-08-06 02:08:31 +02001628# escape tabs (convert tabs to spaces)
1629sub untabify {
1630 my $line = shift;
1631
1632 while ((my $pos = index($line, "\t")) != -1) {
1633 if (my $count = (8 - ($pos % 8))) {
1634 my $spaces = ' ' x $count;
1635 $line =~ s/\t/$spaces/;
1636 }
1637 }
1638
1639 return $line;
1640}
1641
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +02001642sub project_in_list {
1643 my $project = shift;
1644 my @list = git_get_projects_list();
1645 return @list && scalar(grep { $_->{'path'} eq $project } @list);
1646}
1647
Jakub Narebski717b8312006-07-31 21:22:15 +02001648## ----------------------------------------------------------------------
1649## HTML aware string manipulation
1650
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001651# Try to chop given string on a word boundary between position
1652# $len and $len+$add_len. If there is no word boundary there,
1653# chop at $len+$add_len. Do not chop if chopped part plus ellipsis
1654# (marking chopped part) would be longer than given string.
Jakub Narebski717b8312006-07-31 21:22:15 +02001655sub chop_str {
1656 my $str = shift;
1657 my $len = shift;
1658 my $add_len = shift || 10;
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001659 my $where = shift || 'right'; # 'left' | 'center' | 'right'
Jakub Narebski717b8312006-07-31 21:22:15 +02001660
Anders Waldenborgdee27752008-05-21 13:44:43 +02001661 # Make sure perl knows it is utf8 encoded so we don't
1662 # cut in the middle of a utf8 multibyte char.
1663 $str = to_utf8($str);
1664
Jakub Narebski717b8312006-07-31 21:22:15 +02001665 # allow only $len chars, but don't cut a word if it would fit in $add_len
1666 # 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 +01001667 # remove chopped character entities entirely
1668
1669 # when chopping in the middle, distribute $len into left and right part
1670 # return early if chopping wouldn't make string shorter
1671 if ($where eq 'center') {
1672 return $str if ($len + 5 >= length($str)); # filler is length 5
1673 $len = int($len/2);
1674 } else {
1675 return $str if ($len + 4 >= length($str)); # filler is length 4
Jakub Narebski717b8312006-07-31 21:22:15 +02001676 }
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001677
1678 # regexps: ending and beginning with word part up to $add_len
1679 my $endre = qr/.{$len}\w{0,$add_len}/;
1680 my $begre = qr/\w{0,$add_len}.{$len}/;
1681
1682 if ($where eq 'left') {
1683 $str =~ m/^(.*?)($begre)$/;
1684 my ($lead, $body) = ($1, $2);
1685 if (length($lead) > 4) {
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001686 $lead = " ...";
1687 }
1688 return "$lead$body";
1689
1690 } elsif ($where eq 'center') {
1691 $str =~ m/^($endre)(.*)$/;
1692 my ($left, $str) = ($1, $2);
1693 $str =~ m/^(.*?)($begre)$/;
1694 my ($mid, $right) = ($1, $2);
1695 if (length($mid) > 5) {
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001696 $mid = " ... ";
1697 }
1698 return "$left$mid$right";
1699
1700 } else {
1701 $str =~ m/^($endre)(.*)$/;
1702 my $body = $1;
1703 my $tail = $2;
1704 if (length($tail) > 4) {
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001705 $tail = "... ";
1706 }
1707 return "$body$tail";
1708 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001709}
1710
David Symondsce58ec92007-10-23 11:31:22 +10001711# takes the same arguments as chop_str, but also wraps a <span> around the
1712# result with a title attribute if it does get chopped. Additionally, the
1713# string is HTML-escaped.
1714sub chop_and_escape_str {
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001715 my ($str) = @_;
David Symondsce58ec92007-10-23 11:31:22 +10001716
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001717 my $chopped = chop_str(@_);
Jürgen Kreileder168c1e02011-12-17 10:22:21 +01001718 $str = to_utf8($str);
David Symondsce58ec92007-10-23 11:31:22 +10001719 if ($chopped eq $str) {
1720 return esc_html($chopped);
1721 } else {
Jakub Narebski14afe772009-05-22 17:35:46 +02001722 $str =~ s/[[:cntrl:]]/?/g;
Jakub Narebski850b90a2008-02-16 23:07:46 +01001723 return $cgi->span({-title=>$str}, esc_html($chopped));
David Symondsce58ec92007-10-23 11:31:22 +10001724 }
1725}
1726
Jakub Narebski337da8d2012-02-27 02:55:19 +01001727# Highlight selected fragments of string, using given CSS class,
1728# and escape HTML. It is assumed that fragments do not overlap.
1729# Regions are passed as list of pairs (array references).
1730#
1731# Example: esc_html_hl_regions("foobar", "mark", [ 0, 3 ]) returns
1732# '<span class="mark">foo</span>bar'
1733sub esc_html_hl_regions {
1734 my ($str, $css_class, @sel) = @_;
Jakub Narębski9768a9d2012-04-11 23:18:39 +02001735 my %opts = grep { ref($_) ne 'ARRAY' } @sel;
1736 @sel = grep { ref($_) eq 'ARRAY' } @sel;
1737 return esc_html($str, %opts) unless @sel;
Jakub Narebski337da8d2012-02-27 02:55:19 +01001738
1739 my $out = '';
1740 my $pos = 0;
1741
1742 for my $s (@sel) {
Michał Kiedrowiczce61fb92012-04-11 23:18:37 +02001743 my ($begin, $end) = @$s;
Jakub Narebski337da8d2012-02-27 02:55:19 +01001744
Michał Kiedrowiczcbbea3d2012-04-11 23:18:38 +02001745 # Don't create empty <span> elements.
1746 next if $end <= $begin;
1747
Jakub Narębski9768a9d2012-04-11 23:18:39 +02001748 my $escaped = esc_html(substr($str, $begin, $end - $begin),
1749 %opts);
Michał Kiedrowiczce61fb92012-04-11 23:18:37 +02001750
Jakub Narębski9768a9d2012-04-11 23:18:39 +02001751 $out .= esc_html(substr($str, $pos, $begin - $pos), %opts)
Michał Kiedrowiczce61fb92012-04-11 23:18:37 +02001752 if ($begin - $pos > 0);
1753 $out .= $cgi->span({-class => $css_class}, $escaped);
1754
1755 $pos = $end;
Jakub Narebski337da8d2012-02-27 02:55:19 +01001756 }
Jakub Narębski9768a9d2012-04-11 23:18:39 +02001757 $out .= esc_html(substr($str, $pos), %opts)
Jakub Narebski337da8d2012-02-27 02:55:19 +01001758 if ($pos < length($str));
1759
1760 return $out;
1761}
1762
Jakub Narebskie607b792012-02-27 02:55:22 +01001763# return positions of beginning and end of each match
1764sub matchpos_list {
Jakub Narebski337da8d2012-02-27 02:55:19 +01001765 my ($str, $regexp) = @_;
Jakub Narebskie607b792012-02-27 02:55:22 +01001766 return unless (defined $str && defined $regexp);
Jakub Narebski337da8d2012-02-27 02:55:19 +01001767
1768 my @matches;
1769 while ($str =~ /$regexp/g) {
1770 push @matches, [$-[0], $+[0]];
1771 }
Jakub Narebskie607b792012-02-27 02:55:22 +01001772 return @matches;
1773}
1774
1775# highlight match (if any), and escape HTML
1776sub esc_html_match_hl {
1777 my ($str, $regexp) = @_;
1778 return esc_html($str) unless defined $regexp;
1779
1780 my @matches = matchpos_list($str, $regexp);
Jakub Narebski337da8d2012-02-27 02:55:19 +01001781 return esc_html($str) unless @matches;
1782
1783 return esc_html_hl_regions($str, 'match', @matches);
1784}
1785
Jakub Narebskie607b792012-02-27 02:55:22 +01001786
1787# highlight match (if any) of shortened string, and escape HTML
1788sub esc_html_match_hl_chopped {
1789 my ($str, $chopped, $regexp) = @_;
1790 return esc_html_match_hl($str, $regexp) unless defined $chopped;
1791
1792 my @matches = matchpos_list($str, $regexp);
1793 return esc_html($chopped) unless @matches;
1794
1795 # filter matches so that we mark chopped string
1796 my $tail = "... "; # see chop_str
1797 unless ($chopped =~ s/\Q$tail\E$//) {
1798 $tail = '';
1799 }
1800 my $chop_len = length($chopped);
1801 my $tail_len = length($tail);
1802 my @filtered;
1803
1804 for my $m (@matches) {
1805 if ($m->[0] > $chop_len) {
1806 push @filtered, [ $chop_len, $chop_len + $tail_len ] if ($tail_len > 0);
1807 last;
1808 } elsif ($m->[1] > $chop_len) {
1809 push @filtered, [ $m->[0], $chop_len + $tail_len ];
1810 last;
1811 }
1812 push @filtered, $m;
1813 }
1814
1815 return esc_html_hl_regions($chopped . $tail, 'match', @filtered);
1816}
1817
Jakub Narebski717b8312006-07-31 21:22:15 +02001818## ----------------------------------------------------------------------
1819## functions returning short strings
1820
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00001821# CSS class for given age value (in seconds)
1822sub age_class {
1823 my $age = shift;
1824
Jakub Narebski785cdea2007-05-13 12:39:22 +02001825 if (!defined $age) {
1826 return "noage";
1827 } elsif ($age < 60*60*2) {
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00001828 return "age0";
1829 } elsif ($age < 60*60*24*2) {
1830 return "age1";
1831 } else {
1832 return "age2";
1833 }
1834}
1835
Jakub Narebski717b8312006-07-31 21:22:15 +02001836# convert age in seconds to "nn units ago" string
1837sub age_string {
1838 my $age = shift;
1839 my $age_str;
1840
1841 if ($age > 60*60*24*365*2) {
1842 $age_str = (int $age/60/60/24/365);
1843 $age_str .= " years ago";
1844 } elsif ($age > 60*60*24*(365/12)*2) {
1845 $age_str = int $age/60/60/24/(365/12);
1846 $age_str .= " months ago";
1847 } elsif ($age > 60*60*24*7*2) {
1848 $age_str = int $age/60/60/24/7;
1849 $age_str .= " weeks ago";
1850 } elsif ($age > 60*60*24*2) {
1851 $age_str = int $age/60/60/24;
1852 $age_str .= " days ago";
1853 } elsif ($age > 60*60*2) {
1854 $age_str = int $age/60/60;
1855 $age_str .= " hours ago";
1856 } elsif ($age > 60*2) {
1857 $age_str = int $age/60;
1858 $age_str .= " min ago";
1859 } elsif ($age > 2) {
1860 $age_str = int $age;
1861 $age_str .= " sec ago";
1862 } else {
1863 $age_str .= " right now";
1864 }
1865 return $age_str;
1866}
1867
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001868use constant {
1869 S_IFINVALID => 0030000,
1870 S_IFGITLINK => 0160000,
1871};
1872
1873# submodule/subproject, a commit object reference
Jakub Narebski74fd8722009-05-07 19:11:29 +02001874sub S_ISGITLINK {
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001875 my $mode = shift;
1876
1877 return (($mode & S_IFMT) == S_IFGITLINK)
1878}
1879
Jakub Narebski717b8312006-07-31 21:22:15 +02001880# convert file mode in octal to symbolic file mode string
1881sub mode_str {
1882 my $mode = oct shift;
1883
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001884 if (S_ISGITLINK($mode)) {
1885 return 'm---------';
1886 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001887 return 'drwxr-xr-x';
1888 } elsif (S_ISLNK($mode)) {
1889 return 'lrwxrwxrwx';
1890 } elsif (S_ISREG($mode)) {
1891 # git cares only about the executable bit
1892 if ($mode & S_IXUSR) {
1893 return '-rwxr-xr-x';
1894 } else {
1895 return '-rw-r--r--';
1896 };
1897 } else {
1898 return '----------';
1899 }
1900}
1901
1902# convert file mode in octal to file type string
1903sub file_type {
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02001904 my $mode = shift;
1905
1906 if ($mode !~ m/^[0-7]+$/) {
1907 return $mode;
1908 } else {
1909 $mode = oct $mode;
1910 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001911
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001912 if (S_ISGITLINK($mode)) {
1913 return "submodule";
1914 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001915 return "directory";
1916 } elsif (S_ISLNK($mode)) {
1917 return "symlink";
1918 } elsif (S_ISREG($mode)) {
1919 return "file";
1920 } else {
1921 return "unknown";
1922 }
1923}
1924
Jakub Narebski744d0ac2006-11-08 17:59:41 +01001925# convert file mode in octal to file type description string
1926sub file_type_long {
1927 my $mode = shift;
1928
1929 if ($mode !~ m/^[0-7]+$/) {
1930 return $mode;
1931 } else {
1932 $mode = oct $mode;
1933 }
1934
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001935 if (S_ISGITLINK($mode)) {
1936 return "submodule";
1937 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski744d0ac2006-11-08 17:59:41 +01001938 return "directory";
1939 } elsif (S_ISLNK($mode)) {
1940 return "symlink";
1941 } elsif (S_ISREG($mode)) {
1942 if ($mode & S_IXUSR) {
1943 return "executable";
1944 } else {
1945 return "file";
1946 };
1947 } else {
1948 return "unknown";
1949 }
1950}
1951
1952
Jakub Narebski717b8312006-07-31 21:22:15 +02001953## ----------------------------------------------------------------------
1954## functions returning short HTML fragments, or transforming HTML fragments
Pavel Roskin3dff5372007-02-03 23:49:16 -05001955## which don't belong to other sections
Jakub Narebski717b8312006-07-31 21:22:15 +02001956
Junio C Hamano225932e2006-11-09 00:57:13 -08001957# format line of commit message.
Jakub Narebski717b8312006-07-31 21:22:15 +02001958sub format_log_line_html {
1959 my $line = shift;
1960
Junio C Hamano225932e2006-11-09 00:57:13 -08001961 $line = esc_html($line, -nbsp=>1);
Marcel M. Cary7d233de2009-02-17 19:00:43 -08001962 $line =~ s{\b([0-9a-fA-F]{8,40})\b}{
1963 $cgi->a({-href => href(action=>"object", hash=>$1),
1964 -class => "text"}, $1);
1965 }eg;
1966
Jakub Narebski717b8312006-07-31 21:22:15 +02001967 return $line;
1968}
1969
1970# format marker of refs pointing to given object
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02001971
1972# the destination action is chosen based on object type and current context:
1973# - for annotated tags, we choose the tag view unless it's the current view
1974# already, in which case we go to shortlog view
1975# - for other refs, we keep the current view if we're in history, shortlog or
1976# log view, and select shortlog otherwise
Jakub Narebski847e01f2006-08-14 02:05:47 +02001977sub format_ref_marker {
Jakub Narebski717b8312006-07-31 21:22:15 +02001978 my ($refs, $id) = @_;
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001979 my $markers = '';
Jakub Narebski717b8312006-07-31 21:22:15 +02001980
1981 if (defined $refs->{$id}) {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001982 foreach my $ref (@{$refs->{$id}}) {
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02001983 # this code exploits the fact that non-lightweight tags are the
1984 # only indirect objects, and that they are the only objects for which
1985 # we want to use tag instead of shortlog as action
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001986 my ($type, $name) = qw();
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02001987 my $indirect = ($ref =~ s/\^\{\}$//);
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001988 # e.g. tags/v2.6.11 or heads/next
1989 if ($ref =~ m!^(.*?)s?/(.*)$!) {
1990 $type = $1;
1991 $name = $2;
1992 } else {
1993 $type = "ref";
1994 $name = $ref;
1995 }
1996
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02001997 my $class = $type;
1998 $class .= " indirect" if $indirect;
1999
2000 my $dest_action = "shortlog";
2001
2002 if ($indirect) {
2003 $dest_action = "tag" unless $action eq "tag";
2004 } elsif ($action =~ /^(history|(short)?log)$/) {
2005 $dest_action = $action;
2006 }
2007
2008 my $dest = "";
2009 $dest .= "refs/" unless $ref =~ m!^refs/!;
2010 $dest .= $ref;
2011
2012 my $link = $cgi->a({
2013 -href => href(
2014 action=>$dest_action,
2015 hash=>$dest
2016 )}, $name);
2017
Jakub Narebski3017ed62010-12-15 00:34:01 +01002018 $markers .= " <span class=\"".esc_attr($class)."\" title=\"".esc_attr($ref)."\">" .
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02002019 $link . "</span>";
Jakub Narebskid294e1c2006-08-14 02:14:20 +02002020 }
2021 }
2022
2023 if ($markers) {
2024 return ' <span class="refs">'. $markers . '</span>';
Jakub Narebski717b8312006-07-31 21:22:15 +02002025 } else {
2026 return "";
2027 }
2028}
2029
Jakub Narebski17d07442006-08-14 02:08:27 +02002030# format, perhaps shortened and with markers, title line
2031sub format_subject_html {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02002032 my ($long, $short, $href, $extra) = @_;
Jakub Narebski17d07442006-08-14 02:08:27 +02002033 $extra = '' unless defined($extra);
2034
2035 if (length($short) < length($long)) {
Jakub Narebski14afe772009-05-22 17:35:46 +02002036 $long =~ s/[[:cntrl:]]/?/g;
Jakub Narebski7c278012006-08-22 12:02:48 +02002037 return $cgi->a({-href => $href, -class => "list subject",
Martin Koegler00f429a2007-06-03 17:42:44 +02002038 -title => to_utf8($long)},
Giuseppe Bilotta01b89f02009-08-23 10:28:09 +02002039 esc_html($short)) . $extra;
Jakub Narebski17d07442006-08-14 02:08:27 +02002040 } else {
Jakub Narebski7c278012006-08-22 12:02:48 +02002041 return $cgi->a({-href => $href, -class => "list subject"},
Giuseppe Bilotta01b89f02009-08-23 10:28:09 +02002042 esc_html($long)) . $extra;
Jakub Narebski17d07442006-08-14 02:08:27 +02002043 }
2044}
2045
Giuseppe Bilotta5a371b72009-06-30 00:00:52 +02002046# Rather than recomputing the url for an email multiple times, we cache it
2047# after the first hit. This gives a visible benefit in views where the avatar
2048# for the same email is used repeatedly (e.g. shortlog).
2049# The cache is shared by all avatar engines (currently gravatar only), which
2050# are free to use it as preferred. Since only one avatar engine is used for any
2051# given page, there's no risk for cache conflicts.
2052our %avatar_cache = ();
2053
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +02002054# Compute the picon url for a given email, by using the picon search service over at
2055# http://www.cs.indiana.edu/picons/search.html
2056sub picon_url {
2057 my $email = lc shift;
2058 if (!$avatar_cache{$email}) {
2059 my ($user, $domain) = split('@', $email);
2060 $avatar_cache{$email} =
2061 "http://www.cs.indiana.edu/cgi-pub/kinzler/piconsearch.cgi/" .
2062 "$domain/$user/" .
2063 "users+domains+unknown/up/single";
2064 }
2065 return $avatar_cache{$email};
2066}
2067
Giuseppe Bilotta5a371b72009-06-30 00:00:52 +02002068# Compute the gravatar url for a given email, if it's not in the cache already.
2069# Gravatar stores only the part of the URL before the size, since that's the
2070# one computationally more expensive. This also allows reuse of the cache for
2071# different sizes (for this particular engine).
2072sub gravatar_url {
2073 my $email = lc shift;
2074 my $size = shift;
2075 $avatar_cache{$email} ||=
2076 "http://www.gravatar.com/avatar/" .
2077 Digest::MD5::md5_hex($email) . "?s=";
2078 return $avatar_cache{$email} . $size;
2079}
2080
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02002081# Insert an avatar for the given $email at the given $size if the feature
2082# is enabled.
2083sub git_get_avatar {
2084 my ($email, %opts) = @_;
2085 my $pre_white = ($opts{-pad_before} ? "&nbsp;" : "");
2086 my $post_white = ($opts{-pad_after} ? "&nbsp;" : "");
2087 $opts{-size} ||= 'default';
2088 my $size = $avatar_size{$opts{-size}} || $avatar_size{'default'};
2089 my $url = "";
2090 if ($git_avatar eq 'gravatar') {
Giuseppe Bilotta5a371b72009-06-30 00:00:52 +02002091 $url = gravatar_url($email, $size);
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +02002092 } elsif ($git_avatar eq 'picon') {
2093 $url = picon_url($email);
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02002094 }
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +02002095 # Other providers can be added by extending the if chain, defining $url
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02002096 # as needed. If no variant puts something in $url, we assume avatars
2097 # are completely disabled/unavailable.
2098 if ($url) {
2099 return $pre_white .
2100 "<img width=\"$size\" " .
2101 "class=\"avatar\" " .
Jakub Narebski3017ed62010-12-15 00:34:01 +01002102 "src=\"".esc_url($url)."\" " .
Giuseppe Bilotta7d25ef42009-06-30 00:00:54 +02002103 "alt=\"\" " .
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02002104 "/>" . $post_white;
2105 } else {
2106 return "";
2107 }
2108}
2109
Stephen Boyde133d652009-10-15 21:14:59 -07002110sub format_search_author {
2111 my ($author, $searchtype, $displaytext) = @_;
2112 my $have_search = gitweb_check_feature('search');
2113
2114 if ($have_search) {
2115 my $performed = "";
2116 if ($searchtype eq 'author') {
2117 $performed = "authored";
2118 } elsif ($searchtype eq 'committer') {
2119 $performed = "committed";
2120 }
2121
2122 return $cgi->a({-href => href(action=>"search", hash=>$hash,
2123 searchtext=>$author,
2124 searchtype=>$searchtype), class=>"list",
2125 title=>"Search for commits $performed by $author"},
2126 $displaytext);
2127
2128 } else {
2129 return $displaytext;
2130 }
2131}
2132
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02002133# format the author name of the given commit with the given tag
2134# the author name is chopped and escaped according to the other
2135# optional parameters (see chop_str).
2136sub format_author_html {
2137 my $tag = shift;
2138 my $co = shift;
2139 my $author = chop_and_escape_str($co->{'author_name'}, @_);
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02002140 return "<$tag class=\"author\">" .
Stephen Boyde133d652009-10-15 21:14:59 -07002141 format_search_author($co->{'author_name'}, "author",
2142 git_get_avatar($co->{'author_email'}, -pad_after => 1) .
2143 $author) .
2144 "</$tag>";
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02002145}
2146
Jakub Narebski90921742007-06-08 13:27:42 +02002147# format git diff header line, i.e. "diff --(git|combined|cc) ..."
2148sub format_git_diff_header_line {
2149 my $line = shift;
2150 my $diffinfo = shift;
2151 my ($from, $to) = @_;
2152
2153 if ($diffinfo->{'nparents'}) {
2154 # combined diff
2155 $line =~ s!^(diff (.*?) )"?.*$!$1!;
2156 if ($to->{'href'}) {
2157 $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
2158 esc_path($to->{'file'}));
2159 } else { # file was deleted (no href)
2160 $line .= esc_path($to->{'file'});
2161 }
2162 } else {
2163 # "ordinary" diff
2164 $line =~ s!^(diff (.*?) )"?a/.*$!$1!;
2165 if ($from->{'href'}) {
2166 $line .= $cgi->a({-href => $from->{'href'}, -class => "path"},
2167 'a/' . esc_path($from->{'file'}));
2168 } else { # file was added (no href)
2169 $line .= 'a/' . esc_path($from->{'file'});
2170 }
2171 $line .= ' ';
2172 if ($to->{'href'}) {
2173 $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
2174 'b/' . esc_path($to->{'file'}));
2175 } else { # file was deleted
2176 $line .= 'b/' . esc_path($to->{'file'});
2177 }
2178 }
2179
2180 return "<div class=\"diff header\">$line</div>\n";
2181}
2182
2183# format extended diff header line, before patch itself
2184sub format_extended_diff_header_line {
2185 my $line = shift;
2186 my $diffinfo = shift;
2187 my ($from, $to) = @_;
2188
2189 # match <path>
2190 if ($line =~ s!^((copy|rename) from ).*$!$1! && $from->{'href'}) {
2191 $line .= $cgi->a({-href=>$from->{'href'}, -class=>"path"},
2192 esc_path($from->{'file'}));
2193 }
2194 if ($line =~ s!^((copy|rename) to ).*$!$1! && $to->{'href'}) {
2195 $line .= $cgi->a({-href=>$to->{'href'}, -class=>"path"},
2196 esc_path($to->{'file'}));
2197 }
2198 # match single <mode>
2199 if ($line =~ m/\s(\d{6})$/) {
2200 $line .= '<span class="info"> (' .
2201 file_type_long($1) .
2202 ')</span>';
2203 }
2204 # match <hash>
2205 if ($line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
2206 # can match only for combined diff
2207 $line = 'index ';
2208 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
2209 if ($from->{'href'}[$i]) {
2210 $line .= $cgi->a({-href=>$from->{'href'}[$i],
2211 -class=>"hash"},
2212 substr($diffinfo->{'from_id'}[$i],0,7));
2213 } else {
2214 $line .= '0' x 7;
2215 }
2216 # separator
2217 $line .= ',' if ($i < $diffinfo->{'nparents'} - 1);
2218 }
2219 $line .= '..';
2220 if ($to->{'href'}) {
2221 $line .= $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
2222 substr($diffinfo->{'to_id'},0,7));
2223 } else {
2224 $line .= '0' x 7;
2225 }
2226
2227 } elsif ($line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
2228 # can match only for ordinary diff
2229 my ($from_link, $to_link);
2230 if ($from->{'href'}) {
2231 $from_link = $cgi->a({-href=>$from->{'href'}, -class=>"hash"},
2232 substr($diffinfo->{'from_id'},0,7));
2233 } else {
2234 $from_link = '0' x 7;
2235 }
2236 if ($to->{'href'}) {
2237 $to_link = $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
2238 substr($diffinfo->{'to_id'},0,7));
2239 } else {
2240 $to_link = '0' x 7;
2241 }
2242 my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
2243 $line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
2244 }
2245
2246 return $line . "<br/>\n";
2247}
2248
2249# format from-file/to-file diff header
2250sub format_diff_from_to_header {
Jakub Narebski91af4ce2007-06-08 13:32:44 +02002251 my ($from_line, $to_line, $diffinfo, $from, $to, @parents) = @_;
Jakub Narebski90921742007-06-08 13:27:42 +02002252 my $line;
2253 my $result = '';
2254
2255 $line = $from_line;
2256 #assert($line =~ m/^---/) if DEBUG;
Jakub Narebskideaa01a2007-06-08 13:29:49 +02002257 # no extra formatting for "^--- /dev/null"
2258 if (! $diffinfo->{'nparents'}) {
2259 # ordinary (single parent) diff
2260 if ($line =~ m!^--- "?a/!) {
2261 if ($from->{'href'}) {
2262 $line = '--- a/' .
2263 $cgi->a({-href=>$from->{'href'}, -class=>"path"},
2264 esc_path($from->{'file'}));
2265 } else {
2266 $line = '--- a/' .
2267 esc_path($from->{'file'});
2268 }
2269 }
2270 $result .= qq!<div class="diff from_file">$line</div>\n!;
2271
2272 } else {
2273 # combined diff (merge commit)
2274 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
2275 if ($from->{'href'}[$i]) {
2276 $line = '--- ' .
Jakub Narebski91af4ce2007-06-08 13:32:44 +02002277 $cgi->a({-href=>href(action=>"blobdiff",
2278 hash_parent=>$diffinfo->{'from_id'}[$i],
2279 hash_parent_base=>$parents[$i],
2280 file_parent=>$from->{'file'}[$i],
2281 hash=>$diffinfo->{'to_id'},
2282 hash_base=>$hash,
2283 file_name=>$to->{'file'}),
2284 -class=>"path",
2285 -title=>"diff" . ($i+1)},
2286 $i+1) .
2287 '/' .
Jakub Narebskideaa01a2007-06-08 13:29:49 +02002288 $cgi->a({-href=>$from->{'href'}[$i], -class=>"path"},
2289 esc_path($from->{'file'}[$i]));
2290 } else {
2291 $line = '--- /dev/null';
2292 }
2293 $result .= qq!<div class="diff from_file">$line</div>\n!;
Jakub Narebski90921742007-06-08 13:27:42 +02002294 }
2295 }
Jakub Narebski90921742007-06-08 13:27:42 +02002296
2297 $line = $to_line;
2298 #assert($line =~ m/^\+\+\+/) if DEBUG;
2299 # no extra formatting for "^+++ /dev/null"
2300 if ($line =~ m!^\+\+\+ "?b/!) {
2301 if ($to->{'href'}) {
2302 $line = '+++ b/' .
2303 $cgi->a({-href=>$to->{'href'}, -class=>"path"},
2304 esc_path($to->{'file'}));
2305 } else {
2306 $line = '+++ b/' .
2307 esc_path($to->{'file'});
2308 }
2309 }
2310 $result .= qq!<div class="diff to_file">$line</div>\n!;
2311
2312 return $result;
2313}
2314
Jakub Narebskicd030c32007-06-08 13:33:28 +02002315# create note for patch simplified by combined diff
2316sub format_diff_cc_simplified {
2317 my ($diffinfo, @parents) = @_;
2318 my $result = '';
2319
2320 $result .= "<div class=\"diff header\">" .
2321 "diff --cc ";
2322 if (!is_deleted($diffinfo)) {
2323 $result .= $cgi->a({-href => href(action=>"blob",
2324 hash_base=>$hash,
2325 hash=>$diffinfo->{'to_id'},
2326 file_name=>$diffinfo->{'to_file'}),
2327 -class => "path"},
2328 esc_path($diffinfo->{'to_file'}));
2329 } else {
2330 $result .= esc_path($diffinfo->{'to_file'});
2331 }
2332 $result .= "</div>\n" . # class="diff header"
2333 "<div class=\"diff nodifferences\">" .
2334 "Simple merge" .
2335 "</div>\n"; # class="diff nodifferences"
2336
2337 return $result;
2338}
2339
Jakub Narebski20a864c2011-10-31 00:36:20 +01002340sub diff_line_class {
2341 my ($line, $from, $to) = @_;
2342
2343 # ordinary diff
2344 my $num_sign = 1;
2345 # combined diff
2346 if ($from && $to && ref($from->{'href'}) eq "ARRAY") {
2347 $num_sign = scalar @{$from->{'href'}};
2348 }
2349
2350 my @diff_line_classifier = (
2351 { regexp => qr/^\@\@{$num_sign} /, class => "chunk_header"},
2352 { regexp => qr/^\\/, class => "incomplete" },
2353 { regexp => qr/^ {$num_sign}/, class => "ctx" },
2354 # classifier for context must come before classifier add/rem,
2355 # or we would have to use more complicated regexp, for example
2356 # qr/(?= {0,$m}\+)[+ ]{$num_sign}/, where $m = $num_sign - 1;
2357 { regexp => qr/^[+ ]{$num_sign}/, class => "add" },
2358 { regexp => qr/^[- ]{$num_sign}/, class => "rem" },
2359 );
2360 for my $clsfy (@diff_line_classifier) {
2361 return $clsfy->{'class'}
2362 if ($line =~ $clsfy->{'regexp'});
2363 }
2364
2365 # fallback
2366 return "";
2367}
2368
Jakub Narebskif1310cf2011-10-31 00:36:21 +01002369# assumes that $from and $to are defined and correctly filled,
2370# and that $line holds a line of chunk header for unified diff
2371sub format_unidiff_chunk_header {
2372 my ($line, $from, $to) = @_;
2373
2374 my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
2375 $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
2376
2377 $from_lines = 0 unless defined $from_lines;
2378 $to_lines = 0 unless defined $to_lines;
2379
2380 if ($from->{'href'}) {
2381 $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
2382 -class=>"list"}, $from_text);
2383 }
2384 if ($to->{'href'}) {
2385 $to_text = $cgi->a({-href=>"$to->{'href'}#l$to_start",
2386 -class=>"list"}, $to_text);
2387 }
2388 $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
2389 "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
2390 return $line;
2391}
2392
2393# assumes that $from and $to are defined and correctly filled,
2394# and that $line holds a line of chunk header for combined diff
2395sub format_cc_diff_chunk_header {
2396 my ($line, $from, $to) = @_;
2397
2398 my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
2399 my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
2400
2401 @from_text = split(' ', $ranges);
2402 for (my $i = 0; $i < @from_text; ++$i) {
2403 ($from_start[$i], $from_nlines[$i]) =
2404 (split(',', substr($from_text[$i], 1)), 0);
2405 }
2406
2407 $to_text = pop @from_text;
2408 $to_start = pop @from_start;
2409 $to_nlines = pop @from_nlines;
2410
2411 $line = "<span class=\"chunk_info\">$prefix ";
2412 for (my $i = 0; $i < @from_text; ++$i) {
2413 if ($from->{'href'}[$i]) {
2414 $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
2415 -class=>"list"}, $from_text[$i]);
2416 } else {
2417 $line .= $from_text[$i];
2418 }
2419 $line .= " ";
2420 }
2421 if ($to->{'href'}) {
2422 $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
2423 -class=>"list"}, $to_text);
2424 } else {
2425 $line .= $to_text;
2426 }
2427 $line .= " $prefix</span>" .
2428 "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
2429 return $line;
2430}
2431
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01002432# process patch (diff) line (not to be used for diff headers),
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02002433# returning HTML-formatted (but not wrapped) line.
2434# If the line is passed as a reference, it is treated as HTML and not
2435# esc_html()'ed.
Michał Kiedrowiczf4a81022012-04-11 23:18:42 +02002436sub format_diff_line {
2437 my ($line, $diff_class, $from, $to) = @_;
Jakub Narebskieee08902006-08-24 00:15:14 +02002438
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02002439 if (ref($line)) {
2440 $line = $$line;
Michał Kiedrowiczf4a81022012-04-11 23:18:42 +02002441 } else {
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02002442 chomp $line;
2443 $line = untabify($line);
2444
2445 if ($from && $to && $line =~ m/^\@{2} /) {
2446 $line = format_unidiff_chunk_header($line, $from, $to);
2447 } elsif ($from && $to && $line =~ m/^\@{3}/) {
2448 $line = format_cc_diff_chunk_header($line, $from, $to);
2449 } else {
2450 $line = esc_html($line, -nbsp=>1);
2451 }
Jakub Narebski59e3b142006-11-18 23:35:40 +01002452 }
Michał Kiedrowiczf4a81022012-04-11 23:18:42 +02002453
2454 my $diff_classes = "diff";
2455 $diff_classes .= " $diff_class" if ($diff_class);
2456 $line = "<div class=\"$diff_classes\">$line</div>\n";
2457
2458 return $line;
Jakub Narebskieee08902006-08-24 00:15:14 +02002459}
2460
Matt McCutchena3c8ab32007-07-22 01:30:27 +02002461# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
2462# linked. Pass the hash of the tree/commit to snapshot.
2463sub format_snapshot_links {
2464 my ($hash) = @_;
Matt McCutchena3c8ab32007-07-22 01:30:27 +02002465 my $num_fmts = @snapshot_fmts;
2466 if ($num_fmts > 1) {
2467 # A parenthesized list of links bearing format names.
Jakub Narebskia7817852007-07-22 23:41:20 +02002468 # e.g. "snapshot (_tar.gz_ _zip_)"
Matt McCutchena3c8ab32007-07-22 01:30:27 +02002469 return "snapshot (" . join(' ', map
2470 $cgi->a({
2471 -href => href(
2472 action=>"snapshot",
2473 hash=>$hash,
2474 snapshot_format=>$_
2475 )
2476 }, $known_snapshot_formats{$_}{'display'})
2477 , @snapshot_fmts) . ")";
2478 } elsif ($num_fmts == 1) {
2479 # A single "snapshot" link whose tooltip bears the format name.
Jakub Narebskia7817852007-07-22 23:41:20 +02002480 # i.e. "_snapshot_"
Matt McCutchena3c8ab32007-07-22 01:30:27 +02002481 my ($fmt) = @snapshot_fmts;
Jakub Narebskia7817852007-07-22 23:41:20 +02002482 return
2483 $cgi->a({
Matt McCutchena3c8ab32007-07-22 01:30:27 +02002484 -href => href(
2485 action=>"snapshot",
2486 hash=>$hash,
2487 snapshot_format=>$fmt
2488 ),
2489 -title => "in format: $known_snapshot_formats{$fmt}{'display'}"
2490 }, "snapshot");
2491 } else { # $num_fmts == 0
2492 return undef;
2493 }
2494}
2495
Jakub Narebski35621982008-04-20 22:09:48 +02002496## ......................................................................
2497## functions returning values to be passed, perhaps after some
2498## transformation, to other functions; e.g. returning arguments to href()
2499
2500# returns hash to be passed to href to generate gitweb URL
2501# in -title key it returns description of link
2502sub get_feed_info {
2503 my $format = shift || 'Atom';
2504 my %res = (action => lc($format));
2505
2506 # feed links are possible only for project views
2507 return unless (defined $project);
2508 # some views should link to OPML, or to generic project feed,
2509 # or don't have specific feed yet (so they should use generic)
Jakub Narebski18ab83e2012-01-07 11:47:38 +01002510 return if (!$action || $action =~ /^(?:tags|heads|forks|tag|search)$/x);
Jakub Narebski35621982008-04-20 22:09:48 +02002511
2512 my $branch;
2513 # branches refs uses 'refs/heads/' prefix (fullname) to differentiate
2514 # from tag links; this also makes possible to detect branch links
2515 if ((defined $hash_base && $hash_base =~ m!^refs/heads/(.*)$!) ||
2516 (defined $hash && $hash =~ m!^refs/heads/(.*)$!)) {
2517 $branch = $1;
2518 }
2519 # find log type for feed description (title)
2520 my $type = 'log';
2521 if (defined $file_name) {
2522 $type = "history of $file_name";
2523 $type .= "/" if ($action eq 'tree');
2524 $type .= " on '$branch'" if (defined $branch);
2525 } else {
2526 $type = "log of $branch" if (defined $branch);
2527 }
2528
2529 $res{-title} = $type;
2530 $res{'hash'} = (defined $branch ? "refs/heads/$branch" : undef);
2531 $res{'file_name'} = $file_name;
2532
2533 return %res;
2534}
2535
Jakub Narebski717b8312006-07-31 21:22:15 +02002536## ----------------------------------------------------------------------
2537## git utility subroutines, invoking git commands
2538
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002539# returns path to the core git executable and the --git-dir parameter as list
2540sub git_cmd {
Jakub Narebskiaa7dd052009-09-01 13:39:16 +02002541 $number_of_git_cmds++;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002542 return $GIT, '--git-dir='.$git_dir;
2543}
2544
Lea Wiemann516381d2008-06-17 23:46:35 +02002545# quote the given arguments for passing them to the shell
2546# quote_command("command", "arg 1", "arg with ' and ! characters")
2547# => "'command' 'arg 1' 'arg with '\'' and '\!' characters'"
2548# Try to avoid using this function wherever possible.
2549sub quote_command {
2550 return join(' ',
Jakub Narebski68cedb12009-05-10 02:40:37 +02002551 map { my $a = $_; $a =~ s/(['!])/'\\$1'/g; "'$a'" } @_ );
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002552}
2553
Jakub Narebski717b8312006-07-31 21:22:15 +02002554# get HEAD ref of given project as hash
Jakub Narebski847e01f2006-08-14 02:05:47 +02002555sub git_get_head_hash {
Mark Radab6292752009-11-07 16:13:29 +01002556 return git_get_full_hash(shift, 'HEAD');
2557}
2558
2559sub git_get_full_hash {
2560 return git_get_hash(@_);
2561}
2562
2563sub git_get_short_hash {
2564 return git_get_hash(@_, '--short=7');
2565}
2566
2567sub git_get_hash {
2568 my ($project, $hash, @options) = @_;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002569 my $o_git_dir = $git_dir;
Jakub Narebski717b8312006-07-31 21:22:15 +02002570 my $retval = undef;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002571 $git_dir = "$projectroot/$project";
Mark Radab6292752009-11-07 16:13:29 +01002572 if (open my $fd, '-|', git_cmd(), 'rev-parse',
2573 '--verify', '-q', @options, $hash) {
2574 $retval = <$fd>;
2575 chomp $retval if defined $retval;
Jakub Narebski717b8312006-07-31 21:22:15 +02002576 close $fd;
Jakub Narebski717b8312006-07-31 21:22:15 +02002577 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002578 if (defined $o_git_dir) {
2579 $git_dir = $o_git_dir;
Jakub Narebski717b8312006-07-31 21:22:15 +02002580 }
2581 return $retval;
2582}
2583
2584# get type of given object
2585sub git_get_type {
2586 my $hash = shift;
2587
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002588 open my $fd, "-|", git_cmd(), "cat-file", '-t', $hash or return;
Jakub Narebski717b8312006-07-31 21:22:15 +02002589 my $type = <$fd>;
2590 close $fd or return;
2591 chomp $type;
2592 return $type;
2593}
2594
Jakub Narebskib2019272007-11-03 00:41:19 +01002595# repository configuration
2596our $config_file = '';
2597our %config;
2598
2599# store multiple values for single key as anonymous array reference
2600# single values stored directly in the hash, not as [ <value> ]
2601sub hash_set_multi {
2602 my ($hash, $key, $value) = @_;
2603
2604 if (!exists $hash->{$key}) {
2605 $hash->{$key} = $value;
2606 } elsif (!ref $hash->{$key}) {
2607 $hash->{$key} = [ $hash->{$key}, $value ];
2608 } else {
2609 push @{$hash->{$key}}, $value;
2610 }
2611}
2612
2613# return hash of git project configuration
2614# optionally limited to some section, e.g. 'gitweb'
2615sub git_parse_project_config {
2616 my $section_regexp = shift;
2617 my %config;
2618
2619 local $/ = "\0";
2620
2621 open my $fh, "-|", git_cmd(), "config", '-z', '-l',
2622 or return;
2623
2624 while (my $keyval = <$fh>) {
2625 chomp $keyval;
2626 my ($key, $value) = split(/\n/, $keyval, 2);
2627
2628 hash_set_multi(\%config, $key, $value)
2629 if (!defined $section_regexp || $key =~ /^(?:$section_regexp)\./o);
2630 }
2631 close $fh;
2632
2633 return %config;
2634}
2635
Marcel M. Carydf5d10a2009-02-18 14:09:41 +01002636# convert config value to boolean: 'true' or 'false'
Jakub Narebskib2019272007-11-03 00:41:19 +01002637# no value, number > 0, 'true' and 'yes' values are true
2638# rest of values are treated as false (never as error)
2639sub config_to_bool {
2640 my $val = shift;
2641
Marcel M. Carydf5d10a2009-02-18 14:09:41 +01002642 return 1 if !defined $val; # section.key
2643
Jakub Narebskib2019272007-11-03 00:41:19 +01002644 # strip leading and trailing whitespace
2645 $val =~ s/^\s+//;
2646 $val =~ s/\s+$//;
2647
Marcel M. Carydf5d10a2009-02-18 14:09:41 +01002648 return (($val =~ /^\d+$/ && $val) || # section.key = 1
Jakub Narebskib2019272007-11-03 00:41:19 +01002649 ($val =~ /^(?:true|yes)$/i)); # section.key = true
2650}
2651
2652# convert config value to simple decimal number
2653# an optional value suffix of 'k', 'm', or 'g' will cause the value
2654# to be multiplied by 1024, 1048576, or 1073741824
2655sub config_to_int {
2656 my $val = shift;
2657
2658 # strip leading and trailing whitespace
2659 $val =~ s/^\s+//;
2660 $val =~ s/\s+$//;
2661
2662 if (my ($num, $unit) = ($val =~ /^([0-9]*)([kmg])$/i)) {
2663 $unit = lc($unit);
2664 # unknown unit is treated as 1
2665 return $num * ($unit eq 'g' ? 1073741824 :
2666 $unit eq 'm' ? 1048576 :
2667 $unit eq 'k' ? 1024 : 1);
2668 }
2669 return $val;
2670}
2671
2672# convert config value to array reference, if needed
2673sub config_to_multi {
2674 my $val = shift;
2675
Jakub Narebskid76a5852007-12-20 10:48:09 +01002676 return ref($val) ? $val : (defined($val) ? [ $val ] : []);
Jakub Narebskib2019272007-11-03 00:41:19 +01002677}
2678
Jakub Narebski717b8312006-07-31 21:22:15 +02002679sub git_get_project_config {
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05302680 my ($key, $type) = @_;
Jakub Narebski717b8312006-07-31 21:22:15 +02002681
Jakub Narebski7a49c252010-03-27 20:26:59 +01002682 return unless defined $git_dir;
Jakub Narebski9be36142010-03-01 22:51:34 +01002683
Jakub Narebskib2019272007-11-03 00:41:19 +01002684 # key sanity check
Jakub Narebski717b8312006-07-31 21:22:15 +02002685 return unless ($key);
Jakub Narebski14569cd2011-07-28 23:38:03 +02002686 # only subsection, if exists, is case sensitive,
2687 # and not lowercased by 'git config -z -l'
2688 if (my ($hi, $mi, $lo) = ($key =~ /^([^.]*)\.(.*)\.([^.]*)$/)) {
2689 $key = join(".", lc($hi), $mi, lc($lo));
2690 } else {
2691 $key = lc($key);
2692 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002693 $key =~ s/^gitweb\.//;
2694 return if ($key =~ m/\W/);
2695
Jakub Narebskib2019272007-11-03 00:41:19 +01002696 # type sanity check
2697 if (defined $type) {
2698 $type =~ s/^--//;
2699 $type = undef
2700 unless ($type eq 'bool' || $type eq 'int');
2701 }
2702
2703 # get config
2704 if (!defined $config_file ||
2705 $config_file ne "$git_dir/config") {
2706 %config = git_parse_project_config('gitweb');
2707 $config_file = "$git_dir/config";
2708 }
2709
Marcel M. Carydf5d10a2009-02-18 14:09:41 +01002710 # check if config variable (key) exists
2711 return unless exists $config{"gitweb.$key"};
2712
Jakub Narebskib2019272007-11-03 00:41:19 +01002713 # ensure given type
2714 if (!defined $type) {
2715 return $config{"gitweb.$key"};
2716 } elsif ($type eq 'bool') {
2717 # backward compatibility: 'git config --bool' returns true/false
2718 return config_to_bool($config{"gitweb.$key"}) ? 'true' : 'false';
2719 } elsif ($type eq 'int') {
2720 return config_to_int($config{"gitweb.$key"});
2721 }
2722 return $config{"gitweb.$key"};
Jakub Narebski717b8312006-07-31 21:22:15 +02002723}
2724
Jakub Narebski717b8312006-07-31 21:22:15 +02002725# get hash of given path at given ref
2726sub git_get_hash_by_path {
2727 my $base = shift;
2728 my $path = shift || return undef;
Jakub Narebski1d782b02006-09-21 18:09:12 +02002729 my $type = shift;
Jakub Narebski717b8312006-07-31 21:22:15 +02002730
Jakub Narebski4b02f482006-09-26 01:54:24 +02002731 $path =~ s,/+$,,;
Jakub Narebski717b8312006-07-31 21:22:15 +02002732
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002733 open my $fd, "-|", git_cmd(), "ls-tree", $base, "--", $path
Lea Wiemann074afaa2008-06-19 22:03:21 +02002734 or die_error(500, "Open git-ls-tree failed");
Jakub Narebski717b8312006-07-31 21:22:15 +02002735 my $line = <$fd>;
2736 close $fd or return undef;
2737
Jakub Narebski198a2a82007-05-12 21:16:34 +02002738 if (!defined $line) {
2739 # there is no tree or hash given by $path at $base
2740 return undef;
2741 }
2742
Jakub Narebski717b8312006-07-31 21:22:15 +02002743 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
Jakub Narebski8b4b94c2006-10-30 22:25:11 +01002744 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/;
Jakub Narebski1d782b02006-09-21 18:09:12 +02002745 if (defined $type && $type ne $2) {
2746 # type doesn't match
2747 return undef;
2748 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002749 return $3;
2750}
2751
Jakub Narebskied224de2007-05-07 01:10:04 +02002752# get path of entry with given hash at given tree-ish (ref)
2753# used to get 'from' filename for combined diff (merge commit) for renames
2754sub git_get_path_by_hash {
2755 my $base = shift || return;
2756 my $hash = shift || return;
2757
2758 local $/ = "\0";
2759
2760 open my $fd, "-|", git_cmd(), "ls-tree", '-r', '-t', '-z', $base
2761 or return undef;
2762 while (my $line = <$fd>) {
2763 chomp $line;
2764
2765 #'040000 tree 595596a6a9117ddba9fe379b6b012b558bac8423 gitweb'
2766 #'100644 blob e02e90f0429be0d2a69b76571101f20b8f75530f gitweb/README'
2767 if ($line =~ m/(?:[0-9]+) (?:.+) $hash\t(.+)$/) {
2768 close $fd;
2769 return $1;
2770 }
2771 }
2772 close $fd;
2773 return undef;
2774}
2775
Jakub Narebski717b8312006-07-31 21:22:15 +02002776## ......................................................................
2777## git utility functions, directly accessing git repository
2778
Sebastien Ceveye4e3b322011-04-29 19:52:00 +02002779# get the value of config variable either from file named as the variable
2780# itself in the repository ($GIT_DIR/$name file), or from gitweb.$name
2781# configuration variable in the repository config file.
2782sub git_get_file_or_project_config {
2783 my ($path, $name) = @_;
Jakub Narebski717b8312006-07-31 21:22:15 +02002784
Jakub Narebski0e121a22007-11-03 00:41:20 +01002785 $git_dir = "$projectroot/$path";
Sebastien Ceveye4e3b322011-04-29 19:52:00 +02002786 open my $fd, '<', "$git_dir/$name"
2787 or return git_get_project_config($name);
2788 my $conf = <$fd>;
Jakub Narebski717b8312006-07-31 21:22:15 +02002789 close $fd;
Sebastien Ceveye4e3b322011-04-29 19:52:00 +02002790 if (defined $conf) {
2791 chomp $conf;
Junio C Hamano2eb54ef2007-05-16 21:04:16 -07002792 }
Sebastien Ceveye4e3b322011-04-29 19:52:00 +02002793 return $conf;
Jakub Narebski717b8312006-07-31 21:22:15 +02002794}
2795
Sebastien Ceveye4e3b322011-04-29 19:52:00 +02002796sub git_get_project_description {
2797 my $path = shift;
2798 return git_get_file_or_project_config($path, 'description');
Jakub Narebski717b8312006-07-31 21:22:15 +02002799}
2800
Sebastien Ceveyd940c902011-04-29 19:52:01 +02002801sub git_get_project_category {
2802 my $path = shift;
2803 return git_get_file_or_project_config($path, 'category');
2804}
2805
2806
Jakub Narebski0368c492011-04-29 19:51:57 +02002807# supported formats:
2808# * $GIT_DIR/ctags/<tagname> file (in 'ctags' subdirectory)
2809# - if its contents is a number, use it as tag weight,
2810# - otherwise add a tag with weight 1
2811# * $GIT_DIR/ctags file, each line is a tag (with weight 1)
2812# the same value multiple times increases tag weight
2813# * `gitweb.ctag' multi-valued repo config variable
Petr Baudisaed93de2008-10-02 17:13:02 +02002814sub git_get_project_ctags {
Jakub Narebski0368c492011-04-29 19:51:57 +02002815 my $project = shift;
Petr Baudisaed93de2008-10-02 17:13:02 +02002816 my $ctags = {};
2817
Jakub Narebski0368c492011-04-29 19:51:57 +02002818 $git_dir = "$projectroot/$project";
2819 if (opendir my $dh, "$git_dir/ctags") {
2820 my @files = grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh);
2821 foreach my $tagfile (@files) {
2822 open my $ct, '<', $tagfile
2823 or next;
2824 my $val = <$ct>;
2825 chomp $val if $val;
2826 close $ct;
2827
2828 (my $ctag = $tagfile) =~ s#.*/##;
Jonathan Nieder2c162b52011-06-09 02:08:57 -05002829 if ($val =~ /^\d+$/) {
Jakub Narebski0368c492011-04-29 19:51:57 +02002830 $ctags->{$ctag} = $val;
2831 } else {
2832 $ctags->{$ctag} = 1;
2833 }
2834 }
2835 closedir $dh;
2836
2837 } elsif (open my $fh, '<', "$git_dir/ctags") {
2838 while (my $line = <$fh>) {
2839 chomp $line;
2840 $ctags->{$line}++ if $line;
2841 }
2842 close $fh;
2843
2844 } else {
2845 my $taglist = config_to_multi(git_get_project_config('ctag'));
2846 foreach my $tag (@$taglist) {
2847 $ctags->{$tag}++;
2848 }
Petr Baudisaed93de2008-10-02 17:13:02 +02002849 }
Jakub Narebski0368c492011-04-29 19:51:57 +02002850
2851 return $ctags;
2852}
2853
2854# return hash, where keys are content tags ('ctags'),
2855# and values are sum of weights of given tag in every project
2856sub git_gather_all_ctags {
2857 my $projects = shift;
2858 my $ctags = {};
2859
2860 foreach my $p (@$projects) {
2861 foreach my $ct (keys %{$p->{'ctags'}}) {
2862 $ctags->{$ct} += $p->{'ctags'}->{$ct};
2863 }
2864 }
2865
2866 return $ctags;
Petr Baudisaed93de2008-10-02 17:13:02 +02002867}
2868
2869sub git_populate_project_tagcloud {
2870 my $ctags = shift;
2871
2872 # First, merge different-cased tags; tags vote on casing
2873 my %ctags_lc;
2874 foreach (keys %$ctags) {
2875 $ctags_lc{lc $_}->{count} += $ctags->{$_};
2876 if (not $ctags_lc{lc $_}->{topcount}
2877 or $ctags_lc{lc $_}->{topcount} < $ctags->{$_}) {
2878 $ctags_lc{lc $_}->{topcount} = $ctags->{$_};
2879 $ctags_lc{lc $_}->{topname} = $_;
2880 }
2881 }
2882
2883 my $cloud;
Jakub Narebski84d9e2d2012-02-03 13:44:54 +01002884 my $matched = $input_params{'ctag'};
Petr Baudisaed93de2008-10-02 17:13:02 +02002885 if (eval { require HTML::TagCloud; 1; }) {
2886 $cloud = HTML::TagCloud->new;
Jakub Narebski0368c492011-04-29 19:51:57 +02002887 foreach my $ctag (sort keys %ctags_lc) {
Petr Baudisaed93de2008-10-02 17:13:02 +02002888 # Pad the title with spaces so that the cloud looks
2889 # less crammed.
Jakub Narebski0368c492011-04-29 19:51:57 +02002890 my $title = esc_html($ctags_lc{$ctag}->{topname});
Petr Baudisaed93de2008-10-02 17:13:02 +02002891 $title =~ s/ /&nbsp;/g;
2892 $title =~ s/^/&nbsp;/g;
2893 $title =~ s/$/&nbsp;/g;
Jakub Narebski4b9447f2011-04-29 19:51:58 +02002894 if (defined $matched && $matched eq $ctag) {
2895 $title = qq(<span class="match">$title</span>);
2896 }
Jakub Narebski0368c492011-04-29 19:51:57 +02002897 $cloud->add($title, href(project=>undef, ctag=>$ctag),
2898 $ctags_lc{$ctag}->{count});
Petr Baudisaed93de2008-10-02 17:13:02 +02002899 }
2900 } else {
Jakub Narebski0368c492011-04-29 19:51:57 +02002901 $cloud = {};
2902 foreach my $ctag (keys %ctags_lc) {
Jakub Narebski4b9447f2011-04-29 19:51:58 +02002903 my $title = esc_html($ctags_lc{$ctag}->{topname}, -nbsp=>1);
2904 if (defined $matched && $matched eq $ctag) {
2905 $title = qq(<span class="match">$title</span>);
2906 }
Jakub Narebski0368c492011-04-29 19:51:57 +02002907 $cloud->{$ctag}{count} = $ctags_lc{$ctag}->{count};
2908 $cloud->{$ctag}{ctag} =
Jakub Narebski4b9447f2011-04-29 19:51:58 +02002909 $cgi->a({-href=>href(project=>undef, ctag=>$ctag)}, $title);
Jakub Narebski0368c492011-04-29 19:51:57 +02002910 }
Petr Baudisaed93de2008-10-02 17:13:02 +02002911 }
Jakub Narebski0368c492011-04-29 19:51:57 +02002912 return $cloud;
Petr Baudisaed93de2008-10-02 17:13:02 +02002913}
2914
2915sub git_show_project_tagcloud {
2916 my ($cloud, $count) = @_;
Petr Baudisaed93de2008-10-02 17:13:02 +02002917 if (ref $cloud eq 'HTML::TagCloud') {
2918 return $cloud->html_and_css($count);
2919 } else {
Jakub Narebski0368c492011-04-29 19:51:57 +02002920 my @tags = sort { $cloud->{$a}->{'count'} <=> $cloud->{$b}->{'count'} } keys %$cloud;
2921 return
2922 '<div id="htmltagcloud"'.($project ? '' : ' align="center"').'>' .
2923 join (', ', map {
2924 $cloud->{$_}->{'ctag'}
2925 } splice(@tags, 0, $count)) .
2926 '</div>';
Petr Baudisaed93de2008-10-02 17:13:02 +02002927 }
2928}
2929
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02002930sub git_get_project_url_list {
2931 my $path = shift;
2932
Jakub Narebski0e121a22007-11-03 00:41:20 +01002933 $git_dir = "$projectroot/$path";
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02002934 open my $fd, '<', "$git_dir/cloneurl"
Jakub Narebski0e121a22007-11-03 00:41:20 +01002935 or return wantarray ?
2936 @{ config_to_multi(git_get_project_config('url')) } :
2937 config_to_multi(git_get_project_config('url'));
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02002938 my @git_project_url_list = map { chomp; $_ } <$fd>;
2939 close $fd;
2940
2941 return wantarray ? @git_project_url_list : \@git_project_url_list;
2942}
2943
Jakub Narebski847e01f2006-08-14 02:05:47 +02002944sub git_get_projects_list {
Jakub Narebski12b14432011-04-29 19:51:56 +02002945 my $filter = shift || '';
Bernhard R. Link348a6582012-01-30 21:06:38 +01002946 my $paranoid = shift;
Jakub Narebski717b8312006-07-31 21:22:15 +02002947 my @list;
2948
2949 if (-d $projects_list) {
2950 # search in directory
Jakub Narebski12b14432011-04-29 19:51:56 +02002951 my $dir = $projects_list;
Aneesh Kumar K.V6768d6b2006-11-03 10:41:45 +05302952 # remove the trailing "/"
2953 $dir =~ s!/+$!!;
Matthieu Moyac593b72012-01-04 11:07:45 +01002954 my $pfxlen = length("$dir");
2955 my $pfxdepth = ($dir =~ tr!/!!);
Jakub Narebski12b14432011-04-29 19:51:56 +02002956 # when filtering, search only given subdirectory
Bernhard R. Link348a6582012-01-30 21:06:38 +01002957 if ($filter && !$paranoid) {
Jakub Narebski12b14432011-04-29 19:51:56 +02002958 $dir .= "/$filter";
2959 $dir =~ s!/+$!!;
2960 }
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002961
2962 File::Find::find({
2963 follow_fast => 1, # follow symbolic links
Junio C Hamanod20602e2007-07-27 01:23:03 -07002964 follow_skip => 2, # ignore duplicates
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002965 dangling_symlinks => 0, # ignore dangling symlinks, silently
2966 wanted => sub {
Jakub Narebskiee1d8ee2010-04-30 18:30:31 +02002967 # global variables
2968 our $project_maxdepth;
2969 our $projectroot;
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002970 # skip project-list toplevel, if we get it.
2971 return if (m!^[/.]$!);
2972 # only directories can be git repositories
2973 return unless (-d $_);
Luke Luca5e9492007-10-16 20:45:25 -07002974 # don't traverse too deep (Find is super slow on os x)
Jakub Narebski12b14432011-04-29 19:51:56 +02002975 # $project_maxdepth excludes depth of $projectroot
Luke Luca5e9492007-10-16 20:45:25 -07002976 if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) {
2977 $File::Find::prune = 1;
2978 return;
2979 }
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002980
Jakub Narebski12b14432011-04-29 19:51:56 +02002981 my $path = substr($File::Find::name, $pfxlen + 1);
Bernhard R. Link348a6582012-01-30 21:06:38 +01002982 # paranoidly only filter here
2983 if ($paranoid && $filter && $path !~ m!^\Q$filter\E/!) {
2984 next;
2985 }
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002986 # we check related file in $projectroot
Devin Doucettefb3bb3d2008-12-27 02:39:31 -07002987 if (check_export_ok("$projectroot/$path")) {
2988 push @list, { path => $path };
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002989 $File::Find::prune = 1;
2990 }
2991 },
2992 }, "$dir");
2993
Jakub Narebski717b8312006-07-31 21:22:15 +02002994 } elsif (-f $projects_list) {
2995 # read from file(url-encoded):
2996 # 'git%2Fgit.git Linus+Torvalds'
2997 # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
2998 # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02002999 open my $fd, '<', $projects_list or return;
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02003000 PROJECT:
Jakub Narebski717b8312006-07-31 21:22:15 +02003001 while (my $line = <$fd>) {
3002 chomp $line;
3003 my ($path, $owner) = split ' ', $line;
3004 $path = unescape($path);
3005 $owner = unescape($owner);
3006 if (!defined $path) {
3007 next;
3008 }
Jakub Narebski12b14432011-04-29 19:51:56 +02003009 # if $filter is rpovided, check if $path begins with $filter
3010 if ($filter && $path !~ m!^\Q$filter\E/!) {
3011 next;
Junio C Hamano83ee94c2006-11-07 22:37:17 -08003012 }
Junio C Hamano2172ce42006-10-03 02:30:47 -07003013 if (check_export_ok("$projectroot/$path")) {
Jakub Narebski717b8312006-07-31 21:22:15 +02003014 my $pr = {
3015 path => $path,
Martin Koegler00f429a2007-06-03 17:42:44 +02003016 owner => to_utf8($owner),
Jakub Narebski717b8312006-07-31 21:22:15 +02003017 };
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02003018 push @list, $pr;
Jakub Narebski717b8312006-07-31 21:22:15 +02003019 }
3020 }
3021 close $fd;
3022 }
Jakub Narebski717b8312006-07-31 21:22:15 +02003023 return @list;
3024}
3025
Jakub Narebski12b14432011-04-29 19:51:56 +02003026# written with help of Tree::Trie module (Perl Artistic License, GPL compatibile)
3027# as side effects it sets 'forks' field to list of forks for forked projects
3028sub filter_forks_from_projects_list {
3029 my $projects = shift;
3030
3031 my %trie; # prefix tree of directories (path components)
3032 # generate trie out of those directories that might contain forks
3033 foreach my $pr (@$projects) {
3034 my $path = $pr->{'path'};
3035 $path =~ s/\.git$//; # forks of 'repo.git' are in 'repo/' directory
3036 next if ($path =~ m!/$!); # skip non-bare repositories, e.g. 'repo/.git'
3037 next unless ($path); # skip '.git' repository: tests, git-instaweb
Julien Muchembled53c632f2011-10-21 21:04:21 +02003038 next unless (-d "$projectroot/$path"); # containing directory exists
Jakub Narebski12b14432011-04-29 19:51:56 +02003039 $pr->{'forks'} = []; # there can be 0 or more forks of project
3040
3041 # add to trie
3042 my @dirs = split('/', $path);
3043 # walk the trie, until either runs out of components or out of trie
3044 my $ref = \%trie;
3045 while (scalar @dirs &&
3046 exists($ref->{$dirs[0]})) {
3047 $ref = $ref->{shift @dirs};
3048 }
3049 # create rest of trie structure from rest of components
3050 foreach my $dir (@dirs) {
3051 $ref = $ref->{$dir} = {};
3052 }
3053 # create end marker, store $pr as a data
3054 $ref->{''} = $pr if (!exists $ref->{''});
3055 }
3056
3057 # filter out forks, by finding shortest prefix match for paths
3058 my @filtered;
3059 PROJECT:
3060 foreach my $pr (@$projects) {
3061 # trie lookup
3062 my $ref = \%trie;
3063 DIR:
3064 foreach my $dir (split('/', $pr->{'path'})) {
3065 if (exists $ref->{''}) {
3066 # found [shortest] prefix, is a fork - skip it
3067 push @{$ref->{''}{'forks'}}, $pr;
3068 next PROJECT;
3069 }
3070 if (!exists $ref->{$dir}) {
3071 # not in trie, cannot have prefix, not a fork
3072 push @filtered, $pr;
3073 next PROJECT;
3074 }
3075 # If the dir is there, we just walk one step down the trie.
3076 $ref = $ref->{$dir};
3077 }
3078 # we ran out of trie
3079 # (shouldn't happen: it's either no match, or end marker)
3080 push @filtered, $pr;
3081 }
3082
3083 return @filtered;
3084}
3085
3086# note: fill_project_list_info must be run first,
3087# for 'descr_long' and 'ctags' to be filled
3088sub search_projects_list {
3089 my ($projlist, %opts) = @_;
3090 my $tagfilter = $opts{'tagfilter'};
Jakub Narebskie65ceb62012-03-02 23:34:24 +01003091 my $search_re = $opts{'search_regexp'};
Jakub Narebski12b14432011-04-29 19:51:56 +02003092
3093 return @$projlist
Jakub Narebskie65ceb62012-03-02 23:34:24 +01003094 unless ($tagfilter || $search_re);
Jakub Narebski12b14432011-04-29 19:51:56 +02003095
Jakub Narebski07b257f2012-02-23 16:54:48 +01003096 # searching projects require filling to be run before it;
3097 fill_project_list_info($projlist,
3098 $tagfilter ? 'ctags' : (),
Junio C Hamanoaa145bf2012-03-08 13:04:49 -08003099 $search_re ? ('path', 'descr') : ());
Jakub Narebski12b14432011-04-29 19:51:56 +02003100 my @projects;
3101 PROJECT:
3102 foreach my $pr (@$projlist) {
3103
3104 if ($tagfilter) {
3105 next unless ref($pr->{'ctags'}) eq 'HASH';
3106 next unless
3107 grep { lc($_) eq lc($tagfilter) } keys %{$pr->{'ctags'}};
3108 }
3109
Jakub Narebskie65ceb62012-03-02 23:34:24 +01003110 if ($search_re) {
Jakub Narebski12b14432011-04-29 19:51:56 +02003111 next unless
Jakub Narebskie65ceb62012-03-02 23:34:24 +01003112 $pr->{'path'} =~ /$search_re/ ||
3113 $pr->{'descr_long'} =~ /$search_re/;
Jakub Narebski12b14432011-04-29 19:51:56 +02003114 }
3115
3116 push @projects, $pr;
3117 }
3118
3119 return @projects;
3120}
3121
Junio C Hamano47852452007-07-03 22:10:42 -07003122our $gitweb_project_owner = undef;
3123sub git_get_project_list_from_file {
Jakub Narebski1e0cf032006-08-14 02:10:06 +02003124
Junio C Hamano47852452007-07-03 22:10:42 -07003125 return if (defined $gitweb_project_owner);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02003126
Junio C Hamano47852452007-07-03 22:10:42 -07003127 $gitweb_project_owner = {};
Jakub Narebski1e0cf032006-08-14 02:10:06 +02003128 # read from file (url-encoded):
3129 # 'git%2Fgit.git Linus+Torvalds'
3130 # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
3131 # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
3132 if (-f $projects_list) {
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02003133 open(my $fd, '<', $projects_list);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02003134 while (my $line = <$fd>) {
3135 chomp $line;
3136 my ($pr, $ow) = split ' ', $line;
3137 $pr = unescape($pr);
3138 $ow = unescape($ow);
Junio C Hamano47852452007-07-03 22:10:42 -07003139 $gitweb_project_owner->{$pr} = to_utf8($ow);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02003140 }
3141 close $fd;
3142 }
Junio C Hamano47852452007-07-03 22:10:42 -07003143}
3144
3145sub git_get_project_owner {
3146 my $project = shift;
3147 my $owner;
3148
3149 return undef unless $project;
Bruno Ribasb59012e2008-02-08 14:38:04 -02003150 $git_dir = "$projectroot/$project";
Junio C Hamano47852452007-07-03 22:10:42 -07003151
3152 if (!defined $gitweb_project_owner) {
3153 git_get_project_list_from_file();
3154 }
3155
3156 if (exists $gitweb_project_owner->{$project}) {
3157 $owner = $gitweb_project_owner->{$project};
3158 }
Bruno Ribasb59012e2008-02-08 14:38:04 -02003159 if (!defined $owner){
3160 $owner = git_get_project_config('owner');
3161 }
Jakub Narebski1e0cf032006-08-14 02:10:06 +02003162 if (!defined $owner) {
Bruno Ribasb59012e2008-02-08 14:38:04 -02003163 $owner = get_file_owner("$git_dir");
Jakub Narebski1e0cf032006-08-14 02:10:06 +02003164 }
3165
3166 return $owner;
3167}
3168
Jakub Narebskic60c56c2006-10-28 19:43:40 +02003169sub git_get_last_activity {
3170 my ($path) = @_;
3171 my $fd;
3172
3173 $git_dir = "$projectroot/$path";
3174 open($fd, "-|", git_cmd(), 'for-each-ref',
Robert Fitzsimons0ff5ec72006-12-22 19:38:13 +00003175 '--format=%(committer)',
Jakub Narebskic60c56c2006-10-28 19:43:40 +02003176 '--sort=-committerdate',
Robert Fitzsimons0ff5ec72006-12-22 19:38:13 +00003177 '--count=1',
Jakub Narebskic60c56c2006-10-28 19:43:40 +02003178 'refs/heads') or return;
3179 my $most_recent = <$fd>;
3180 close $fd or return;
Jakub Narebski785cdea2007-05-13 12:39:22 +02003181 if (defined $most_recent &&
3182 $most_recent =~ / (\d+) [-+][01]\d\d\d$/) {
Jakub Narebskic60c56c2006-10-28 19:43:40 +02003183 my $timestamp = $1;
3184 my $age = time - $timestamp;
3185 return ($age, age_string($age));
3186 }
Matt McCutchenc9563952007-06-28 18:15:22 -04003187 return (undef, undef);
Jakub Narebskic60c56c2006-10-28 19:43:40 +02003188}
3189
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01003190# Implementation note: when a single remote is wanted, we cannot use 'git
3191# remote show -n' because that command always work (assuming it's a remote URL
3192# if it's not defined), and we cannot use 'git remote show' because that would
3193# try to make a network roundtrip. So the only way to find if that particular
3194# remote is defined is to walk the list provided by 'git remote -v' and stop if
3195# and when we find what we want.
3196sub git_get_remotes_list {
3197 my $wanted = shift;
3198 my %remotes = ();
3199
3200 open my $fd, '-|' , git_cmd(), 'remote', '-v';
3201 return unless $fd;
3202 while (my $remote = <$fd>) {
3203 chomp $remote;
3204 $remote =~ s!\t(.*?)\s+\((\w+)\)$!!;
3205 next if $wanted and not $remote eq $wanted;
3206 my ($url, $key) = ($1, $2);
3207
3208 $remotes{$remote} ||= { 'heads' => () };
3209 $remotes{$remote}{$key} = $url;
3210 }
3211 close $fd or return;
3212 return wantarray ? %remotes : \%remotes;
3213}
3214
3215# Takes a hash of remotes as first parameter and fills it by adding the
3216# available remote heads for each of the indicated remotes.
3217sub fill_remote_heads {
3218 my $remotes = shift;
3219 my @heads = map { "remotes/$_" } keys %$remotes;
3220 my @remoteheads = git_get_heads_list(undef, @heads);
3221 foreach my $remote (keys %$remotes) {
3222 $remotes->{$remote}{'heads'} = [ grep {
3223 $_->{'name'} =~ s!^$remote/!!
3224 } @remoteheads ];
3225 }
3226}
3227
Jakub Narebski847e01f2006-08-14 02:05:47 +02003228sub git_get_references {
Jakub Narebski717b8312006-07-31 21:22:15 +02003229 my $type = shift || "";
3230 my %refs;
Jakub Narebski28b9d9f2006-11-25 11:32:08 +01003231 # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
3232 # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
3233 open my $fd, "-|", git_cmd(), "show-ref", "--dereference",
3234 ($type ? ("--", "refs/$type") : ()) # use -- <pattern> if $type
Jakub Narebski9704d752006-09-19 14:31:49 +02003235 or return;
Jakub Narebskid294e1c2006-08-14 02:14:20 +02003236
Jakub Narebski717b8312006-07-31 21:22:15 +02003237 while (my $line = <$fd>) {
3238 chomp $line;
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02003239 if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type.*)$!) {
Jakub Narebski717b8312006-07-31 21:22:15 +02003240 if (defined $refs{$1}) {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02003241 push @{$refs{$1}}, $2;
Jakub Narebski717b8312006-07-31 21:22:15 +02003242 } else {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02003243 $refs{$1} = [ $2 ];
Jakub Narebski717b8312006-07-31 21:22:15 +02003244 }
3245 }
3246 }
3247 close $fd or return;
3248 return \%refs;
3249}
3250
Jakub Narebski56a322f2006-08-24 19:41:23 +02003251sub git_get_rev_name_tags {
3252 my $hash = shift || return undef;
3253
Dennis Stosberg25691fb2006-08-28 17:49:58 +02003254 open my $fd, "-|", git_cmd(), "name-rev", "--tags", $hash
Jakub Narebski56a322f2006-08-24 19:41:23 +02003255 or return;
3256 my $name_rev = <$fd>;
3257 close $fd;
3258
3259 if ($name_rev =~ m|^$hash tags/(.*)$|) {
3260 return $1;
3261 } else {
3262 # catches also '$hash undefined' output
3263 return undef;
3264 }
3265}
3266
Jakub Narebski717b8312006-07-31 21:22:15 +02003267## ----------------------------------------------------------------------
3268## parse to hash functions
3269
Jakub Narebski847e01f2006-08-14 02:05:47 +02003270sub parse_date {
Jakub Narebski717b8312006-07-31 21:22:15 +02003271 my $epoch = shift;
3272 my $tz = shift || "-0000";
3273
3274 my %date;
3275 my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
3276 my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
3277 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
3278 $date{'hour'} = $hour;
3279 $date{'minute'} = $min;
3280 $date{'mday'} = $mday;
3281 $date{'day'} = $days[$wday];
3282 $date{'month'} = $months[$mon];
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01003283 $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
3284 $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
Jakub Narebski952c65f2006-08-22 16:52:50 +02003285 $date{'mday-time'} = sprintf "%d %s %02d:%02d",
3286 $mday, $months[$mon], $hour ,$min;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01003287 $date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
Vincent Zanottia62d6d82007-11-10 19:55:27 +01003288 1900+$year, 1+$mon, $mday, $hour ,$min, $sec;
Jakub Narebski717b8312006-07-31 21:22:15 +02003289
Jakub Narebski2b1e1722011-03-25 20:20:49 +01003290 my ($tz_sign, $tz_hour, $tz_min) =
3291 ($tz =~ m/^([-+])(\d\d)(\d\d)$/);
3292 $tz_sign = ($tz_sign eq '-' ? -1 : +1);
3293 my $local = $epoch + $tz_sign*((($tz_hour*60) + $tz_min)*60);
Jakub Narebski717b8312006-07-31 21:22:15 +02003294 ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
3295 $date{'hour_local'} = $hour;
3296 $date{'minute_local'} = $min;
3297 $date{'tz_local'} = $tz;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01003298 $date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
3299 1900+$year, $mon+1, $mday,
3300 $hour, $min, $sec, $tz);
Jakub Narebski717b8312006-07-31 21:22:15 +02003301 return %date;
3302}
3303
Jakub Narebski847e01f2006-08-14 02:05:47 +02003304sub parse_tag {
Jakub Narebski717b8312006-07-31 21:22:15 +02003305 my $tag_id = shift;
3306 my %tag;
3307 my @comment;
3308
Dennis Stosberg25691fb2006-08-28 17:49:58 +02003309 open my $fd, "-|", git_cmd(), "cat-file", "tag", $tag_id or return;
Jakub Narebski717b8312006-07-31 21:22:15 +02003310 $tag{'id'} = $tag_id;
3311 while (my $line = <$fd>) {
3312 chomp $line;
3313 if ($line =~ m/^object ([0-9a-fA-F]{40})$/) {
3314 $tag{'object'} = $1;
3315 } elsif ($line =~ m/^type (.+)$/) {
3316 $tag{'type'} = $1;
3317 } elsif ($line =~ m/^tag (.+)$/) {
3318 $tag{'name'} = $1;
3319 } elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
3320 $tag{'author'} = $1;
Giuseppe Bilottaba924732009-06-30 00:00:50 +02003321 $tag{'author_epoch'} = $2;
3322 $tag{'author_tz'} = $3;
3323 if ($tag{'author'} =~ m/^([^<]+) <([^>]*)>/) {
3324 $tag{'author_name'} = $1;
3325 $tag{'author_email'} = $2;
3326 } else {
3327 $tag{'author_name'} = $tag{'author'};
3328 }
Jakub Narebski717b8312006-07-31 21:22:15 +02003329 } elsif ($line =~ m/--BEGIN/) {
3330 push @comment, $line;
3331 last;
3332 } elsif ($line eq "") {
3333 last;
3334 }
3335 }
3336 push @comment, <$fd>;
3337 $tag{'comment'} = \@comment;
3338 close $fd or return;
3339 if (!defined $tag{'name'}) {
3340 return
3341 };
3342 return %tag
3343}
3344
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003345sub parse_commit_text {
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00003346 my ($commit_text, $withparents) = @_;
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003347 my @commit_lines = split '\n', $commit_text;
Jakub Narebski717b8312006-07-31 21:22:15 +02003348 my %co;
3349
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003350 pop @commit_lines; # Remove '\0'
3351
Jakub Narebski198a2a82007-05-12 21:16:34 +02003352 if (! @commit_lines) {
3353 return;
3354 }
3355
Jakub Narebski717b8312006-07-31 21:22:15 +02003356 my $header = shift @commit_lines;
Jakub Narebski198a2a82007-05-12 21:16:34 +02003357 if ($header !~ m/^[0-9a-fA-F]{40}/) {
Jakub Narebski717b8312006-07-31 21:22:15 +02003358 return;
3359 }
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00003360 ($co{'id'}, my @parents) = split ' ', $header;
Jakub Narebski717b8312006-07-31 21:22:15 +02003361 while (my $line = shift @commit_lines) {
3362 last if $line eq "\n";
3363 if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
3364 $co{'tree'} = $1;
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00003365 } elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) {
Robert Fitzsimons208b2df2006-12-24 14:31:43 +00003366 push @parents, $1;
Jakub Narebski717b8312006-07-31 21:22:15 +02003367 } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
Zoltán Füzesi5ed5bbc2009-08-02 09:42:24 +02003368 $co{'author'} = to_utf8($1);
Jakub Narebski717b8312006-07-31 21:22:15 +02003369 $co{'author_epoch'} = $2;
3370 $co{'author_tz'} = $3;
Jakub Narebskiba00b8c2006-11-25 15:54:32 +01003371 if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
3372 $co{'author_name'} = $1;
3373 $co{'author_email'} = $2;
Jakub Narebski717b8312006-07-31 21:22:15 +02003374 } else {
3375 $co{'author_name'} = $co{'author'};
3376 }
3377 } elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
Zoltán Füzesi5ed5bbc2009-08-02 09:42:24 +02003378 $co{'committer'} = to_utf8($1);
Jakub Narebski717b8312006-07-31 21:22:15 +02003379 $co{'committer_epoch'} = $2;
3380 $co{'committer_tz'} = $3;
Jakub Narebskiba00b8c2006-11-25 15:54:32 +01003381 if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
3382 $co{'committer_name'} = $1;
3383 $co{'committer_email'} = $2;
3384 } else {
3385 $co{'committer_name'} = $co{'committer'};
3386 }
Jakub Narebski717b8312006-07-31 21:22:15 +02003387 }
3388 }
3389 if (!defined $co{'tree'}) {
3390 return;
3391 };
Robert Fitzsimons208b2df2006-12-24 14:31:43 +00003392 $co{'parents'} = \@parents;
3393 $co{'parent'} = $parents[0];
Jakub Narebski717b8312006-07-31 21:22:15 +02003394
3395 foreach my $title (@commit_lines) {
3396 $title =~ s/^ //;
3397 if ($title ne "") {
3398 $co{'title'} = chop_str($title, 80, 5);
3399 # remove leading stuff of merges to make the interesting part visible
3400 if (length($title) > 50) {
3401 $title =~ s/^Automatic //;
3402 $title =~ s/^merge (of|with) /Merge ... /i;
3403 if (length($title) > 50) {
3404 $title =~ s/(http|rsync):\/\///;
3405 }
3406 if (length($title) > 50) {
3407 $title =~ s/(master|www|rsync)\.//;
3408 }
3409 if (length($title) > 50) {
3410 $title =~ s/kernel.org:?//;
3411 }
3412 if (length($title) > 50) {
3413 $title =~ s/\/pub\/scm//;
3414 }
3415 }
3416 $co{'title_short'} = chop_str($title, 50, 5);
3417 last;
3418 }
3419 }
Joey Hess53c39672008-09-05 14:26:29 -04003420 if (! defined $co{'title'} || $co{'title'} eq "") {
Petr Baudis7e0fe5c2006-10-06 18:55:04 +02003421 $co{'title'} = $co{'title_short'} = '(no commit message)';
3422 }
Jakub Narebski717b8312006-07-31 21:22:15 +02003423 # remove added spaces
3424 foreach my $line (@commit_lines) {
3425 $line =~ s/^ //;
3426 }
3427 $co{'comment'} = \@commit_lines;
3428
3429 my $age = time - $co{'committer_epoch'};
3430 $co{'age'} = $age;
3431 $co{'age_string'} = age_string($age);
3432 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'});
3433 if ($age > 60*60*24*7*2) {
3434 $co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
3435 $co{'age_string_age'} = $co{'age_string'};
3436 } else {
3437 $co{'age_string_date'} = $co{'age_string'};
3438 $co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
3439 }
3440 return %co;
3441}
3442
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003443sub parse_commit {
3444 my ($commit_id) = @_;
3445 my %co;
3446
3447 local $/ = "\0";
3448
3449 open my $fd, "-|", git_cmd(), "rev-list",
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00003450 "--parents",
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003451 "--header",
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003452 "--max-count=1",
3453 $commit_id,
3454 "--",
Lea Wiemann074afaa2008-06-19 22:03:21 +02003455 or die_error(500, "Open git-rev-list failed");
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00003456 %co = parse_commit_text(<$fd>, 1);
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003457 close $fd;
3458
3459 return %co;
3460}
3461
3462sub parse_commits {
Jakub Narebski311e5522008-02-26 13:22:06 +01003463 my ($commit_id, $maxcount, $skip, $filename, @args) = @_;
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003464 my @cos;
3465
3466 $maxcount ||= 1;
3467 $skip ||= 0;
3468
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003469 local $/ = "\0";
3470
3471 open my $fd, "-|", git_cmd(), "rev-list",
3472 "--header",
Jakub Narebski311e5522008-02-26 13:22:06 +01003473 @args,
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003474 ("--max-count=" . $maxcount),
Robert Fitzsimonsf47efbb2006-12-24 14:31:49 +00003475 ("--skip=" . $skip),
Miklos Vajna868bc062007-07-12 20:39:27 +02003476 @extra_options,
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003477 $commit_id,
3478 "--",
3479 ($filename ? ($filename) : ())
Lea Wiemann074afaa2008-06-19 22:03:21 +02003480 or die_error(500, "Open git-rev-list failed");
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003481 while (my $line = <$fd>) {
3482 my %co = parse_commit_text($line);
3483 push @cos, \%co;
3484 }
3485 close $fd;
3486
3487 return wantarray ? @cos : \@cos;
3488}
3489
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003490# parse line of git-diff-tree "raw" output
Jakub Narebski740e67f2006-08-21 23:07:00 +02003491sub parse_difftree_raw_line {
3492 my $line = shift;
3493 my %res;
3494
3495 # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c'
3496 # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c'
3497 if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) {
3498 $res{'from_mode'} = $1;
3499 $res{'to_mode'} = $2;
3500 $res{'from_id'} = $3;
3501 $res{'to_id'} = $4;
Jakub Narebski4ed4a342008-04-05 21:13:24 +01003502 $res{'status'} = $5;
Jakub Narebski740e67f2006-08-21 23:07:00 +02003503 $res{'similarity'} = $6;
3504 if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003505 ($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7);
Jakub Narebski740e67f2006-08-21 23:07:00 +02003506 } else {
Jakub Narebski9d301452007-11-01 12:38:08 +01003507 $res{'from_file'} = $res{'to_file'} = $res{'file'} = unquote($7);
Jakub Narebski740e67f2006-08-21 23:07:00 +02003508 }
3509 }
Jakub Narebski78bc4032007-05-07 01:10:03 +02003510 # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh'
3511 # combined diff (for merge commit)
3512 elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) {
3513 $res{'nparents'} = length($1);
3514 $res{'from_mode'} = [ split(' ', $2) ];
3515 $res{'to_mode'} = pop @{$res{'from_mode'}};
3516 $res{'from_id'} = [ split(' ', $3) ];
3517 $res{'to_id'} = pop @{$res{'from_id'}};
3518 $res{'status'} = [ split('', $4) ];
3519 $res{'to_file'} = unquote($5);
3520 }
Jakub Narebski740e67f2006-08-21 23:07:00 +02003521 # 'c512b523472485aef4fff9e57b229d9d243c967f'
Jakub Narebski0edcb372006-08-31 00:36:04 +02003522 elsif ($line =~ m/^([0-9a-fA-F]{40})$/) {
3523 $res{'commit'} = $1;
3524 }
Jakub Narebski740e67f2006-08-21 23:07:00 +02003525
3526 return wantarray ? %res : \%res;
3527}
3528
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003529# wrapper: return parsed line of git-diff-tree "raw" output
3530# (the argument might be raw line, or parsed info)
3531sub parsed_difftree_line {
3532 my $line_or_ref = shift;
3533
3534 if (ref($line_or_ref) eq "HASH") {
3535 # pre-parsed (or generated by hand)
3536 return $line_or_ref;
3537 } else {
3538 return parse_difftree_raw_line($line_or_ref);
3539 }
3540}
3541
Jakub Narebskicb849b42006-08-31 00:32:15 +02003542# parse line of git-ls-tree output
Jakub Narebski74fd8722009-05-07 19:11:29 +02003543sub parse_ls_tree_line {
Jakub Narebskicb849b42006-08-31 00:32:15 +02003544 my $line = shift;
3545 my %opts = @_;
3546 my %res;
3547
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02003548 if ($opts{'-l'}) {
3549 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa 16717 panic.c'
3550 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40}) +(-|[0-9]+)\t(.+)$/s;
Jakub Narebskicb849b42006-08-31 00:32:15 +02003551
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02003552 $res{'mode'} = $1;
3553 $res{'type'} = $2;
3554 $res{'hash'} = $3;
3555 $res{'size'} = $4;
3556 if ($opts{'-z'}) {
3557 $res{'name'} = $5;
3558 } else {
3559 $res{'name'} = unquote($5);
3560 }
Jakub Narebskicb849b42006-08-31 00:32:15 +02003561 } else {
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02003562 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
3563 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
3564
3565 $res{'mode'} = $1;
3566 $res{'type'} = $2;
3567 $res{'hash'} = $3;
3568 if ($opts{'-z'}) {
3569 $res{'name'} = $4;
3570 } else {
3571 $res{'name'} = unquote($4);
3572 }
Jakub Narebskicb849b42006-08-31 00:32:15 +02003573 }
3574
3575 return wantarray ? %res : \%res;
3576}
3577
Jakub Narebski90921742007-06-08 13:27:42 +02003578# generates _two_ hashes, references to which are passed as 2 and 3 argument
3579sub parse_from_to_diffinfo {
3580 my ($diffinfo, $from, $to, @parents) = @_;
3581
3582 if ($diffinfo->{'nparents'}) {
3583 # combined diff
3584 $from->{'file'} = [];
3585 $from->{'href'} = [];
3586 fill_from_file_info($diffinfo, @parents)
3587 unless exists $diffinfo->{'from_file'};
3588 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
Jakub Narebski9d301452007-11-01 12:38:08 +01003589 $from->{'file'}[$i] =
3590 defined $diffinfo->{'from_file'}[$i] ?
3591 $diffinfo->{'from_file'}[$i] :
3592 $diffinfo->{'to_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02003593 if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
3594 $from->{'href'}[$i] = href(action=>"blob",
3595 hash_base=>$parents[$i],
3596 hash=>$diffinfo->{'from_id'}[$i],
3597 file_name=>$from->{'file'}[$i]);
3598 } else {
3599 $from->{'href'}[$i] = undef;
3600 }
3601 }
3602 } else {
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003603 # ordinary (not combined) diff
Jakub Narebski9d301452007-11-01 12:38:08 +01003604 $from->{'file'} = $diffinfo->{'from_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02003605 if ($diffinfo->{'status'} ne "A") { # not new (added) file
3606 $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent,
3607 hash=>$diffinfo->{'from_id'},
3608 file_name=>$from->{'file'});
3609 } else {
3610 delete $from->{'href'};
3611 }
3612 }
3613
Jakub Narebski9d301452007-11-01 12:38:08 +01003614 $to->{'file'} = $diffinfo->{'to_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02003615 if (!is_deleted($diffinfo)) { # file exists in result
3616 $to->{'href'} = href(action=>"blob", hash_base=>$hash,
3617 hash=>$diffinfo->{'to_id'},
3618 file_name=>$to->{'file'});
3619 } else {
3620 delete $to->{'href'};
3621 }
3622}
3623
Jakub Narebski717b8312006-07-31 21:22:15 +02003624## ......................................................................
3625## parse to array of hashes functions
3626
Jakub Narebskicd146402006-11-02 20:23:11 +01003627sub git_get_heads_list {
Giuseppe Bilotta9b3f3de2010-11-11 13:26:10 +01003628 my ($limit, @classes) = @_;
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01003629 @classes = ('heads') unless @classes;
Giuseppe Bilotta9b3f3de2010-11-11 13:26:10 +01003630 my @patterns = map { "refs/$_" } @classes;
Jakub Narebskicd146402006-11-02 20:23:11 +01003631 my @headslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02003632
Jakub Narebskicd146402006-11-02 20:23:11 +01003633 open my $fd, '-|', git_cmd(), 'for-each-ref',
3634 ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
3635 '--format=%(objectname) %(refname) %(subject)%00%(committer)',
Giuseppe Bilotta9b3f3de2010-11-11 13:26:10 +01003636 @patterns
Jakub Narebskic83a77e2006-09-15 03:43:28 +02003637 or return;
3638 while (my $line = <$fd>) {
Jakub Narebskicd146402006-11-02 20:23:11 +01003639 my %ref_item;
Jakub Narebski120ddde2006-09-19 14:33:22 +02003640
Jakub Narebskicd146402006-11-02 20:23:11 +01003641 chomp $line;
3642 my ($refinfo, $committerinfo) = split(/\0/, $line);
3643 my ($hash, $name, $title) = split(' ', $refinfo, 3);
3644 my ($committer, $epoch, $tz) =
3645 ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
Jakub Narebskibf901f82007-12-15 15:40:28 +01003646 $ref_item{'fullname'} = $name;
Giuseppe Bilotta60efa242010-11-11 13:26:09 +01003647 $name =~ s!^refs/(?:head|remote)s/!!;
Jakub Narebskicd146402006-11-02 20:23:11 +01003648
3649 $ref_item{'name'} = $name;
3650 $ref_item{'id'} = $hash;
3651 $ref_item{'title'} = $title || '(no commit message)';
3652 $ref_item{'epoch'} = $epoch;
3653 if ($epoch) {
3654 $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
3655 } else {
3656 $ref_item{'age'} = "unknown";
Jakub Narebski717b8312006-07-31 21:22:15 +02003657 }
Jakub Narebskicd146402006-11-02 20:23:11 +01003658
3659 push @headslist, \%ref_item;
Jakub Narebskic83a77e2006-09-15 03:43:28 +02003660 }
3661 close $fd;
Junio C Hamano7a13b992006-07-31 19:18:34 -07003662
Jakub Narebskicd146402006-11-02 20:23:11 +01003663 return wantarray ? @headslist : \@headslist;
3664}
Jakub Narebskic83a77e2006-09-15 03:43:28 +02003665
Jakub Narebskicd146402006-11-02 20:23:11 +01003666sub git_get_tags_list {
3667 my $limit = shift;
3668 my @tagslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02003669
Jakub Narebskicd146402006-11-02 20:23:11 +01003670 open my $fd, '-|', git_cmd(), 'for-each-ref',
3671 ($limit ? '--count='.($limit+1) : ()), '--sort=-creatordate',
3672 '--format=%(objectname) %(objecttype) %(refname) '.
3673 '%(*objectname) %(*objecttype) %(subject)%00%(creator)',
3674 'refs/tags'
3675 or return;
3676 while (my $line = <$fd>) {
3677 my %ref_item;
3678
3679 chomp $line;
3680 my ($refinfo, $creatorinfo) = split(/\0/, $line);
3681 my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6);
3682 my ($creator, $epoch, $tz) =
3683 ($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/);
Jakub Narebskibf901f82007-12-15 15:40:28 +01003684 $ref_item{'fullname'} = $name;
Jakub Narebskicd146402006-11-02 20:23:11 +01003685 $name =~ s!^refs/tags/!!;
3686
3687 $ref_item{'type'} = $type;
3688 $ref_item{'id'} = $id;
3689 $ref_item{'name'} = $name;
3690 if ($type eq "tag") {
3691 $ref_item{'subject'} = $title;
3692 $ref_item{'reftype'} = $reftype;
3693 $ref_item{'refid'} = $refid;
3694 } else {
3695 $ref_item{'reftype'} = $type;
3696 $ref_item{'refid'} = $id;
3697 }
3698
3699 if ($type eq "tag" || $type eq "commit") {
3700 $ref_item{'epoch'} = $epoch;
3701 if ($epoch) {
3702 $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
3703 } else {
3704 $ref_item{'age'} = "unknown";
3705 }
3706 }
3707
3708 push @tagslist, \%ref_item;
Jakub Narebski717b8312006-07-31 21:22:15 +02003709 }
Jakub Narebskicd146402006-11-02 20:23:11 +01003710 close $fd;
3711
3712 return wantarray ? @tagslist : \@tagslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02003713}
3714
3715## ----------------------------------------------------------------------
3716## filesystem-related functions
3717
3718sub get_file_owner {
3719 my $path = shift;
3720
3721 my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path);
3722 my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid);
3723 if (!defined $gcos) {
3724 return undef;
3725 }
3726 my $owner = $gcos;
3727 $owner =~ s/[,;].*$//;
Martin Koegler00f429a2007-06-03 17:42:44 +02003728 return to_utf8($owner);
Jakub Narebski717b8312006-07-31 21:22:15 +02003729}
3730
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01003731# assume that file exists
3732sub insert_file {
3733 my $filename = shift;
3734
3735 open my $fd, '<', $filename;
Jakub Narebski45868642008-12-08 14:13:21 +01003736 print map { to_utf8($_) } <$fd>;
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01003737 close $fd;
3738}
3739
Jakub Narebski717b8312006-07-31 21:22:15 +02003740## ......................................................................
3741## mimetype related functions
3742
3743sub mimetype_guess_file {
3744 my $filename = shift;
3745 my $mimemap = shift;
3746 -r $mimemap or return undef;
3747
3748 my %mimemap;
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02003749 open(my $mh, '<', $mimemap) or return undef;
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02003750 while (<$mh>) {
Jakub Narebski618918e2006-08-14 02:15:22 +02003751 next if m/^#/; # skip comments
Ludwig Nussel93a6ad12011-06-15 08:10:08 +02003752 my ($mimetype, @exts) = split(/\s+/);
3753 foreach my $ext (@exts) {
3754 $mimemap{$ext} = $mimetype;
Jakub Narebski717b8312006-07-31 21:22:15 +02003755 }
3756 }
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02003757 close($mh);
Jakub Narebski717b8312006-07-31 21:22:15 +02003758
Jakub Narebski80593192006-09-19 13:57:03 +02003759 $filename =~ /\.([^.]*)$/;
Jakub Narebski717b8312006-07-31 21:22:15 +02003760 return $mimemap{$1};
3761}
3762
3763sub mimetype_guess {
3764 my $filename = shift;
3765 my $mime;
3766 $filename =~ /\./ or return undef;
3767
3768 if ($mimetypes_file) {
3769 my $file = $mimetypes_file;
Jakub Narebskid5aa50d2006-08-14 02:16:33 +02003770 if ($file !~ m!^/!) { # if it is relative path
3771 # it is relative to project
3772 $file = "$projectroot/$project/$file";
3773 }
Jakub Narebski717b8312006-07-31 21:22:15 +02003774 $mime = mimetype_guess_file($filename, $file);
3775 }
3776 $mime ||= mimetype_guess_file($filename, '/etc/mime.types');
3777 return $mime;
3778}
3779
Jakub Narebski847e01f2006-08-14 02:05:47 +02003780sub blob_mimetype {
Jakub Narebski717b8312006-07-31 21:22:15 +02003781 my $fd = shift;
3782 my $filename = shift;
3783
3784 if ($filename) {
3785 my $mime = mimetype_guess($filename);
3786 $mime and return $mime;
3787 }
3788
3789 # just in case
3790 return $default_blob_plain_mimetype unless $fd;
3791
3792 if (-T $fd) {
Jakub Narebski7f718e82008-06-03 16:47:10 +02003793 return 'text/plain';
Jakub Narebski717b8312006-07-31 21:22:15 +02003794 } elsif (! $filename) {
3795 return 'application/octet-stream';
3796 } elsif ($filename =~ m/\.png$/i) {
3797 return 'image/png';
3798 } elsif ($filename =~ m/\.gif$/i) {
3799 return 'image/gif';
3800 } elsif ($filename =~ m/\.jpe?g$/i) {
3801 return 'image/jpeg';
3802 } else {
3803 return 'application/octet-stream';
3804 }
3805}
3806
Jakub Narebski7f718e82008-06-03 16:47:10 +02003807sub blob_contenttype {
3808 my ($fd, $file_name, $type) = @_;
3809
3810 $type ||= blob_mimetype($fd, $file_name);
3811 if ($type eq 'text/plain' && defined $default_text_plain_charset) {
3812 $type .= "; charset=$default_text_plain_charset";
3813 }
3814
3815 return $type;
3816}
3817
Jakub Narebski592ea412010-04-27 21:34:45 +02003818# guess file syntax for syntax highlighting; return undef if no highlighting
3819# the name of syntax can (in the future) depend on syntax highlighter used
3820sub guess_file_syntax {
3821 my ($highlight, $mimetype, $file_name) = @_;
3822 return undef unless ($highlight && defined $file_name);
Jakub Narebski592ea412010-04-27 21:34:45 +02003823 my $basename = basename($file_name, '.in');
3824 return $highlight_basename{$basename}
3825 if exists $highlight_basename{$basename};
3826
3827 $basename =~ /\.([^.]*)$/;
3828 my $ext = $1 or return undef;
3829 return $highlight_ext{$ext}
3830 if exists $highlight_ext{$ext};
3831
3832 return undef;
3833}
3834
3835# run highlighter and return FD of its output,
3836# or return original FD if no highlighting
3837sub run_highlighter {
3838 my ($fd, $highlight, $syntax) = @_;
3839 return $fd unless ($highlight && defined $syntax);
3840
Sylvain Rabot3ca73532010-12-30 22:20:29 +01003841 close $fd;
Jakub Narebski592ea412010-04-27 21:34:45 +02003842 open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
Christopher Wilson7ce896b2010-09-21 00:25:19 -07003843 quote_command($highlight_bin).
Kevin Cernekee6affdbe2011-03-16 15:34:13 -07003844 " --replace-tabs=8 --fragment --syntax $syntax |"
Jakub Narebski592ea412010-04-27 21:34:45 +02003845 or die_error(500, "Couldn't open file or run syntax highlighter");
3846 return $fd;
3847}
3848
Jakub Narebski717b8312006-07-31 21:22:15 +02003849## ======================================================================
3850## functions printing HTML: header, footer, error page
3851
Jakub Narebskiefb2d0c2010-04-24 16:01:10 +02003852sub get_page_title {
3853 my $title = to_utf8($site_name);
3854
Bernhard R. Link19d2d232012-01-30 21:07:37 +01003855 unless (defined $project) {
3856 if (defined $project_filter) {
Jakub Narebskif4212082012-02-12 16:21:30 +01003857 $title .= " - projects in '" . esc_path($project_filter) . "'";
Bernhard R. Link19d2d232012-01-30 21:07:37 +01003858 }
3859 return $title;
3860 }
Jakub Narebskiefb2d0c2010-04-24 16:01:10 +02003861 $title .= " - " . to_utf8($project);
3862
3863 return $title unless (defined $action);
3864 $title .= "/$action"; # $action is US-ASCII (7bit ASCII)
3865
3866 return $title unless (defined $file_name);
3867 $title .= " - " . esc_path($file_name);
3868 if ($action eq "tree" && $file_name !~ m|/$|) {
3869 $title .= "/";
3870 }
3871
3872 return $title;
3873}
3874
Jakub Narebski6ee90332011-06-22 13:50:46 +02003875sub get_content_type_html {
3876 # require explicit support from the UA if we are to send the page as
3877 # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
3878 # we have to do this because MSIE sometimes globs '*/*', pretending to
3879 # support xhtml+xml but choking when it gets what it asked for.
3880 if (defined $cgi->http('HTTP_ACCEPT') &&
3881 $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
3882 $cgi->Accept('application/xhtml+xml') != 0) {
3883 return 'application/xhtml+xml';
3884 } else {
3885 return 'text/html';
3886 }
3887}
3888
Jakub Narebski05bb5a22010-12-18 21:02:13 +01003889sub print_feed_meta {
3890 if (defined $project) {
3891 my %href_params = get_feed_info();
3892 if (!exists $href_params{'-title'}) {
3893 $href_params{'-title'} = 'log';
3894 }
3895
Ævar Arnfjörð Bjarmason0f54b7d2011-02-19 15:27:41 +00003896 foreach my $format (qw(RSS Atom)) {
Jakub Narebski05bb5a22010-12-18 21:02:13 +01003897 my $type = lc($format);
3898 my %link_attr = (
3899 '-rel' => 'alternate',
3900 '-title' => esc_attr("$project - $href_params{'-title'} - $format feed"),
3901 '-type' => "application/$type+xml"
3902 );
3903
3904 $href_params{'action'} = $type;
3905 $link_attr{'-href'} = href(%href_params);
3906 print "<link ".
3907 "rel=\"$link_attr{'-rel'}\" ".
3908 "title=\"$link_attr{'-title'}\" ".
3909 "href=\"$link_attr{'-href'}\" ".
3910 "type=\"$link_attr{'-type'}\" ".
3911 "/>\n";
3912
3913 $href_params{'extra_options'} = '--no-merges';
3914 $link_attr{'-href'} = href(%href_params);
3915 $link_attr{'-title'} .= ' (no merges)';
3916 print "<link ".
3917 "rel=\"$link_attr{'-rel'}\" ".
3918 "title=\"$link_attr{'-title'}\" ".
3919 "href=\"$link_attr{'-href'}\" ".
3920 "type=\"$link_attr{'-type'}\" ".
3921 "/>\n";
3922 }
3923
3924 } else {
3925 printf('<link rel="alternate" title="%s projects list" '.
3926 'href="%s" type="text/plain; charset=utf-8" />'."\n",
3927 esc_attr($site_name), href(project=>undef, action=>"project_index"));
3928 printf('<link rel="alternate" title="%s projects feeds" '.
3929 'href="%s" type="text/x-opml" />'."\n",
3930 esc_attr($site_name), href(project=>undef, action=>"opml"));
3931 }
3932}
3933
Jakub Narebski6ee90332011-06-22 13:50:46 +02003934sub print_header_links {
3935 my $status = shift;
3936
3937 # print out each stylesheet that exist, providing backwards capability
3938 # for those people who defined $stylesheet in a config file
3939 if (defined $stylesheet) {
3940 print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
3941 } else {
3942 foreach my $stylesheet (@stylesheets) {
3943 next unless $stylesheet;
3944 print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
3945 }
3946 }
3947 print_feed_meta()
3948 if ($status eq '200 OK');
3949 if (defined $favicon) {
3950 print qq(<link rel="shortcut icon" href=").esc_url($favicon).qq(" type="image/png" />\n);
3951 }
3952}
3953
Bernhard R. Link40efa222012-01-30 21:09:43 +01003954sub print_nav_breadcrumbs_path {
3955 my $dirprefix = undef;
3956 while (my $part = shift) {
3957 $dirprefix .= "/" if defined $dirprefix;
3958 $dirprefix .= $part;
3959 print $cgi->a({-href => href(project => undef,
3960 project_filter => $dirprefix,
3961 action => "project_list")},
3962 esc_html($part)) . " / ";
3963 }
3964}
3965
Jakub Narebski6ee90332011-06-22 13:50:46 +02003966sub print_nav_breadcrumbs {
3967 my %opts = @_;
3968
3969 print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
3970 if (defined $project) {
Bernhard R. Link4426ba22012-01-30 21:10:23 +01003971 my @dirname = split '/', $project;
3972 my $projectbasename = pop @dirname;
3973 print_nav_breadcrumbs_path(@dirname);
3974 print $cgi->a({-href => href(action=>"summary")}, esc_html($projectbasename));
Jakub Narebski6ee90332011-06-22 13:50:46 +02003975 if (defined $action) {
3976 my $action_print = $action ;
3977 if (defined $opts{-action_extra}) {
3978 $action_print = $cgi->a({-href => href(action=>$action)},
3979 $action);
3980 }
3981 print " / $action_print";
3982 }
3983 if (defined $opts{-action_extra}) {
3984 print " / $opts{-action_extra}";
3985 }
3986 print "\n";
Bernhard R. Link40efa222012-01-30 21:09:43 +01003987 } elsif (defined $project_filter) {
3988 print_nav_breadcrumbs_path(split '/', $project_filter);
Jakub Narebski6ee90332011-06-22 13:50:46 +02003989 }
3990}
3991
3992sub print_search_form {
3993 if (!defined $searchtext) {
3994 $searchtext = "";
3995 }
3996 my $search_hash;
3997 if (defined $hash_base) {
3998 $search_hash = $hash_base;
3999 } elsif (defined $hash) {
4000 $search_hash = $hash;
4001 } else {
4002 $search_hash = "HEAD";
4003 }
4004 my $action = $my_uri;
4005 my $use_pathinfo = gitweb_check_feature('pathinfo');
4006 if ($use_pathinfo) {
4007 $action .= "/".esc_url($project);
4008 }
4009 print $cgi->startform(-method => "get", -action => $action) .
4010 "<div class=\"search\">\n" .
4011 (!$use_pathinfo &&
4012 $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
4013 $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
4014 $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
4015 $cgi->popup_menu(-name => 'st', -default => 'commit',
4016 -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
4017 $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
4018 " search:\n",
Jakub Narebski84d9e2d2012-02-03 13:44:54 +01004019 $cgi->textfield(-name => "s", -value => $searchtext, -override => 1) . "\n" .
Jakub Narebski6ee90332011-06-22 13:50:46 +02004020 "<span title=\"Extended regular expression\">" .
4021 $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
4022 -checked => $search_use_regexp) .
4023 "</span>" .
4024 "</div>" .
4025 $cgi->end_form() . "\n";
4026}
4027
Kay Sievers12a88f22005-08-07 20:02:47 +02004028sub git_header_html {
Kay Sieversa59d4af2005-08-07 20:15:44 +02004029 my $status = shift || "200 OK";
Kay Sievers11044292005-10-19 03:18:45 +02004030 my $expires = shift;
Jakub Narebski7a597452010-04-24 16:00:04 +02004031 my %opts = @_;
Kay Sieversa59d4af2005-08-07 20:15:44 +02004032
Jakub Narebskiefb2d0c2010-04-24 16:01:10 +02004033 my $title = get_page_title();
Jakub Narebski6ee90332011-06-22 13:50:46 +02004034 my $content_type = get_content_type_html();
Jakub Narebski952c65f2006-08-22 16:52:50 +02004035 print $cgi->header(-type=>$content_type, -charset => 'utf-8',
Jakub Narebski7a597452010-04-24 16:00:04 +02004036 -status=> $status, -expires => $expires)
Jakub Narebskiad709ea2010-06-13 00:35:59 +02004037 unless ($opts{'-no_http_header'});
Jakub Narebski45c9a752006-12-27 23:59:51 +01004038 my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
Kay Sieversa59d4af2005-08-07 20:15:44 +02004039 print <<EOF;
Kay Sievers6191f8e2005-08-07 20:19:56 +02004040<?xml version="1.0" encoding="utf-8"?>
Kay Sievers161332a2005-08-07 19:49:46 +02004041<!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 +02004042<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
Jakub Narebskid4baf9e2006-08-17 11:21:28 +02004043<!-- git web interface version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
Jakub Narebskiae20de52006-06-21 09:48:03 +02004044<!-- git core binaries version $git_version -->
Kay Sievers161332a2005-08-07 19:49:46 +02004045<head>
Alp Tokerf6801d62006-07-11 11:19:34 +01004046<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
Jakub Narebski45c9a752006-12-27 23:59:51 +01004047<meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/>
Kay Sieversc994d622005-08-07 20:27:18 +02004048<meta name="robots" content="index, nofollow"/>
Kay Sieversb87d78d2005-08-07 20:21:04 +02004049<title>$title</title>
Kay Sievers161332a2005-08-07 19:49:46 +02004050EOF
Giuseppe Bilotta41a4d162009-01-31 02:31:52 +01004051 # the stylesheet, favicon etc urls won't work correctly with path_info
4052 # unless we set the appropriate base URL
Giuseppe Bilottac3254ae2009-01-31 02:31:50 +01004053 if ($ENV{'PATH_INFO'}) {
Giuseppe Bilotta81d3fe92009-02-15 10:18:36 +01004054 print "<base href=\"".esc_url($base_url)."\" />\n";
Giuseppe Bilottac3254ae2009-01-31 02:31:50 +01004055 }
Jakub Narebski6ee90332011-06-22 13:50:46 +02004056 print_header_links($status);
Lénaïc Huardc1355b72011-10-21 09:09:29 +02004057
4058 if (defined $site_html_head_string) {
4059 print to_utf8($site_html_head_string);
4060 }
4061
Matthias Lederhoferdd04c422006-08-06 13:25:41 +02004062 print "</head>\n" .
Alan Chandlerb2d34762006-10-03 13:49:03 +01004063 "<body>\n";
4064
John 'Warthog9' Hawley24d4afc2010-01-30 23:30:41 +01004065 if (defined $site_header && -f $site_header) {
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01004066 insert_file($site_header);
Alan Chandlerb2d34762006-10-03 13:49:03 +01004067 }
4068
Jonathan Nieder68220522010-09-03 19:45:09 -05004069 print "<div class=\"page_header\">\n";
4070 if (defined $logo) {
4071 print $cgi->a({-href => esc_url($logo_url),
4072 -title => $logo_label},
4073 $cgi->img({-src => esc_url($logo),
4074 -width => 72, -height => 27,
4075 -alt => "git",
4076 -class => "logo"}));
4077 }
Jakub Narebski6ee90332011-06-22 13:50:46 +02004078 print_nav_breadcrumbs(%opts);
Petr Baudisd77b5672007-05-17 04:24:19 +02004079 print "</div>\n";
4080
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004081 my $have_search = gitweb_check_feature('search');
Jakub Narebskif70dda22008-06-02 11:54:41 +02004082 if (defined $project && $have_search) {
Jakub Narebski6ee90332011-06-22 13:50:46 +02004083 print_search_form();
Kay Sievers4c02e3c2005-08-07 19:52:52 +02004084 }
Kay Sievers161332a2005-08-07 19:49:46 +02004085}
4086
Kay Sievers12a88f22005-08-07 20:02:47 +02004087sub git_footer_html {
Jakub Narebski35621982008-04-20 22:09:48 +02004088 my $feed_class = 'rss_logo';
4089
Kay Sievers6191f8e2005-08-07 20:19:56 +02004090 print "<div class=\"page_footer\">\n";
Kay Sieversb87d78d2005-08-07 20:21:04 +02004091 if (defined $project) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004092 my $descr = git_get_project_description($project);
Kay Sieversb87d78d2005-08-07 20:21:04 +02004093 if (defined $descr) {
Kay Sievers40c13812005-11-19 17:41:29 +01004094 print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
Kay Sievers6191f8e2005-08-07 20:19:56 +02004095 }
Jakub Narebski35621982008-04-20 22:09:48 +02004096
4097 my %href_params = get_feed_info();
4098 if (!%href_params) {
4099 $feed_class .= ' generic';
4100 }
4101 $href_params{'-title'} ||= 'log';
4102
Ævar Arnfjörð Bjarmason0f54b7d2011-02-19 15:27:41 +00004103 foreach my $format (qw(RSS Atom)) {
Jakub Narebski35621982008-04-20 22:09:48 +02004104 $href_params{'action'} = lc($format);
4105 print $cgi->a({-href => href(%href_params),
4106 -title => "$href_params{'-title'} $format feed",
4107 -class => $feed_class}, $format)."\n";
4108 }
4109
Kay Sieversc994d622005-08-07 20:27:18 +02004110 } else {
Bernhard R. Link56efd9d2012-01-30 21:09:00 +01004111 print $cgi->a({-href => href(project=>undef, action=>"opml",
4112 project_filter => $project_filter),
Jakub Narebski35621982008-04-20 22:09:48 +02004113 -class => $feed_class}, "OPML") . " ";
Bernhard R. Link56efd9d2012-01-30 21:09:00 +01004114 print $cgi->a({-href => href(project=>undef, action=>"project_index",
4115 project_filter => $project_filter),
Jakub Narebski35621982008-04-20 22:09:48 +02004116 -class => $feed_class}, "TXT") . "\n";
Kay Sieversff7669a2005-08-07 20:13:02 +02004117 }
Jakub Narebski35621982008-04-20 22:09:48 +02004118 print "</div>\n"; # class="page_footer"
Alan Chandlerb2d34762006-10-03 13:49:03 +01004119
Jakub Narebskiaa7dd052009-09-01 13:39:16 +02004120 if (defined $t0 && gitweb_check_feature('timed')) {
4121 print "<div id=\"generating_info\">\n";
4122 print 'This page took '.
4123 '<span id="generating_time" class="time_span">'.
Jakub Narebski3962f1d72010-11-09 19:27:54 +01004124 tv_interval($t0, [ gettimeofday() ]).
Jakub Narebskiaa7dd052009-09-01 13:39:16 +02004125 ' seconds </span>'.
4126 ' and '.
4127 '<span id="generating_cmd">'.
4128 $number_of_git_cmds.
4129 '</span> git commands '.
4130 " to generate.\n";
4131 print "</div>\n"; # class="page_footer"
4132 }
4133
John 'Warthog9' Hawley24d4afc2010-01-30 23:30:41 +01004134 if (defined $site_footer && -f $site_footer) {
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01004135 insert_file($site_footer);
Alan Chandlerb2d34762006-10-03 13:49:03 +01004136 }
4137
Junio C Hamanoabf411e2010-12-15 11:32:57 -08004138 print qq!<script type="text/javascript" src="!.esc_url($javascript).qq!"></script>\n!;
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +01004139 if (defined $action &&
4140 $action eq 'blame_incremental') {
Jakub Narebskic4ccf612009-09-01 13:39:19 +02004141 print qq!<script type="text/javascript">\n!.
4142 qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
4143 qq! "!. href() .qq!");\n!.
4144 qq!</script>\n!;
John 'Warthog9' Hawley291e52b2011-04-28 21:04:09 +02004145 } else {
Jakub Narebski2e987f92011-04-28 21:04:11 +02004146 my ($jstimezone, $tz_cookie, $datetime_class) =
4147 gitweb_get_feature('javascript-timezone');
4148
Jakub Narebskic4ccf612009-09-01 13:39:19 +02004149 print qq!<script type="text/javascript">\n!.
Jakub Narebski2e987f92011-04-28 21:04:11 +02004150 qq!window.onload = function () {\n!;
4151 if (gitweb_check_feature('javascript-actions')) {
4152 print qq! fixLinks();\n!;
4153 }
4154 if ($jstimezone && $tz_cookie && $datetime_class) {
4155 print qq! var tz_cookie = { name: '$tz_cookie', expires: 14, path: '/' };\n!. # in days
4156 qq! onloadTZSetup('$jstimezone', tz_cookie, '$datetime_class');\n!;
4157 }
4158 print qq!};\n!.
Jakub Narebskic4ccf612009-09-01 13:39:19 +02004159 qq!</script>\n!;
4160 }
4161
Alan Chandlerb2d34762006-10-03 13:49:03 +01004162 print "</body>\n" .
Kay Sievers9cd3d982005-08-07 20:17:42 +02004163 "</html>";
Kay Sievers161332a2005-08-07 19:49:46 +02004164}
4165
Jakub Narebski453541f2010-02-07 21:51:18 +01004166# die_error(<http_status_code>, <error_message>[, <detailed_html_description>])
Lea Wiemann074afaa2008-06-19 22:03:21 +02004167# Example: die_error(404, 'Hash not found')
4168# By convention, use the following status codes (as defined in RFC 2616):
4169# 400: Invalid or missing CGI parameters, or
4170# requested object exists but has wrong type.
4171# 403: Requested feature (like "pickaxe" or "snapshot") not enabled on
4172# this server or project.
4173# 404: Requested object/revision/project doesn't exist.
4174# 500: The server isn't configured properly, or
4175# an internal error occurred (e.g. failed assertions caused by bugs), or
4176# an unknown error occurred (e.g. the git binary died unexpectedly).
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +01004177# 503: The server is currently unavailable (because it is overloaded,
4178# or down for maintenance). Generally, this is a temporary state.
Kay Sievers061cc7c2005-08-07 20:15:57 +02004179sub die_error {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004180 my $status = shift || 500;
Jakub Narebski1df48762010-02-07 21:52:25 +01004181 my $error = esc_html(shift) || "Internal Server Error";
John 'Warthog9' Hawleyaa140132010-01-30 23:30:44 +01004182 my $extra = shift;
Jakub Narebski7a597452010-04-24 16:00:04 +02004183 my %opts = @_;
Kay Sievers664f4cc2005-08-07 20:17:19 +02004184
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +01004185 my %http_responses = (
4186 400 => '400 Bad Request',
4187 403 => '403 Forbidden',
4188 404 => '404 Not Found',
4189 500 => '500 Internal Server Error',
4190 503 => '503 Service Unavailable',
4191 );
Jakub Narebski7a597452010-04-24 16:00:04 +02004192 git_header_html($http_responses{$status}, undef, %opts);
Jakub Narebski59b9f612006-08-22 23:42:53 +02004193 print <<EOF;
4194<div class="page_body">
4195<br /><br />
4196$status - $error
4197<br />
Jakub Narebski59b9f612006-08-22 23:42:53 +02004198EOF
John 'Warthog9' Hawleyaa140132010-01-30 23:30:44 +01004199 if (defined $extra) {
4200 print "<hr />\n" .
4201 "$extra\n";
4202 }
4203 print "</div>\n";
4204
Kay Sieversa59d4af2005-08-07 20:15:44 +02004205 git_footer_html();
Jakub Narebski7a597452010-04-24 16:00:04 +02004206 goto DONE_GITWEB
4207 unless ($opts{'-error_handler'});
Kay Sieversa59d4af2005-08-07 20:15:44 +02004208}
4209
Jakub Narebski717b8312006-07-31 21:22:15 +02004210## ----------------------------------------------------------------------
4211## functions printing or outputting HTML: navigation
4212
Jakub Narebski847e01f2006-08-14 02:05:47 +02004213sub git_print_page_nav {
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02004214 my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_;
4215 $extra = '' if !defined $extra; # pager or formats
4216
4217 my @navs = qw(summary shortlog log commit commitdiff tree);
4218 if ($suppress) {
4219 @navs = grep { $_ ne $suppress } @navs;
4220 }
4221
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004222 my %arg = map { $_ => {action=>$_} } @navs;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02004223 if (defined $head) {
4224 for (qw(commit commitdiff)) {
Jakub Narebski3be8e722007-04-01 22:22:21 +02004225 $arg{$_}{'hash'} = $head;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02004226 }
4227 if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
4228 for (qw(shortlog log)) {
Jakub Narebski3be8e722007-04-01 22:22:21 +02004229 $arg{$_}{'hash'} = $head;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02004230 }
4231 }
4232 }
Petr Baudisd627f682008-10-02 16:36:52 +02004233
Jakub Narebski3be8e722007-04-01 22:22:21 +02004234 $arg{'tree'}{'hash'} = $treehead if defined $treehead;
4235 $arg{'tree'}{'hash_base'} = $treebase if defined $treebase;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02004236
Junio C Hamanoa7c5a282008-11-29 13:02:08 -08004237 my @actions = gitweb_get_feature('actions');
Jakub Narebski2b11e052008-10-12 00:02:32 +02004238 my %repl = (
4239 '%' => '%',
4240 'n' => $project, # project name
4241 'f' => $git_dir, # project path within filesystem
4242 'h' => $treehead || '', # current hash ('h' parameter)
4243 'b' => $treebase || '', # hash base ('hb' parameter)
4244 );
Petr Baudisd627f682008-10-02 16:36:52 +02004245 while (@actions) {
Jakub Narebski2b11e052008-10-12 00:02:32 +02004246 my ($label, $link, $pos) = splice(@actions,0,3);
4247 # insert
Petr Baudisd627f682008-10-02 16:36:52 +02004248 @navs = map { $_ eq $pos ? ($_, $label) : $_ } @navs;
4249 # munch munch
Jakub Narebski2b11e052008-10-12 00:02:32 +02004250 $link =~ s/%([%nfhb])/$repl{$1}/g;
Petr Baudisd627f682008-10-02 16:36:52 +02004251 $arg{$label}{'_href'} = $link;
4252 }
4253
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02004254 print "<div class=\"page_nav\">\n" .
4255 (join " | ",
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004256 map { $_ eq $current ?
Petr Baudisd627f682008-10-02 16:36:52 +02004257 $_ : $cgi->a({-href => ($arg{$_}{_href} ? $arg{$_}{_href} : href(%{$arg{$_}}))}, "$_")
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004258 } @navs);
Jakub Narebski898a8932006-07-30 16:14:43 +02004259 print "<br/>\n$extra<br/>\n" .
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02004260 "</div>\n";
4261}
4262
Giuseppe Bilotta11e7bec2010-11-11 13:26:12 +01004263# returns a submenu for the nagivation of the refs views (tags, heads,
4264# remotes) with the current view disabled and the remotes view only
4265# available if the feature is enabled
4266sub format_ref_views {
4267 my ($current) = @_;
4268 my @ref_views = qw{tags heads};
4269 push @ref_views, 'remotes' if gitweb_check_feature('remote_heads');
4270 return join " | ", map {
4271 $_ eq $current ? $_ :
4272 $cgi->a({-href => href(action=>$_)}, $_)
4273 } @ref_views
4274}
4275
Jakub Narebski847e01f2006-08-14 02:05:47 +02004276sub format_paging_nav {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01004277 my ($action, $page, $has_next_link) = @_;
Jakub Narebski43ffc062006-07-30 17:49:00 +02004278 my $paging_nav;
4279
4280
Jakub Narebski43ffc062006-07-30 17:49:00 +02004281 if ($page > 0) {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01004282 $paging_nav .=
4283 $cgi->a({-href => href(-replay=>1, page=>undef)}, "first") .
4284 " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01004285 $cgi->a({-href => href(-replay=>1, page=>$page-1),
Jakub Narebski26298b52006-08-10 12:38:47 +02004286 -accesskey => "p", -title => "Alt-p"}, "prev");
Jakub Narebski43ffc062006-07-30 17:49:00 +02004287 } else {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01004288 $paging_nav .= "first &sdot; prev";
Jakub Narebski43ffc062006-07-30 17:49:00 +02004289 }
4290
Lea Wiemann1f684dc2008-05-28 01:25:42 +02004291 if ($has_next_link) {
Jakub Narebski43ffc062006-07-30 17:49:00 +02004292 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01004293 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Jakub Narebski26298b52006-08-10 12:38:47 +02004294 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski43ffc062006-07-30 17:49:00 +02004295 } else {
4296 $paging_nav .= " &sdot; next";
4297 }
4298
4299 return $paging_nav;
4300}
4301
Jakub Narebski717b8312006-07-31 21:22:15 +02004302## ......................................................................
4303## functions printing or outputting HTML: div
Kay Sievers42f7eb92005-08-07 20:21:46 +02004304
Jakub Narebski847e01f2006-08-14 02:05:47 +02004305sub git_print_header_div {
Jakub Narebski717b8312006-07-31 21:22:15 +02004306 my ($action, $title, $hash, $hash_base) = @_;
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004307 my %args = ();
Jakub Narebski717b8312006-07-31 21:22:15 +02004308
Jakub Narebski3be8e722007-04-01 22:22:21 +02004309 $args{'action'} = $action;
4310 $args{'hash'} = $hash if $hash;
4311 $args{'hash_base'} = $hash_base if $hash_base;
Jakub Narebski717b8312006-07-31 21:22:15 +02004312
4313 print "<div class=\"header\">\n" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004314 $cgi->a({-href => href(%args), -class => "title"},
4315 $title ? $title : $action) .
4316 "\n</div>\n";
Kay Sievers42f7eb92005-08-07 20:21:46 +02004317}
4318
Giuseppe Bilotta0e656992010-11-11 13:26:15 +01004319sub format_repo_url {
4320 my ($name, $url) = @_;
4321 return "<tr class=\"metadata_url\"><td>$name</td><td>$url</td></tr>\n";
4322}
4323
Giuseppe Bilottab891d522010-11-11 13:26:16 +01004324# Group output by placing it in a DIV element and adding a header.
4325# Options for start_div() can be provided by passing a hash reference as the
4326# first parameter to the function.
4327# Options to git_print_header_div() can be provided by passing an array
4328# reference. This must follow the options to start_div if they are present.
4329# The content can be a scalar, which is output as-is, a scalar reference, which
4330# is output after html escaping, an IO handle passed either as *handle or
4331# *handle{IO}, or a function reference. In the latter case all following
4332# parameters will be taken as argument to the content function call.
4333sub git_print_section {
4334 my ($div_args, $header_args, $content);
4335 my $arg = shift;
4336 if (ref($arg) eq 'HASH') {
4337 $div_args = $arg;
4338 $arg = shift;
4339 }
4340 if (ref($arg) eq 'ARRAY') {
4341 $header_args = $arg;
4342 $arg = shift;
4343 }
4344 $content = $arg;
4345
4346 print $cgi->start_div($div_args);
4347 git_print_header_div(@$header_args);
4348
4349 if (ref($content) eq 'CODE') {
4350 $content->(@_);
4351 } elsif (ref($content) eq 'SCALAR') {
4352 print esc_html($$content);
4353 } elsif (ref($content) eq 'GLOB' or ref($content) eq 'IO::Handle') {
4354 print <$content>;
4355 } elsif (!ref($content) && defined($content)) {
4356 print $content;
4357 }
4358
4359 print $cgi->end_div;
4360}
4361
Jakub Narebski256b7b42011-04-28 21:04:07 +02004362sub format_timestamp_html {
Jakub Narebskice71b072011-04-28 21:04:08 +02004363 my $date = shift;
Jakub Narebski2e987f92011-04-28 21:04:11 +02004364 my $strtime = $date->{'rfc2822'};
John 'Warthog9' Hawley0cf207f2010-01-30 23:30:42 +01004365
Jakub Narebski2e987f92011-04-28 21:04:11 +02004366 my (undef, undef, $datetime_class) =
4367 gitweb_get_feature('javascript-timezone');
4368 if ($datetime_class) {
4369 $strtime = qq!<span class="$datetime_class">$strtime</span>!;
Jakub Narebskia44465c2006-08-28 23:17:31 +02004370 }
John 'Warthog9' Hawley0cf207f2010-01-30 23:30:42 +01004371
Jakub Narebski256b7b42011-04-28 21:04:07 +02004372 my $localtime_format = '(%02d:%02d %s)';
4373 if ($date->{'hour_local'} < 6) {
4374 $localtime_format = '(<span class="atnight">%02d:%02d</span> %s)';
Jakub Narebskia44465c2006-08-28 23:17:31 +02004375 }
Jakub Narebski256b7b42011-04-28 21:04:07 +02004376 $strtime .= ' ' .
4377 sprintf($localtime_format,
4378 $date->{'hour_local'}, $date->{'minute_local'}, $date->{'tz_local'});
John 'Warthog9' Hawley0cf207f2010-01-30 23:30:42 +01004379
Jakub Narebski256b7b42011-04-28 21:04:07 +02004380 return $strtime;
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004381}
4382
4383# Outputs the author name and date in long form
4384sub git_print_authorship {
4385 my $co = shift;
4386 my %opts = @_;
4387 my $tag = $opts{-tag} || 'div';
Stephen Boyde133d652009-10-15 21:14:59 -07004388 my $author = $co->{'author_name'};
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004389
4390 my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
4391 print "<$tag class=\"author_date\">" .
Stephen Boyde133d652009-10-15 21:14:59 -07004392 format_search_author($author, "author", esc_html($author)) .
Jakub Narebskice71b072011-04-28 21:04:08 +02004393 " [".format_timestamp_html(\%ad)."]".
Jakub Narebski256b7b42011-04-28 21:04:07 +02004394 git_get_avatar($co->{'author_email'}, -pad_before => 1) .
4395 "</$tag>\n";
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004396}
4397
4398# Outputs table rows containing the full author or committer information,
Ralf Wildenhues22e5e582010-08-22 13:12:12 +02004399# in the format expected for 'commit' view (& similar).
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004400# Parameters are a commit hash reference, followed by the list of people
Ralf Wildenhues22e5e582010-08-22 13:12:12 +02004401# to output information for. If the list is empty it defaults to both
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004402# author and committer.
4403sub git_print_authorship_rows {
4404 my $co = shift;
4405 # too bad we can't use @people = @_ || ('author', 'committer')
4406 my @people = @_;
4407 @people = ('author', 'committer') unless @people;
4408 foreach my $who (@people) {
4409 my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"});
Stephen Boyde133d652009-10-15 21:14:59 -07004410 print "<tr><td>$who</td><td>" .
4411 format_search_author($co->{"${who}_name"}, $who,
Jakub Narebski256b7b42011-04-28 21:04:07 +02004412 esc_html($co->{"${who}_name"})) . " " .
Stephen Boyde133d652009-10-15 21:14:59 -07004413 format_search_author($co->{"${who}_email"}, $who,
Jakub Narebski256b7b42011-04-28 21:04:07 +02004414 esc_html("<" . $co->{"${who}_email"} . ">")) .
Stephen Boyde133d652009-10-15 21:14:59 -07004415 "</td><td rowspan=\"2\">" .
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02004416 git_get_avatar($co->{"${who}_email"}, -size => 'double') .
4417 "</td></tr>\n" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004418 "<tr>" .
Jakub Narebski256b7b42011-04-28 21:04:07 +02004419 "<td></td><td>" .
Jakub Narebskice71b072011-04-28 21:04:08 +02004420 format_timestamp_html(\%wd) .
Jakub Narebski256b7b42011-04-28 21:04:07 +02004421 "</td>" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004422 "</tr>\n";
4423 }
Jakub Narebski6fd92a22006-08-28 14:48:12 +02004424}
4425
Jakub Narebski717b8312006-07-31 21:22:15 +02004426sub git_print_page_path {
4427 my $name = shift;
4428 my $type = shift;
Luben Tuikov59fb1c92006-08-17 10:39:29 -07004429 my $hb = shift;
Junio C Hamanodf2c37a2006-01-09 13:13:39 +01004430
Jakub Narebski4df118e2006-10-21 17:53:55 +02004431
4432 print "<div class=\"page_path\">";
4433 print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
Martin Koegler00f429a2007-06-03 17:42:44 +02004434 -title => 'tree root'}, to_utf8("[$project]"));
Jakub Narebski4df118e2006-10-21 17:53:55 +02004435 print " / ";
4436 if (defined $name) {
Jakub Narebski762c7202006-09-04 18:17:58 +02004437 my @dirname = split '/', $name;
4438 my $basename = pop @dirname;
4439 my $fullname = '';
4440
Jakub Narebski762c7202006-09-04 18:17:58 +02004441 foreach my $dir (@dirname) {
Petr Baudis16fdb482006-09-21 02:05:50 +02004442 $fullname .= ($fullname ? '/' : '') . $dir;
Jakub Narebski762c7202006-09-04 18:17:58 +02004443 print $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
4444 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01004445 -title => $fullname}, esc_path($dir));
Petr Baudis26d0a972006-09-23 01:00:12 +02004446 print " / ";
Jakub Narebski762c7202006-09-04 18:17:58 +02004447 }
4448 if (defined $type && $type eq 'blob') {
Jakub Narebski952c65f2006-08-22 16:52:50 +02004449 print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name,
Jakub Narebski762c7202006-09-04 18:17:58 +02004450 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01004451 -title => $name}, esc_path($basename));
Jakub Narebski762c7202006-09-04 18:17:58 +02004452 } elsif (defined $type && $type eq 'tree') {
4453 print $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
4454 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01004455 -title => $name}, esc_path($basename));
Jakub Narebski4df118e2006-10-21 17:53:55 +02004456 print " / ";
Luben Tuikov59fb1c92006-08-17 10:39:29 -07004457 } else {
Jakub Narebski403d0902006-11-08 11:48:56 +01004458 print esc_path($basename);
Luben Tuikov59fb1c92006-08-17 10:39:29 -07004459 }
Kay Sievers8ed23e12005-08-07 20:05:44 +02004460 }
Jakub Narebski4df118e2006-10-21 17:53:55 +02004461 print "<br/></div>\n";
Kay Sievers4c02e3c2005-08-07 19:52:52 +02004462}
4463
Jakub Narebski74fd8722009-05-07 19:11:29 +02004464sub git_print_log {
Jakub Narebskid16d0932006-08-17 11:21:23 +02004465 my $log = shift;
Jakub Narebskib7f92532006-08-28 14:48:10 +02004466 my %opts = @_;
Jakub Narebskid16d0932006-08-17 11:21:23 +02004467
Jakub Narebskib7f92532006-08-28 14:48:10 +02004468 if ($opts{'-remove_title'}) {
4469 # remove title, i.e. first line of log
4470 shift @$log;
4471 }
Jakub Narebskid16d0932006-08-17 11:21:23 +02004472 # remove leading empty lines
4473 while (defined $log->[0] && $log->[0] eq "") {
4474 shift @$log;
4475 }
4476
4477 # print log
4478 my $signoff = 0;
4479 my $empty = 0;
4480 foreach my $line (@$log) {
Jakub Narebskib7f92532006-08-28 14:48:10 +02004481 if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
4482 $signoff = 1;
Jakub Narebskifba20b42006-08-28 14:48:13 +02004483 $empty = 0;
Jakub Narebskib7f92532006-08-28 14:48:10 +02004484 if (! $opts{'-remove_signoff'}) {
4485 print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n";
4486 next;
4487 } else {
4488 # remove signoff lines
4489 next;
4490 }
4491 } else {
4492 $signoff = 0;
4493 }
4494
Jakub Narebskid16d0932006-08-17 11:21:23 +02004495 # print only one empty line
4496 # do not print empty line after signoff
4497 if ($line eq "") {
4498 next if ($empty || $signoff);
4499 $empty = 1;
4500 } else {
4501 $empty = 0;
4502 }
Jakub Narebskib7f92532006-08-28 14:48:10 +02004503
4504 print format_log_line_html($line) . "<br/>\n";
4505 }
4506
4507 if ($opts{'-final_empty_line'}) {
4508 # end with single empty line
4509 print "<br/>\n" unless $empty;
Jakub Narebskid16d0932006-08-17 11:21:23 +02004510 }
4511}
4512
Jakub Narebskie33fba42006-12-10 13:25:46 +01004513# return link target (what link points to)
4514sub git_get_link_target {
4515 my $hash = shift;
4516 my $link_target;
4517
4518 # read link
4519 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
4520 or return;
4521 {
Jakub Narebski34122b52009-05-11 03:29:40 +02004522 local $/ = undef;
Jakub Narebskie33fba42006-12-10 13:25:46 +01004523 $link_target = <$fd>;
4524 }
4525 close $fd
4526 or return;
4527
4528 return $link_target;
4529}
4530
Jakub Narebski3bf9d572006-12-10 13:25:48 +01004531# given link target, and the directory (basedir) the link is in,
4532# return target of link relative to top directory (top tree);
4533# return undef if it is not possible (including absolute links).
4534sub normalize_link_target {
Jakub Narebski15c54fe2009-05-11 19:45:11 +02004535 my ($link_target, $basedir) = @_;
Jakub Narebski3bf9d572006-12-10 13:25:48 +01004536
4537 # absolute symlinks (beginning with '/') cannot be normalized
4538 return if (substr($link_target, 0, 1) eq '/');
4539
4540 # normalize link target to path from top (root) tree (dir)
4541 my $path;
4542 if ($basedir) {
4543 $path = $basedir . '/' . $link_target;
4544 } else {
4545 # we are in top (root) tree (dir)
4546 $path = $link_target;
4547 }
4548
4549 # remove //, /./, and /../
4550 my @path_parts;
4551 foreach my $part (split('/', $path)) {
4552 # discard '.' and ''
4553 next if (!$part || $part eq '.');
4554 # handle '..'
4555 if ($part eq '..') {
4556 if (@path_parts) {
4557 pop @path_parts;
4558 } else {
4559 # link leads outside repository (outside top dir)
4560 return;
4561 }
4562 } else {
4563 push @path_parts, $part;
4564 }
4565 }
4566 $path = join('/', @path_parts);
4567
4568 return $path;
4569}
Jakub Narebskie33fba42006-12-10 13:25:46 +01004570
Jakub Narebskifa702002006-08-31 00:35:07 +02004571# print tree entry (row of git_tree), but without encompassing <tr> element
4572sub git_print_tree_entry {
4573 my ($t, $basedir, $hash_base, $have_blame) = @_;
4574
4575 my %base_key = ();
Jakub Narebskie33fba42006-12-10 13:25:46 +01004576 $base_key{'hash_base'} = $hash_base if defined $hash_base;
Jakub Narebskifa702002006-08-31 00:35:07 +02004577
Luben Tuikov4de741b2006-09-25 22:38:16 -07004578 # The format of a table row is: mode list link. Where mode is
4579 # the mode of the entry, list is the name of the entry, an href,
4580 # and link is the action links of the entry.
4581
Jakub Narebskifa702002006-08-31 00:35:07 +02004582 print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n";
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02004583 if (exists $t->{'size'}) {
4584 print "<td class=\"size\">$t->{'size'}</td>\n";
4585 }
Jakub Narebskifa702002006-08-31 00:35:07 +02004586 if ($t->{'type'} eq "blob") {
4587 print "<td class=\"list\">" .
Luben Tuikov4de741b2006-09-25 22:38:16 -07004588 $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02004589 file_name=>"$basedir$t->{'name'}", %base_key),
Jakub Narebskie33fba42006-12-10 13:25:46 +01004590 -class => "list"}, esc_path($t->{'name'}));
4591 if (S_ISLNK(oct $t->{'mode'})) {
4592 my $link_target = git_get_link_target($t->{'hash'});
4593 if ($link_target) {
Jakub Narebski15c54fe2009-05-11 19:45:11 +02004594 my $norm_target = normalize_link_target($link_target, $basedir);
Jakub Narebski3bf9d572006-12-10 13:25:48 +01004595 if (defined $norm_target) {
4596 print " -> " .
4597 $cgi->a({-href => href(action=>"object", hash_base=>$hash_base,
4598 file_name=>$norm_target),
4599 -title => $norm_target}, esc_path($link_target));
4600 } else {
4601 print " -> " . esc_path($link_target);
4602 }
Jakub Narebskie33fba42006-12-10 13:25:46 +01004603 }
4604 }
4605 print "</td>\n";
Luben Tuikov4de741b2006-09-25 22:38:16 -07004606 print "<td class=\"link\">";
Petr Baudis4777b012006-10-24 05:36:10 +02004607 print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
Jakub Narebskie33fba42006-12-10 13:25:46 +01004608 file_name=>"$basedir$t->{'name'}", %base_key)},
4609 "blob");
Jakub Narebskifa702002006-08-31 00:35:07 +02004610 if ($have_blame) {
Petr Baudis4777b012006-10-24 05:36:10 +02004611 print " | " .
4612 $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
Jakub Narebskie33fba42006-12-10 13:25:46 +01004613 file_name=>"$basedir$t->{'name'}", %base_key)},
4614 "blame");
Jakub Narebskifa702002006-08-31 00:35:07 +02004615 }
4616 if (defined $hash_base) {
Petr Baudis4777b012006-10-24 05:36:10 +02004617 print " | " .
4618 $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
Jakub Narebskifa702002006-08-31 00:35:07 +02004619 hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
4620 "history");
4621 }
4622 print " | " .
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07004623 $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base,
Jakub Narebskie7fb0222006-10-21 17:52:19 +02004624 file_name=>"$basedir$t->{'name'}")},
4625 "raw");
Luben Tuikov4de741b2006-09-25 22:38:16 -07004626 print "</td>\n";
Jakub Narebskifa702002006-08-31 00:35:07 +02004627
4628 } elsif ($t->{'type'} eq "tree") {
Luben Tuikov0fa105e2006-09-26 12:45:37 -07004629 print "<td class=\"list\">";
4630 print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02004631 file_name=>"$basedir$t->{'name'}",
4632 %base_key)},
Jakub Narebski403d0902006-11-08 11:48:56 +01004633 esc_path($t->{'name'}));
Luben Tuikov0fa105e2006-09-26 12:45:37 -07004634 print "</td>\n";
4635 print "<td class=\"link\">";
Petr Baudis4777b012006-10-24 05:36:10 +02004636 print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02004637 file_name=>"$basedir$t->{'name'}",
4638 %base_key)},
Jakub Narebskie33fba42006-12-10 13:25:46 +01004639 "tree");
Jakub Narebskifa702002006-08-31 00:35:07 +02004640 if (defined $hash_base) {
Petr Baudis4777b012006-10-24 05:36:10 +02004641 print " | " .
4642 $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
Jakub Narebskifa702002006-08-31 00:35:07 +02004643 file_name=>"$basedir$t->{'name'}")},
4644 "history");
4645 }
4646 print "</td>\n";
Jakub Narebski01ac1e32007-07-28 16:27:31 +02004647 } else {
4648 # unknown object: we can only present history for it
4649 # (this includes 'commit' object, i.e. submodule support)
4650 print "<td class=\"list\">" .
4651 esc_path($t->{'name'}) .
4652 "</td>\n";
4653 print "<td class=\"link\">";
4654 if (defined $hash_base) {
4655 print $cgi->a({-href => href(action=>"history",
4656 hash_base=>$hash_base,
4657 file_name=>"$basedir$t->{'name'}")},
4658 "history");
4659 }
4660 print "</td>\n";
Jakub Narebskifa702002006-08-31 00:35:07 +02004661 }
4662}
4663
Jakub Narebski717b8312006-07-31 21:22:15 +02004664## ......................................................................
4665## functions printing large fragments of HTML
Kay Sieversede5e102005-08-07 20:23:12 +02004666
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004667# get pre-image filenames for merge (combined) diff
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02004668sub fill_from_file_info {
4669 my ($diff, @parents) = @_;
4670
4671 $diff->{'from_file'} = [ ];
4672 $diff->{'from_file'}[$diff->{'nparents'} - 1] = undef;
4673 for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
4674 if ($diff->{'status'}[$i] eq 'R' ||
4675 $diff->{'status'}[$i] eq 'C') {
4676 $diff->{'from_file'}[$i] =
4677 git_get_path_by_hash($parents[$i], $diff->{'from_id'}[$i]);
4678 }
4679 }
4680
4681 return $diff;
4682}
4683
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004684# is current raw difftree line of file deletion
Jakub Narebski90921742007-06-08 13:27:42 +02004685sub is_deleted {
4686 my $diffinfo = shift;
4687
Jakub Narebski4ed4a342008-04-05 21:13:24 +01004688 return $diffinfo->{'to_id'} eq ('0' x 40);
Jakub Narebski90921742007-06-08 13:27:42 +02004689}
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02004690
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004691# does patch correspond to [previous] difftree raw line
4692# $diffinfo - hashref of parsed raw diff format
4693# $patchinfo - hashref of parsed patch diff format
4694# (the same keys as in $diffinfo)
4695sub is_patch_split {
4696 my ($diffinfo, $patchinfo) = @_;
4697
4698 return defined $diffinfo && defined $patchinfo
Jakub Narebski9d301452007-11-01 12:38:08 +01004699 && $diffinfo->{'to_file'} eq $patchinfo->{'to_file'};
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004700}
4701
4702
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004703sub git_difftree_body {
Jakub Narebskied224de2007-05-07 01:10:04 +02004704 my ($difftree, $hash, @parents) = @_;
4705 my ($parent) = $parents[0];
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004706 my $have_blame = gitweb_check_feature('blame');
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004707 print "<div class=\"list_head\">\n";
4708 if ($#{$difftree} > 10) {
4709 print(($#{$difftree} + 1) . " files changed:\n");
4710 }
4711 print "</div>\n";
4712
Jakub Narebskied224de2007-05-07 01:10:04 +02004713 print "<table class=\"" .
4714 (@parents > 1 ? "combined " : "") .
4715 "diff_tree\">\n";
Jakub Narebski47598d72007-06-08 13:24:56 +02004716
4717 # header only for combined diff in 'commitdiff' view
Jakub Narebski3ef408a2007-09-08 21:54:28 +02004718 my $has_header = @$difftree && @parents > 1 && $action eq 'commitdiff';
Jakub Narebski47598d72007-06-08 13:24:56 +02004719 if ($has_header) {
4720 # table header
4721 print "<thead><tr>\n" .
4722 "<th></th><th></th>\n"; # filename, patchN link
4723 for (my $i = 0; $i < @parents; $i++) {
4724 my $par = $parents[$i];
4725 print "<th>" .
4726 $cgi->a({-href => href(action=>"commitdiff",
4727 hash=>$hash, hash_parent=>$par),
4728 -title => 'commitdiff to parent number ' .
4729 ($i+1) . ': ' . substr($par,0,7)},
4730 $i+1) .
4731 "&nbsp;</th>\n";
4732 }
4733 print "</tr></thead>\n<tbody>\n";
4734 }
4735
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004736 my $alternate = 1;
Jakub Narebskib4657e72006-08-28 14:48:14 +02004737 my $patchno = 0;
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004738 foreach my $line (@{$difftree}) {
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004739 my $diff = parsed_difftree_line($line);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004740
4741 if ($alternate) {
4742 print "<tr class=\"dark\">\n";
4743 } else {
4744 print "<tr class=\"light\">\n";
4745 }
4746 $alternate ^= 1;
4747
Jakub Narebski493e01d2007-05-07 01:10:06 +02004748 if (exists $diff->{'nparents'}) { # combined diff
Jakub Narebskied224de2007-05-07 01:10:04 +02004749
Jakub Narebski493e01d2007-05-07 01:10:06 +02004750 fill_from_file_info($diff, @parents)
4751 unless exists $diff->{'from_file'};
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02004752
Jakub Narebski90921742007-06-08 13:27:42 +02004753 if (!is_deleted($diff)) {
Jakub Narebskied224de2007-05-07 01:10:04 +02004754 # file exists in the result (child) commit
4755 print "<td>" .
Jakub Narebski493e01d2007-05-07 01:10:06 +02004756 $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4757 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02004758 hash_base=>$hash),
Jakub Narebski493e01d2007-05-07 01:10:06 +02004759 -class => "list"}, esc_path($diff->{'to_file'})) .
Jakub Narebskied224de2007-05-07 01:10:04 +02004760 "</td>\n";
4761 } else {
4762 print "<td>" .
Jakub Narebski493e01d2007-05-07 01:10:06 +02004763 esc_path($diff->{'to_file'}) .
Jakub Narebskied224de2007-05-07 01:10:04 +02004764 "</td>\n";
4765 }
4766
4767 if ($action eq 'commitdiff') {
4768 # link to patch
4769 $patchno++;
4770 print "<td class=\"link\">" .
Kevin Cernekee5e96a842011-03-18 17:00:16 +01004771 $cgi->a({-href => href(-anchor=>"patch$patchno")},
4772 "patch") .
Jakub Narebskied224de2007-05-07 01:10:04 +02004773 " | " .
4774 "</td>\n";
4775 }
4776
4777 my $has_history = 0;
4778 my $not_deleted = 0;
Jakub Narebski493e01d2007-05-07 01:10:06 +02004779 for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
Jakub Narebskied224de2007-05-07 01:10:04 +02004780 my $hash_parent = $parents[$i];
Jakub Narebski493e01d2007-05-07 01:10:06 +02004781 my $from_hash = $diff->{'from_id'}[$i];
4782 my $from_path = $diff->{'from_file'}[$i];
4783 my $status = $diff->{'status'}[$i];
Jakub Narebskied224de2007-05-07 01:10:04 +02004784
4785 $has_history ||= ($status ne 'A');
4786 $not_deleted ||= ($status ne 'D');
4787
Jakub Narebskied224de2007-05-07 01:10:04 +02004788 if ($status eq 'A') {
4789 print "<td class=\"link\" align=\"right\"> | </td>\n";
4790 } elsif ($status eq 'D') {
4791 print "<td class=\"link\">" .
4792 $cgi->a({-href => href(action=>"blob",
4793 hash_base=>$hash,
4794 hash=>$from_hash,
4795 file_name=>$from_path)},
4796 "blob" . ($i+1)) .
4797 " | </td>\n";
4798 } else {
Jakub Narebski493e01d2007-05-07 01:10:06 +02004799 if ($diff->{'to_id'} eq $from_hash) {
Jakub Narebskied224de2007-05-07 01:10:04 +02004800 print "<td class=\"link nochange\">";
4801 } else {
4802 print "<td class=\"link\">";
4803 }
4804 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02004805 hash=>$diff->{'to_id'},
Jakub Narebskied224de2007-05-07 01:10:04 +02004806 hash_parent=>$from_hash,
4807 hash_base=>$hash,
4808 hash_parent_base=>$hash_parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004809 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02004810 file_parent=>$from_path)},
4811 "diff" . ($i+1)) .
4812 " | </td>\n";
4813 }
4814 }
4815
4816 print "<td class=\"link\">";
4817 if ($not_deleted) {
4818 print $cgi->a({-href => href(action=>"blob",
Jakub Narebski493e01d2007-05-07 01:10:06 +02004819 hash=>$diff->{'to_id'},
4820 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02004821 hash_base=>$hash)},
4822 "blob");
4823 print " | " if ($has_history);
4824 }
4825 if ($has_history) {
4826 print $cgi->a({-href => href(action=>"history",
Jakub Narebski493e01d2007-05-07 01:10:06 +02004827 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02004828 hash_base=>$hash)},
4829 "history");
4830 }
4831 print "</td>\n";
4832
4833 print "</tr>\n";
4834 next; # instead of 'else' clause, to avoid extra indent
4835 }
4836 # else ordinary diff
4837
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004838 my ($to_mode_oct, $to_mode_str, $to_file_type);
4839 my ($from_mode_oct, $from_mode_str, $from_file_type);
Jakub Narebski493e01d2007-05-07 01:10:06 +02004840 if ($diff->{'to_mode'} ne ('0' x 6)) {
4841 $to_mode_oct = oct $diff->{'to_mode'};
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004842 if (S_ISREG($to_mode_oct)) { # only for regular file
4843 $to_mode_str = sprintf("%04o", $to_mode_oct & 0777); # permission bits
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004844 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004845 $to_file_type = file_type($diff->{'to_mode'});
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004846 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004847 if ($diff->{'from_mode'} ne ('0' x 6)) {
4848 $from_mode_oct = oct $diff->{'from_mode'};
Ævar Arnfjörð Bjarmason98885c22011-02-19 15:27:42 +00004849 if (S_ISREG($from_mode_oct)) { # only for regular file
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004850 $from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits
4851 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004852 $from_file_type = file_type($diff->{'from_mode'});
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004853 }
4854
Jakub Narebski493e01d2007-05-07 01:10:06 +02004855 if ($diff->{'status'} eq "A") { # created
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004856 my $mode_chng = "<span class=\"file_status new\">[new $to_file_type";
4857 $mode_chng .= " with mode: $to_mode_str" if $to_mode_str;
4858 $mode_chng .= "]</span>";
Luben Tuikov499faed2006-09-27 17:23:25 -07004859 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004860 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4861 hash_base=>$hash, file_name=>$diff->{'file'}),
4862 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07004863 print "</td>\n";
4864 print "<td>$mode_chng</td>\n";
4865 print "<td class=\"link\">";
Jakub Narebski72dbafa2006-09-04 18:19:58 +02004866 if ($action eq 'commitdiff') {
Jakub Narebskib4657e72006-08-28 14:48:14 +02004867 # link to patch
4868 $patchno++;
Kevin Cernekee5e96a842011-03-18 17:00:16 +01004869 print $cgi->a({-href => href(-anchor=>"patch$patchno")},
4870 "patch") .
4871 " | ";
Jakub Narebskib4657e72006-08-28 14:48:14 +02004872 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004873 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4874 hash_base=>$hash, file_name=>$diff->{'file'})},
Jakub Narebski3faa5412007-01-08 02:10:42 +01004875 "blob");
Jakub Narebskib4657e72006-08-28 14:48:14 +02004876 print "</td>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004877
Jakub Narebski493e01d2007-05-07 01:10:06 +02004878 } elsif ($diff->{'status'} eq "D") { # deleted
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004879 my $mode_chng = "<span class=\"file_status deleted\">[deleted $from_file_type]</span>";
Luben Tuikov499faed2006-09-27 17:23:25 -07004880 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004881 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
4882 hash_base=>$parent, file_name=>$diff->{'file'}),
4883 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07004884 print "</td>\n";
4885 print "<td>$mode_chng</td>\n";
4886 print "<td class=\"link\">";
Jakub Narebski72dbafa2006-09-04 18:19:58 +02004887 if ($action eq 'commitdiff') {
Jakub Narebskib4657e72006-08-28 14:48:14 +02004888 # link to patch
4889 $patchno++;
Kevin Cernekee5e96a842011-03-18 17:00:16 +01004890 print $cgi->a({-href => href(-anchor=>"patch$patchno")},
4891 "patch") .
4892 " | ";
Jakub Narebskib4657e72006-08-28 14:48:14 +02004893 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004894 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
4895 hash_base=>$parent, file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004896 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004897 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01004898 print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004899 file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004900 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004901 }
Jakub Narebskib4657e72006-08-28 14:48:14 +02004902 print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004903 file_name=>$diff->{'file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02004904 "history");
Luben Tuikov499faed2006-09-27 17:23:25 -07004905 print "</td>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004906
Jakub Narebski493e01d2007-05-07 01:10:06 +02004907 } elsif ($diff->{'status'} eq "M" || $diff->{'status'} eq "T") { # modified, or type changed
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004908 my $mode_chnge = "";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004909 if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004910 $mode_chnge = "<span class=\"file_status mode_chnge\">[changed";
Jakub Narebski6e72cf42007-01-03 20:47:24 +01004911 if ($from_file_type ne $to_file_type) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004912 $mode_chnge .= " from $from_file_type to $to_file_type";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004913 }
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004914 if (($from_mode_oct & 0777) != ($to_mode_oct & 0777)) {
4915 if ($from_mode_str && $to_mode_str) {
4916 $mode_chnge .= " mode: $from_mode_str->$to_mode_str";
4917 } elsif ($to_mode_str) {
4918 $mode_chnge .= " mode: $to_mode_str";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004919 }
4920 }
4921 $mode_chnge .= "]</span>\n";
4922 }
4923 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004924 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4925 hash_base=>$hash, file_name=>$diff->{'file'}),
4926 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07004927 print "</td>\n";
4928 print "<td>$mode_chnge</td>\n";
4929 print "<td class=\"link\">";
Jakub Narebski241cc592006-10-31 17:36:27 +01004930 if ($action eq 'commitdiff') {
4931 # link to patch
4932 $patchno++;
Kevin Cernekee5e96a842011-03-18 17:00:16 +01004933 print $cgi->a({-href => href(-anchor=>"patch$patchno")},
4934 "patch") .
Jakub Narebski241cc592006-10-31 17:36:27 +01004935 " | ";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004936 } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
Jakub Narebski241cc592006-10-31 17:36:27 +01004937 # "commit" view and modified file (not onlu mode changed)
4938 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02004939 hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
Jakub Narebski241cc592006-10-31 17:36:27 +01004940 hash_base=>$hash, hash_parent_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004941 file_name=>$diff->{'file'})},
Jakub Narebski241cc592006-10-31 17:36:27 +01004942 "diff") .
4943 " | ";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004944 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004945 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4946 hash_base=>$hash, file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004947 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004948 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01004949 print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004950 file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004951 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004952 }
Luben Tuikoveb51ec92006-09-27 17:24:49 -07004953 print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004954 file_name=>$diff->{'file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02004955 "history");
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004956 print "</td>\n";
4957
Jakub Narebski493e01d2007-05-07 01:10:06 +02004958 } elsif ($diff->{'status'} eq "R" || $diff->{'status'} eq "C") { # renamed or copied
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004959 my %status_name = ('R' => 'moved', 'C' => 'copied');
Jakub Narebski493e01d2007-05-07 01:10:06 +02004960 my $nstatus = $status_name{$diff->{'status'}};
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004961 my $mode_chng = "";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004962 if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004963 # mode also for directories, so we cannot use $to_mode_str
4964 $mode_chng = sprintf(", mode: %04o", $to_mode_oct & 0777);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004965 }
4966 print "<td>" .
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004967 $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004968 hash=>$diff->{'to_id'}, file_name=>$diff->{'to_file'}),
4969 -class => "list"}, esc_path($diff->{'to_file'})) . "</td>\n" .
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004970 "<td><span class=\"file_status $nstatus\">[$nstatus from " .
4971 $cgi->a({-href => href(action=>"blob", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004972 hash=>$diff->{'from_id'}, file_name=>$diff->{'from_file'}),
4973 -class => "list"}, esc_path($diff->{'from_file'})) .
4974 " with " . (int $diff->{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
Luben Tuikov499faed2006-09-27 17:23:25 -07004975 "<td class=\"link\">";
Jakub Narebski241cc592006-10-31 17:36:27 +01004976 if ($action eq 'commitdiff') {
4977 # link to patch
4978 $patchno++;
Kevin Cernekee5e96a842011-03-18 17:00:16 +01004979 print $cgi->a({-href => href(-anchor=>"patch$patchno")},
4980 "patch") .
Jakub Narebski241cc592006-10-31 17:36:27 +01004981 " | ";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004982 } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
Jakub Narebski241cc592006-10-31 17:36:27 +01004983 # "commit" view and modified file (not only pure rename or copy)
4984 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02004985 hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
Jakub Narebski241cc592006-10-31 17:36:27 +01004986 hash_base=>$hash, hash_parent_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004987 file_name=>$diff->{'to_file'}, file_parent=>$diff->{'from_file'})},
Jakub Narebski241cc592006-10-31 17:36:27 +01004988 "diff") .
4989 " | ";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004990 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004991 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4992 hash_base=>$parent, file_name=>$diff->{'to_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=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004996 file_name=>$diff->{'to_file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004997 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004998 }
Jakub Narebski897d1d22006-11-19 22:51:39 +01004999 print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02005000 file_name=>$diff->{'to_file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02005001 "history");
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005002 print "</td>\n";
5003
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005004 } # we should not encounter Unmerged (U) or Unknown (X) status
5005 print "</tr>\n";
5006 }
Jakub Narebski47598d72007-06-08 13:24:56 +02005007 print "</tbody>" if $has_header;
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005008 print "</table>\n";
5009}
5010
Michał Kiedrowiczd21102c2012-04-11 23:18:40 +02005011# Print context lines and then rem/add lines in a side-by-side manner.
5012sub print_sidebyside_diff_lines {
5013 my ($ctx, $rem, $add) = @_;
5014
5015 # print context block before add/rem block
5016 if (@$ctx) {
5017 print join '',
5018 '<div class="chunk_block ctx">',
5019 '<div class="old">',
5020 @$ctx,
5021 '</div>',
5022 '<div class="new">',
5023 @$ctx,
5024 '</div>',
5025 '</div>';
5026 }
5027
5028 if (!@$add) {
5029 # pure removal
5030 print join '',
5031 '<div class="chunk_block rem">',
5032 '<div class="old">',
5033 @$rem,
5034 '</div>',
5035 '</div>';
5036 } elsif (!@$rem) {
5037 # pure addition
5038 print join '',
5039 '<div class="chunk_block add">',
5040 '<div class="new">',
5041 @$add,
5042 '</div>',
5043 '</div>';
5044 } else {
5045 print join '',
5046 '<div class="chunk_block chg">',
5047 '<div class="old">',
5048 @$rem,
5049 '</div>',
5050 '<div class="new">',
5051 @$add,
5052 '</div>',
5053 '</div>';
5054 }
5055}
5056
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005057# Print context lines and then rem/add lines in inline manner.
5058sub print_inline_diff_lines {
5059 my ($ctx, $rem, $add) = @_;
5060
5061 print @$ctx, @$rem, @$add;
5062}
5063
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02005064# Format removed and added line, mark changed part and HTML-format them.
5065# Implementation is based on contrib/diff-highlight
5066sub format_rem_add_lines_pair {
5067 my ($rem, $add) = @_;
5068
5069 # We need to untabify lines before split()'ing them;
5070 # otherwise offsets would be invalid.
5071 chomp $rem;
5072 chomp $add;
5073 $rem = untabify($rem);
5074 $add = untabify($add);
5075
5076 my @rem = split(//, $rem);
5077 my @add = split(//, $add);
5078 my ($esc_rem, $esc_add);
5079 # Ignore +/- character, thus $prefix_len is set to 1.
5080 my ($prefix_len, $suffix_len) = (1, 0);
5081 my ($prefix_has_nonspace, $suffix_has_nonspace);
5082
5083 my $shorter = (@rem < @add) ? @rem : @add;
5084 while ($prefix_len < $shorter) {
5085 last if ($rem[$prefix_len] ne $add[$prefix_len]);
5086
5087 $prefix_has_nonspace = 1 if ($rem[$prefix_len] !~ /\s/);
5088 $prefix_len++;
5089 }
5090
5091 while ($prefix_len + $suffix_len < $shorter) {
5092 last if ($rem[-1 - $suffix_len] ne $add[-1 - $suffix_len]);
5093
5094 $suffix_has_nonspace = 1 if ($rem[-1 - $suffix_len] !~ /\s/);
5095 $suffix_len++;
5096 }
5097
5098 # Mark lines that are different from each other, but have some common
5099 # part that isn't whitespace. If lines are completely different, don't
5100 # mark them because that would make output unreadable, especially if
5101 # diff consists of multiple lines.
5102 if ($prefix_has_nonspace || $suffix_has_nonspace) {
5103 $esc_rem = esc_html_hl_regions($rem, 'marked',
5104 [$prefix_len, @rem - $suffix_len], -nbsp=>1);
5105 $esc_add = esc_html_hl_regions($add, 'marked',
5106 [$prefix_len, @add - $suffix_len], -nbsp=>1);
5107 } else {
5108 $esc_rem = esc_html($rem, -nbsp=>1);
5109 $esc_add = esc_html($add, -nbsp=>1);
5110 }
5111
5112 return format_diff_line(\$esc_rem, 'rem'),
5113 format_diff_line(\$esc_add, 'add');
5114}
5115
5116# HTML-format diff context, removed and added lines.
5117sub format_ctx_rem_add_lines {
5118 my ($ctx, $rem, $add, $is_combined) = @_;
5119 my (@new_ctx, @new_rem, @new_add);
5120
5121 # Highlight if every removed line has a corresponding added line.
5122 # Combined diffs are not supported at this moment.
5123 if (!$is_combined && @$add > 0 && @$add == @$rem) {
5124 for (my $i = 0; $i < @$add; $i++) {
5125 my ($line_rem, $line_add) = format_rem_add_lines_pair(
5126 $rem->[$i], $add->[$i]);
5127 push @new_rem, $line_rem;
5128 push @new_add, $line_add;
5129 }
5130 } else {
5131 @new_rem = map { format_diff_line($_, 'rem') } @$rem;
5132 @new_add = map { format_diff_line($_, 'add') } @$add;
5133 }
5134
5135 @new_ctx = map { format_diff_line($_, 'ctx') } @$ctx;
5136
5137 return (\@new_ctx, \@new_rem, \@new_add);
5138}
5139
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005140# Print context lines and then rem/add lines.
5141sub print_diff_lines {
5142 my ($ctx, $rem, $add, $diff_style, $is_combined) = @_;
5143
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02005144 ($ctx, $rem, $add) = format_ctx_rem_add_lines($ctx, $rem, $add,
5145 $is_combined);
5146
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005147 if ($diff_style eq 'sidebyside' && !$is_combined) {
5148 print_sidebyside_diff_lines($ctx, $rem, $add);
5149 } else {
5150 # default 'inline' style and unknown styles
5151 print_inline_diff_lines($ctx, $rem, $add);
5152 }
5153}
5154
5155sub print_diff_chunk {
Michał Kiedrowiczf4a81022012-04-11 23:18:42 +02005156 my ($diff_style, $is_combined, $from, $to, @chunk) = @_;
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005157 my (@ctx, @rem, @add);
5158
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005159 # The class of the previous line.
5160 my $prev_class = '';
5161
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005162 return unless @chunk;
5163
5164 # incomplete last line might be among removed or added lines,
5165 # or both, or among context lines: find which
5166 for (my $i = 1; $i < @chunk; $i++) {
5167 if ($chunk[$i][0] eq 'incomplete') {
5168 $chunk[$i][0] = $chunk[$i-1][0];
5169 }
5170 }
5171
5172 # guardian
5173 push @chunk, ["", ""];
5174
5175 foreach my $line_info (@chunk) {
5176 my ($class, $line) = @$line_info;
5177
5178 # print chunk headers
5179 if ($class && $class eq 'chunk_header') {
Michał Kiedrowicz5fb6ddf2012-04-11 23:18:43 +02005180 print format_diff_line($line, $class, $from, $to);
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005181 next;
5182 }
5183
Michał Kiedrowiczd21102c2012-04-11 23:18:40 +02005184 ## print from accumulator when have some add/rem lines or end
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005185 # of chunk (flush context lines), or when have add and rem
5186 # lines and new block is reached (otherwise add/rem lines could
5187 # be reordered)
5188 if (!$class || ((@rem || @add) && $class eq 'ctx') ||
5189 (@rem && @add && $class ne $prev_class)) {
5190 print_diff_lines(\@ctx, \@rem, \@add,
5191 $diff_style, $is_combined);
Michał Kiedrowiczd21102c2012-04-11 23:18:40 +02005192 @ctx = @rem = @add = ();
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005193 }
5194
5195 ## adding lines to accumulator
5196 # guardian value
5197 last unless $line;
5198 # rem, add or change
5199 if ($class eq 'rem') {
5200 push @rem, $line;
5201 } elsif ($class eq 'add') {
5202 push @add, $line;
5203 }
5204 # context line
5205 if ($class eq 'ctx') {
5206 push @ctx, $line;
5207 }
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005208
5209 $prev_class = $class;
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005210 }
5211}
5212
Jakub Narebskieee08902006-08-24 00:15:14 +02005213sub git_patchset_body {
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005214 my ($fd, $diff_style, $difftree, $hash, @hash_parents) = @_;
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02005215 my ($hash_parent) = $hash_parents[0];
Jakub Narebskieee08902006-08-24 00:15:14 +02005216
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005217 my $is_combined = (@hash_parents > 1);
Jakub Narebskieee08902006-08-24 00:15:14 +02005218 my $patch_idx = 0;
Martin Koegler4280cde2007-04-22 22:49:25 -07005219 my $patch_number = 0;
Jakub Narebski6d55f052006-11-18 23:35:39 +01005220 my $patch_line;
Jakub Narebskife875852006-08-25 20:59:39 +02005221 my $diffinfo;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005222 my $to_name;
Jakub Narebski744d0ac2006-11-08 17:59:41 +01005223 my (%from, %to);
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005224 my @chunk; # for side-by-side diff
Jakub Narebskieee08902006-08-24 00:15:14 +02005225
5226 print "<div class=\"patchset\">\n";
5227
Jakub Narebski6d55f052006-11-18 23:35:39 +01005228 # skip to first patch
5229 while ($patch_line = <$fd>) {
Jakub Narebski157e43b2006-08-24 19:34:36 +02005230 chomp $patch_line;
Jakub Narebskieee08902006-08-24 00:15:14 +02005231
Jakub Narebski6d55f052006-11-18 23:35:39 +01005232 last if ($patch_line =~ m/^diff /);
5233 }
5234
5235 PATCH:
5236 while ($patch_line) {
Jakub Narebski6d55f052006-11-18 23:35:39 +01005237
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005238 # parse "git diff" header line
5239 if ($patch_line =~ m/^diff --git (\"(?:[^\\\"]*(?:\\.[^\\\"]*)*)\"|[^ "]*) (.*)$/) {
5240 # $1 is from_name, which we do not use
5241 $to_name = unquote($2);
5242 $to_name =~ s!^b/!!;
5243 } elsif ($patch_line =~ m/^diff --(cc|combined) ("?.*"?)$/) {
5244 # $1 is 'cc' or 'combined', which we do not use
5245 $to_name = unquote($2);
5246 } else {
5247 $to_name = undef;
Jakub Narebski6d55f052006-11-18 23:35:39 +01005248 }
Jakub Narebski6d55f052006-11-18 23:35:39 +01005249
5250 # check if current patch belong to current raw line
5251 # and parse raw git-diff line if needed
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005252 if (is_patch_split($diffinfo, { 'to_file' => $to_name })) {
Jakub Narebski22065372007-05-12 12:42:32 +02005253 # this is continuation of a split patch
Jakub Narebski6d55f052006-11-18 23:35:39 +01005254 print "<div class=\"patch cont\">\n";
5255 } else {
5256 # advance raw git-diff output if needed
5257 $patch_idx++ if defined $diffinfo;
Jakub Narebskieee08902006-08-24 00:15:14 +02005258
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005259 # read and prepare patch information
5260 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
5261
Jakub Narebskicd030c32007-06-08 13:33:28 +02005262 # compact combined diff output can have some patches skipped
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005263 # find which patch (using pathname of result) we are at now;
5264 if ($is_combined) {
5265 while ($to_name ne $diffinfo->{'to_file'}) {
Jakub Narebskicd030c32007-06-08 13:33:28 +02005266 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
5267 format_diff_cc_simplified($diffinfo, @hash_parents) .
5268 "</div>\n"; # class="patch"
5269
5270 $patch_idx++;
5271 $patch_number++;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005272
5273 last if $patch_idx > $#$difftree;
5274 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
Jakub Narebskicd030c32007-06-08 13:33:28 +02005275 }
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005276 }
Jakub Narebski711fa742007-09-08 21:49:11 +02005277
Jakub Narebski90921742007-06-08 13:27:42 +02005278 # modifies %from, %to hashes
5279 parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents);
Jakub Narebski5f855052007-05-17 22:54:28 +02005280
Jakub Narebski6d55f052006-11-18 23:35:39 +01005281 # this is first patch for raw difftree line with $patch_idx index
5282 # we index @$difftree array from 0, but number patches from 1
5283 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
Jakub Narebski744d0ac2006-11-08 17:59:41 +01005284 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005285
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005286 # git diff header
5287 #assert($patch_line =~ m/^diff /) if DEBUG;
5288 #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
5289 $patch_number++;
Jakub Narebski6d55f052006-11-18 23:35:39 +01005290 # print "git diff" header
Jakub Narebski90921742007-06-08 13:27:42 +02005291 print format_git_diff_header_line($patch_line, $diffinfo,
5292 \%from, \%to);
Jakub Narebskieee08902006-08-24 00:15:14 +02005293
Jakub Narebski6d55f052006-11-18 23:35:39 +01005294 # print extended diff header
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005295 print "<div class=\"diff extended_header\">\n";
Jakub Narebski6d55f052006-11-18 23:35:39 +01005296 EXTENDED_HEADER:
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005297 while ($patch_line = <$fd>) {
5298 chomp $patch_line;
5299
5300 last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /);
5301
Jakub Narebski90921742007-06-08 13:27:42 +02005302 print format_extended_diff_header_line($patch_line, $diffinfo,
5303 \%from, \%to);
Jakub Narebski6d55f052006-11-18 23:35:39 +01005304 }
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005305 print "</div>\n"; # class="diff extended_header"
Jakub Narebskie4e4f822006-08-25 21:04:13 +02005306
Jakub Narebski6d55f052006-11-18 23:35:39 +01005307 # from-file/to-file diff header
Jakub Narebski0bdb28c2007-01-10 00:07:43 +01005308 if (! $patch_line) {
5309 print "</div>\n"; # class="patch"
5310 last PATCH;
5311 }
Jakub Narebski66399ef2007-01-07 02:52:25 +01005312 next PATCH if ($patch_line =~ m/^diff /);
Jakub Narebski6d55f052006-11-18 23:35:39 +01005313 #assert($patch_line =~ m/^---/) if DEBUG;
Jakub Narebski6d55f052006-11-18 23:35:39 +01005314
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005315 my $last_patch_line = $patch_line;
Jakub Narebski6d55f052006-11-18 23:35:39 +01005316 $patch_line = <$fd>;
Jakub Narebski6d55f052006-11-18 23:35:39 +01005317 chomp $patch_line;
Jakub Narebski90921742007-06-08 13:27:42 +02005318 #assert($patch_line =~ m/^\+\+\+/) if DEBUG;
Jakub Narebski6d55f052006-11-18 23:35:39 +01005319
Jakub Narebski90921742007-06-08 13:27:42 +02005320 print format_diff_from_to_header($last_patch_line, $patch_line,
Jakub Narebski91af4ce2007-06-08 13:32:44 +02005321 $diffinfo, \%from, \%to,
5322 @hash_parents);
Jakub Narebski6d55f052006-11-18 23:35:39 +01005323
5324 # the patch itself
5325 LINE:
5326 while ($patch_line = <$fd>) {
5327 chomp $patch_line;
5328
5329 next PATCH if ($patch_line =~ m/^diff /);
5330
Michał Kiedrowiczf4a81022012-04-11 23:18:42 +02005331 my $class = diff_line_class($patch_line, \%from, \%to);
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005332
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005333 if ($class eq 'chunk_header') {
Michał Kiedrowiczf4a81022012-04-11 23:18:42 +02005334 print_diff_chunk($diff_style, $is_combined, \%from, \%to, @chunk);
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005335 @chunk = ();
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005336 }
Michał Kiedrowicz44185f92012-04-11 23:18:41 +02005337
Michał Kiedrowiczf4a81022012-04-11 23:18:42 +02005338 push @chunk, [ $class, $patch_line ];
Jakub Narebskieee08902006-08-24 00:15:14 +02005339 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005340
Jakub Narebski6d55f052006-11-18 23:35:39 +01005341 } continue {
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005342 if (@chunk) {
Michał Kiedrowiczf4a81022012-04-11 23:18:42 +02005343 print_diff_chunk($diff_style, $is_combined, \%from, \%to, @chunk);
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01005344 @chunk = ();
5345 }
Jakub Narebski6d55f052006-11-18 23:35:39 +01005346 print "</div>\n"; # class="patch"
Jakub Narebskieee08902006-08-24 00:15:14 +02005347 }
Jakub Narebskid26c4262007-05-17 00:05:55 +02005348
Ralf Wildenhues22e5e582010-08-22 13:12:12 +02005349 # for compact combined (--cc) format, with chunk and patch simplification
5350 # the patchset might be empty, but there might be unprocessed raw lines
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005351 for (++$patch_idx if $patch_number > 0;
Jakub Narebskicd030c32007-06-08 13:33:28 +02005352 $patch_idx < @$difftree;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005353 ++$patch_idx) {
Jakub Narebskicd030c32007-06-08 13:33:28 +02005354 # read and prepare patch information
Jakub Narebski0cec6db2007-10-30 01:35:05 +01005355 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
Jakub Narebskicd030c32007-06-08 13:33:28 +02005356
5357 # generate anchor for "patch" links in difftree / whatchanged part
5358 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
5359 format_diff_cc_simplified($diffinfo, @hash_parents) .
5360 "</div>\n"; # class="patch"
5361
5362 $patch_number++;
5363 }
5364
Jakub Narebskid26c4262007-05-17 00:05:55 +02005365 if ($patch_number == 0) {
5366 if (@hash_parents > 1) {
5367 print "<div class=\"diff nodifferences\">Trivial merge</div>\n";
5368 } else {
5369 print "<div class=\"diff nodifferences\">No differences found</div>\n";
5370 }
5371 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005372
5373 print "</div>\n"; # class="patchset"
5374}
5375
5376# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5377
Jakub Narebskia1e1b2d2012-01-31 01:20:54 +01005378sub git_project_search_form {
Jakub Narebskib22939a2012-03-02 23:50:01 +01005379 my ($searchtext, $search_use_regexp) = @_;
Jakub Narebskia1e1b2d2012-01-31 01:20:54 +01005380
Jakub Narebskiabc0c9d2012-01-31 01:20:55 +01005381 my $limit = '';
5382 if ($project_filter) {
5383 $limit = " in '$project_filter/'";
5384 }
5385
Jakub Narebskia1e1b2d2012-01-31 01:20:54 +01005386 print "<div class=\"projsearch\">\n";
5387 print $cgi->startform(-method => 'get', -action => $my_uri) .
Jakub Narebskiabc0c9d2012-01-31 01:20:55 +01005388 $cgi->hidden(-name => 'a', -value => 'project_list') . "\n";
5389 print $cgi->hidden(-name => 'pf', -value => $project_filter). "\n"
5390 if (defined $project_filter);
5391 print $cgi->textfield(-name => 's', -value => $searchtext,
5392 -title => "Search project by name and description$limit",
Jakub Narebskia1e1b2d2012-01-31 01:20:54 +01005393 -size => 60) . "\n" .
5394 "<span title=\"Extended regular expression\">" .
5395 $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
5396 -checked => $search_use_regexp) .
5397 "</span>\n" .
5398 $cgi->submit(-name => 'btnS', -value => 'Search') .
5399 $cgi->end_form() . "\n" .
Jakub Narebskiabc0c9d2012-01-31 01:20:55 +01005400 $cgi->a({-href => href(project => undef, searchtext => undef,
5401 project_filter => $project_filter)},
5402 esc_html("List all projects$limit")) . "<br />\n";
Jakub Narebskia1e1b2d2012-01-31 01:20:54 +01005403 print "</div>\n";
5404}
5405
Jakub Narebski14b289b2012-02-23 16:54:46 +01005406# entry for given @keys needs filling if at least one of keys in list
5407# is not present in %$project_info
5408sub project_info_needs_filling {
5409 my ($project_info, @keys) = @_;
5410
5411 # return List::MoreUtils::any { !exists $project_info->{$_} } @keys;
5412 foreach my $key (@keys) {
5413 if (!exists $project_info->{$key}) {
5414 return 1;
5415 }
5416 }
5417 return;
5418}
5419
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005420# fills project list info (age, description, owner, category, forks, etc.)
Sebastien Ceveyd940c902011-04-29 19:52:01 +02005421# for each project in the list, removing invalid projects from
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005422# returned list, or fill only specified info.
5423#
5424# Invalid projects are removed from the returned list if and only if you
5425# ask 'age' or 'age_string' to be filled, because they are the only fields
5426# that run unconditionally git command that requires repository, and
5427# therefore do always check if project repository is invalid.
5428#
5429# USAGE:
5430# * fill_project_list_info(\@project_list, 'descr_long', 'ctags')
5431# ensures that 'descr_long' and 'ctags' fields are filled
5432# * @project_list = fill_project_list_info(\@project_list)
5433# ensures that all fields are filled (and invalid projects removed)
5434#
Jakub Narebski69913412008-06-10 19:21:01 +02005435# NOTE: modifies $projlist, but does not remove entries from it
5436sub fill_project_list_info {
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005437 my ($projlist, @wanted_keys) = @_;
Petr Baudise30496d2006-10-24 05:33:17 +02005438 my @projects;
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005439 my $filter_set = sub { return @_; };
5440 if (@wanted_keys) {
5441 my %wanted_keys = map { $_ => 1 } @wanted_keys;
5442 $filter_set = sub { return grep { $wanted_keys{$_} } @_; };
5443 }
Jakub Narebski69913412008-06-10 19:21:01 +02005444
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08005445 my $show_ctags = gitweb_check_feature('ctags');
Jakub Narebski69913412008-06-10 19:21:01 +02005446 PROJECT:
Petr Baudise30496d2006-10-24 05:33:17 +02005447 foreach my $pr (@$projlist) {
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005448 if (project_info_needs_filling($pr, $filter_set->('age', 'age_string'))) {
Jakub Narebski14b289b2012-02-23 16:54:46 +01005449 my (@activity) = git_get_last_activity($pr->{'path'});
5450 unless (@activity) {
5451 next PROJECT;
5452 }
5453 ($pr->{'age'}, $pr->{'age_string'}) = @activity;
Petr Baudise30496d2006-10-24 05:33:17 +02005454 }
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005455 if (project_info_needs_filling($pr, $filter_set->('descr', 'descr_long'))) {
Petr Baudise30496d2006-10-24 05:33:17 +02005456 my $descr = git_get_project_description($pr->{'path'}) || "";
Jakub Narebski69913412008-06-10 19:21:01 +02005457 $descr = to_utf8($descr);
5458 $pr->{'descr_long'} = $descr;
Michael Hendricks55feb122007-07-04 18:36:48 -06005459 $pr->{'descr'} = chop_str($descr, $projects_list_description_width, 5);
Petr Baudise30496d2006-10-24 05:33:17 +02005460 }
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005461 if (project_info_needs_filling($pr, $filter_set->('owner'))) {
Miklos Vajna76e4f5d2007-07-04 00:11:23 +02005462 $pr->{'owner'} = git_get_project_owner("$pr->{'path'}") || "";
Petr Baudise30496d2006-10-24 05:33:17 +02005463 }
Jakub Narebski14b289b2012-02-23 16:54:46 +01005464 if ($show_ctags &&
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005465 project_info_needs_filling($pr, $filter_set->('ctags'))) {
Jakub Narebski12b14432011-04-29 19:51:56 +02005466 $pr->{'ctags'} = git_get_project_ctags($pr->{'path'});
Petr Baudise30496d2006-10-24 05:33:17 +02005467 }
Jakub Narebski14b289b2012-02-23 16:54:46 +01005468 if ($projects_list_group_categories &&
Jakub Narebski2e3291a2012-02-23 16:54:47 +01005469 project_info_needs_filling($pr, $filter_set->('category'))) {
Sebastien Ceveyd940c902011-04-29 19:52:01 +02005470 my $cat = git_get_project_category($pr->{'path'}) ||
5471 $project_list_default_category;
5472 $pr->{'category'} = to_utf8($cat);
5473 }
5474
Petr Baudise30496d2006-10-24 05:33:17 +02005475 push @projects, $pr;
5476 }
5477
Jakub Narebski69913412008-06-10 19:21:01 +02005478 return @projects;
5479}
5480
Jakub Narebski12b14432011-04-29 19:51:56 +02005481sub sort_projects_list {
5482 my ($projlist, $order) = @_;
5483 my @projects;
5484
5485 my %order_info = (
5486 project => { key => 'path', type => 'str' },
5487 descr => { key => 'descr_long', type => 'str' },
5488 owner => { key => 'owner', type => 'str' },
5489 age => { key => 'age', type => 'num' }
5490 );
5491 my $oi = $order_info{$order};
5492 return @$projlist unless defined $oi;
5493 if ($oi->{'type'} eq 'str') {
5494 @projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @$projlist;
5495 } else {
5496 @projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @$projlist;
5497 }
5498
5499 return @projects;
5500}
5501
Sebastien Ceveyd940c902011-04-29 19:52:01 +02005502# returns a hash of categories, containing the list of project
5503# belonging to each category
5504sub build_projlist_by_category {
5505 my ($projlist, $from, $to) = @_;
5506 my %categories;
5507
5508 $from = 0 unless defined $from;
5509 $to = $#$projlist if (!defined $to || $#$projlist < $to);
5510
5511 for (my $i = $from; $i <= $to; $i++) {
5512 my $pr = $projlist->[$i];
5513 push @{$categories{ $pr->{'category'} }}, $pr;
5514 }
5515
5516 return wantarray ? %categories : \%categories;
5517}
5518
Petr Baudis6b28da62008-09-25 18:48:37 +02005519# print 'sort by' <th> element, generating 'sort by $name' replay link
5520# if that order is not selected
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02005521sub print_sort_th {
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01005522 print format_sort_th(@_);
5523}
5524
5525sub format_sort_th {
Petr Baudis6b28da62008-09-25 18:48:37 +02005526 my ($name, $order, $header) = @_;
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01005527 my $sort_th = "";
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02005528 $header ||= ucfirst($name);
5529
5530 if ($order eq $name) {
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01005531 $sort_th .= "<th>$header</th>\n";
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02005532 } else {
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01005533 $sort_th .= "<th>" .
5534 $cgi->a({-href => href(-replay=>1, order=>$name),
5535 -class => "header"}, $header) .
5536 "</th>\n";
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02005537 }
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01005538
5539 return $sort_th;
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02005540}
5541
Sebastien Cevey0fa920c2011-04-29 19:51:59 +02005542sub git_project_list_rows {
5543 my ($projlist, $from, $to, $check_forks) = @_;
5544
5545 $from = 0 unless defined $from;
5546 $to = $#$projlist if (!defined $to || $#$projlist < $to);
5547
5548 my $alternate = 1;
5549 for (my $i = $from; $i <= $to; $i++) {
5550 my $pr = $projlist->[$i];
5551
5552 if ($alternate) {
5553 print "<tr class=\"dark\">\n";
5554 } else {
5555 print "<tr class=\"light\">\n";
5556 }
5557 $alternate ^= 1;
5558
5559 if ($check_forks) {
5560 print "<td>";
5561 if ($pr->{'forks'}) {
5562 my $nforks = scalar @{$pr->{'forks'}};
5563 if ($nforks > 0) {
5564 print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks"),
5565 -title => "$nforks forks"}, "+");
5566 } else {
5567 print $cgi->span({-title => "$nforks forks"}, "+");
5568 }
5569 }
5570 print "</td>\n";
5571 }
5572 print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
Jakub Narebski07a40062012-02-27 02:55:20 +01005573 -class => "list"},
5574 esc_html_match_hl($pr->{'path'}, $search_regexp)) .
5575 "</td>\n" .
Sebastien Cevey0fa920c2011-04-29 19:51:59 +02005576 "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
Jakub Narebski5fb3cf22012-02-27 02:55:21 +01005577 -class => "list",
Jakub Narebskie607b792012-02-27 02:55:22 +01005578 -title => $pr->{'descr_long'}},
Jakub Narebski5fb3cf22012-02-27 02:55:21 +01005579 $search_regexp
Jakub Narebskie607b792012-02-27 02:55:22 +01005580 ? esc_html_match_hl_chopped($pr->{'descr_long'},
5581 $pr->{'descr'}, $search_regexp)
Jakub Narebski5fb3cf22012-02-27 02:55:21 +01005582 : esc_html($pr->{'descr'})) .
5583 "</td>\n" .
Sebastien Cevey0fa920c2011-04-29 19:51:59 +02005584 "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
5585 print "<td class=\"". age_class($pr->{'age'}) . "\">" .
5586 (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" .
5587 "<td class=\"link\">" .
5588 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " .
5589 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
5590 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
5591 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
5592 ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') .
5593 "</td>\n" .
5594 "</tr>\n";
5595 }
5596}
5597
Jakub Narebski69913412008-06-10 19:21:01 +02005598sub git_project_list_body {
Petr Baudis42326112008-10-02 17:17:01 +02005599 # actually uses global variable $project
Jakub Narebski69913412008-06-10 19:21:01 +02005600 my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
Jakub Narebski12b14432011-04-29 19:51:56 +02005601 my @projects = @$projlist;
Jakub Narebski69913412008-06-10 19:21:01 +02005602
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08005603 my $check_forks = gitweb_check_feature('forks');
Jakub Narebski12b14432011-04-29 19:51:56 +02005604 my $show_ctags = gitweb_check_feature('ctags');
Jakub Narebski84d9e2d2012-02-03 13:44:54 +01005605 my $tagfilter = $show_ctags ? $input_params{'ctag'} : undef;
Jakub Narebski12b14432011-04-29 19:51:56 +02005606 $check_forks = undef
Jakub Narebskie65ceb62012-03-02 23:34:24 +01005607 if ($tagfilter || $search_regexp);
Jakub Narebski12b14432011-04-29 19:51:56 +02005608
5609 # filtering out forks before filling info allows to do less work
5610 @projects = filter_forks_from_projects_list(\@projects)
5611 if ($check_forks);
Jakub Narebski07b257f2012-02-23 16:54:48 +01005612 # search_projects_list pre-fills required info
Jakub Narebski12b14432011-04-29 19:51:56 +02005613 @projects = search_projects_list(\@projects,
Jakub Narebskie65ceb62012-03-02 23:34:24 +01005614 'search_regexp' => $search_regexp,
Jakub Narebski12b14432011-04-29 19:51:56 +02005615 'tagfilter' => $tagfilter)
Jakub Narebskie65ceb62012-03-02 23:34:24 +01005616 if ($tagfilter || $search_regexp);
Jakub Narebski07b257f2012-02-23 16:54:48 +01005617 # fill the rest
5618 @projects = fill_project_list_info(\@projects);
Jakub Narebski69913412008-06-10 19:21:01 +02005619
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02005620 $order ||= $default_projects_order;
Petr Baudise30496d2006-10-24 05:33:17 +02005621 $from = 0 unless defined $from;
5622 $to = $#projects if (!defined $to || $#projects < $to);
5623
Jakub Narebski12b14432011-04-29 19:51:56 +02005624 # short circuit
5625 if ($from > $to) {
5626 print "<center>\n".
5627 "<b>No such projects found</b><br />\n".
5628 "Click ".$cgi->a({-href=>href(project=>undef)},"here")." to view all projects<br />\n".
5629 "</center>\n<br />\n";
5630 return;
Petr Baudis6b28da62008-09-25 18:48:37 +02005631 }
5632
Jakub Narebski12b14432011-04-29 19:51:56 +02005633 @projects = sort_projects_list(\@projects, $order);
5634
Petr Baudisaed93de2008-10-02 17:13:02 +02005635 if ($show_ctags) {
Jakub Narebski0368c492011-04-29 19:51:57 +02005636 my $ctags = git_gather_all_ctags(\@projects);
5637 my $cloud = git_populate_project_tagcloud($ctags);
Petr Baudisaed93de2008-10-02 17:13:02 +02005638 print git_show_project_tagcloud($cloud, 64);
5639 }
5640
Petr Baudise30496d2006-10-24 05:33:17 +02005641 print "<table class=\"project_list\">\n";
5642 unless ($no_header) {
5643 print "<tr>\n";
5644 if ($check_forks) {
5645 print "<th></th>\n";
5646 }
Petr Baudis6b28da62008-09-25 18:48:37 +02005647 print_sort_th('project', $order, 'Project');
5648 print_sort_th('descr', $order, 'Description');
5649 print_sort_th('owner', $order, 'Owner');
5650 print_sort_th('age', $order, 'Last Change');
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02005651 print "<th></th>\n" . # for links
Petr Baudise30496d2006-10-24 05:33:17 +02005652 "</tr>\n";
5653 }
Petr Baudis42326112008-10-02 17:17:01 +02005654
Sebastien Ceveyd940c902011-04-29 19:52:01 +02005655 if ($projects_list_group_categories) {
5656 # only display categories with projects in the $from-$to window
5657 @projects = sort {$a->{'category'} cmp $b->{'category'}} @projects[$from..$to];
5658 my %categories = build_projlist_by_category(\@projects, $from, $to);
5659 foreach my $cat (sort keys %categories) {
5660 unless ($cat eq "") {
5661 print "<tr>\n";
5662 if ($check_forks) {
5663 print "<td></td>\n";
Jakub Narebski12b14432011-04-29 19:51:56 +02005664 }
Sebastien Ceveyd940c902011-04-29 19:52:01 +02005665 print "<td class=\"category\" colspan=\"5\">".esc_html($cat)."</td>\n";
5666 print "</tr>\n";
Petr Baudise30496d2006-10-24 05:33:17 +02005667 }
Sebastien Ceveyd940c902011-04-29 19:52:01 +02005668
5669 git_project_list_rows($categories{$cat}, undef, undef, $check_forks);
Petr Baudise30496d2006-10-24 05:33:17 +02005670 }
Sebastien Ceveyd940c902011-04-29 19:52:01 +02005671 } else {
5672 git_project_list_rows(\@projects, $from, $to, $check_forks);
Petr Baudise30496d2006-10-24 05:33:17 +02005673 }
Jakub Narebski12b14432011-04-29 19:51:56 +02005674
Petr Baudise30496d2006-10-24 05:33:17 +02005675 if (defined $extra) {
5676 print "<tr>\n";
5677 if ($check_forks) {
5678 print "<td></td>\n";
5679 }
5680 print "<td colspan=\"5\">$extra</td>\n" .
5681 "</tr>\n";
5682 }
5683 print "</table>\n";
5684}
5685
Jakub Narebski42671ca2009-11-13 02:02:12 +01005686sub git_log_body {
5687 # uses global variable $project
5688 my ($commitlist, $from, $to, $refs, $extra) = @_;
5689
5690 $from = 0 unless defined $from;
5691 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
5692
5693 for (my $i = 0; $i <= $to; $i++) {
5694 my %co = %{$commitlist->[$i]};
5695 next if !%co;
5696 my $commit = $co{'id'};
5697 my $ref = format_ref_marker($refs, $commit);
Jakub Narebski42671ca2009-11-13 02:02:12 +01005698 git_print_header_div('commit',
5699 "<span class=\"age\">$co{'age_string'}</span>" .
5700 esc_html($co{'title'}) . $ref,
5701 $commit);
5702 print "<div class=\"title_text\">\n" .
5703 "<div class=\"log_link\">\n" .
5704 $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") .
5705 " | " .
5706 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") .
5707 " | " .
5708 $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
5709 "<br/>\n" .
5710 "</div>\n";
5711 git_print_authorship(\%co, -tag => 'span');
5712 print "<br/>\n</div>\n";
5713
5714 print "<div class=\"log_body\">\n";
5715 git_print_log($co{'comment'}, -final_empty_line=> 1);
5716 print "</div>\n";
5717 }
5718 if ($extra) {
5719 print "<div class=\"page_nav\">\n";
5720 print "$extra\n";
5721 print "</div>\n";
5722 }
5723}
5724
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005725sub git_shortlog_body {
5726 # uses global variable $project
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00005727 my ($commitlist, $from, $to, $refs, $extra) = @_;
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05305728
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005729 $from = 0 unless defined $from;
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00005730 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005731
Jakub Narebski591ebf62007-11-19 14:16:11 +01005732 print "<table class=\"shortlog\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07005733 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005734 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00005735 my %co = %{$commitlist->[$i]};
5736 my $commit = $co{'id'};
Jakub Narebski847e01f2006-08-14 02:05:47 +02005737 my $ref = format_ref_marker($refs, $commit);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005738 if ($alternate) {
5739 print "<tr class=\"dark\">\n";
5740 } else {
5741 print "<tr class=\"light\">\n";
5742 }
5743 $alternate ^= 1;
5744 # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
5745 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02005746 format_author_html('td', \%co, 10) . "<td>";
Jakub Narebski952c65f2006-08-22 16:52:50 +02005747 print format_subject_html($co{'title'}, $co{'title_short'},
5748 href(action=>"commit", hash=>$commit), $ref);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005749 print "</td>\n" .
5750 "<td class=\"link\">" .
Petr Baudis4777b012006-10-24 05:36:10 +02005751 $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
Petr Baudis35749ae2006-09-22 03:19:44 +02005752 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
Petr Baudis55ff35c2006-10-06 15:57:52 +02005753 $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
Matt McCutchena3c8ab32007-07-22 01:30:27 +02005754 my $snapshot_links = format_snapshot_links($commit);
5755 if (defined $snapshot_links) {
5756 print " | " . $snapshot_links;
Petr Baudis55ff35c2006-10-06 15:57:52 +02005757 }
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305758 print "</td>\n" .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005759 "</tr>\n";
5760 }
5761 if (defined $extra) {
5762 print "<tr>\n" .
5763 "<td colspan=\"4\">$extra</td>\n" .
5764 "</tr>\n";
5765 }
5766 print "</table>\n";
5767}
5768
Jakub Narebski581860e2006-08-14 02:09:08 +02005769sub git_history_body {
5770 # Warning: assumes constant type (blob or tree) during history
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005771 my ($commitlist, $from, $to, $refs, $extra,
5772 $file_name, $file_hash, $ftype) = @_;
Jakub Narebski8be68352006-09-11 00:36:04 +02005773
5774 $from = 0 unless defined $from;
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00005775 $to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist});
Jakub Narebski581860e2006-08-14 02:09:08 +02005776
Jakub Narebski591ebf62007-11-19 14:16:11 +01005777 print "<table class=\"history\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07005778 my $alternate = 1;
Jakub Narebski8be68352006-09-11 00:36:04 +02005779 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00005780 my %co = %{$commitlist->[$i]};
Jakub Narebski581860e2006-08-14 02:09:08 +02005781 if (!%co) {
5782 next;
5783 }
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00005784 my $commit = $co{'id'};
Jakub Narebski581860e2006-08-14 02:09:08 +02005785
5786 my $ref = format_ref_marker($refs, $commit);
5787
5788 if ($alternate) {
5789 print "<tr class=\"dark\">\n";
5790 } else {
5791 print "<tr class=\"light\">\n";
5792 }
5793 $alternate ^= 1;
5794 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02005795 # shortlog: format_author_html('td', \%co, 10)
5796 format_author_html('td', \%co, 15, 3) . "<td>";
Jakub Narebski581860e2006-08-14 02:09:08 +02005797 # originally git_history used chop_str($co{'title'}, 50)
Jakub Narebski952c65f2006-08-22 16:52:50 +02005798 print format_subject_html($co{'title'}, $co{'title_short'},
5799 href(action=>"commit", hash=>$commit), $ref);
Jakub Narebski581860e2006-08-14 02:09:08 +02005800 print "</td>\n" .
5801 "<td class=\"link\">" .
Luben Tuikov6d81c5a2006-09-28 17:21:07 -07005802 $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype) . " | " .
5803 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
Jakub Narebski581860e2006-08-14 02:09:08 +02005804
5805 if ($ftype eq 'blob') {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005806 my $blob_current = $file_hash;
Jakub Narebski581860e2006-08-14 02:09:08 +02005807 my $blob_parent = git_get_hash_by_path($commit, $file_name);
5808 if (defined $blob_current && defined $blob_parent &&
5809 $blob_current ne $blob_parent) {
5810 print " | " .
Jakub Narebski420e92f2006-08-24 23:53:54 +02005811 $cgi->a({-href => href(action=>"blobdiff",
5812 hash=>$blob_current, hash_parent=>$blob_parent,
5813 hash_base=>$hash_base, hash_parent_base=>$commit,
5814 file_name=>$file_name)},
Jakub Narebski581860e2006-08-14 02:09:08 +02005815 "diff to current");
5816 }
5817 }
5818 print "</td>\n" .
5819 "</tr>\n";
5820 }
5821 if (defined $extra) {
5822 print "<tr>\n" .
5823 "<td colspan=\"4\">$extra</td>\n" .
5824 "</tr>\n";
5825 }
5826 print "</table>\n";
5827}
5828
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005829sub git_tags_body {
5830 # uses global variable $project
5831 my ($taglist, $from, $to, $extra) = @_;
5832 $from = 0 unless defined $from;
5833 $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
5834
Jakub Narebski591ebf62007-11-19 14:16:11 +01005835 print "<table class=\"tags\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07005836 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005837 for (my $i = $from; $i <= $to; $i++) {
5838 my $entry = $taglist->[$i];
5839 my %tag = %$entry;
Jakub Narebskicd146402006-11-02 20:23:11 +01005840 my $comment = $tag{'subject'};
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005841 my $comment_short;
5842 if (defined $comment) {
5843 $comment_short = chop_str($comment, 30, 5);
5844 }
5845 if ($alternate) {
5846 print "<tr class=\"dark\">\n";
5847 } else {
5848 print "<tr class=\"light\">\n";
5849 }
5850 $alternate ^= 1;
Jakub Narebski27dd1a82007-01-03 22:54:29 +01005851 if (defined $tag{'age'}) {
5852 print "<td><i>$tag{'age'}</i></td>\n";
5853 } else {
5854 print "<td></td>\n";
5855 }
5856 print "<td>" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02005857 $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'}),
Jakub Narebski63e42202006-08-22 12:38:59 +02005858 -class => "list name"}, esc_html($tag{'name'})) .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005859 "</td>\n" .
5860 "<td>";
5861 if (defined $comment) {
Jakub Narebski952c65f2006-08-22 16:52:50 +02005862 print format_subject_html($comment, $comment_short,
5863 href(action=>"tag", hash=>$tag{'id'}));
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005864 }
5865 print "</td>\n" .
5866 "<td class=\"selflink\">";
5867 if ($tag{'type'} eq "tag") {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02005868 print $cgi->a({-href => href(action=>"tag", hash=>$tag{'id'})}, "tag");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005869 } else {
5870 print "&nbsp;";
5871 }
5872 print "</td>\n" .
5873 "<td class=\"link\">" . " | " .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02005874 $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'});
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005875 if ($tag{'reftype'} eq "commit") {
Jakub Narebskibf901f82007-12-15 15:40:28 +01005876 print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'fullname'})}, "shortlog") .
5877 " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'fullname'})}, "log");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005878 } elsif ($tag{'reftype'} eq "blob") {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02005879 print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005880 }
5881 print "</td>\n" .
5882 "</tr>";
5883 }
5884 if (defined $extra) {
5885 print "<tr>\n" .
5886 "<td colspan=\"5\">$extra</td>\n" .
5887 "</tr>\n";
5888 }
5889 print "</table>\n";
5890}
5891
5892sub git_heads_body {
5893 # uses global variable $project
Jakub Narebskifd49e562012-02-15 16:36:41 +01005894 my ($headlist, $head_at, $from, $to, $extra) = @_;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005895 $from = 0 unless defined $from;
Jakub Narebski120ddde2006-09-19 14:33:22 +02005896 $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005897
Jakub Narebski591ebf62007-11-19 14:16:11 +01005898 print "<table class=\"heads\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07005899 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005900 for (my $i = $from; $i <= $to; $i++) {
Jakub Narebski120ddde2006-09-19 14:33:22 +02005901 my $entry = $headlist->[$i];
Jakub Narebskicd146402006-11-02 20:23:11 +01005902 my %ref = %$entry;
Jakub Narebskifd49e562012-02-15 16:36:41 +01005903 my $curr = defined $head_at && $ref{'id'} eq $head_at;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005904 if ($alternate) {
5905 print "<tr class=\"dark\">\n";
5906 } else {
5907 print "<tr class=\"light\">\n";
5908 }
5909 $alternate ^= 1;
Jakub Narebskicd146402006-11-02 20:23:11 +01005910 print "<td><i>$ref{'age'}</i></td>\n" .
5911 ($curr ? "<td class=\"current_head\">" : "<td>") .
Jakub Narebskibf901f82007-12-15 15:40:28 +01005912 $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'}),
Jakub Narebskicd146402006-11-02 20:23:11 +01005913 -class => "list name"},esc_html($ref{'name'})) .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005914 "</td>\n" .
5915 "<td class=\"link\">" .
Jakub Narebskibf901f82007-12-15 15:40:28 +01005916 $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'})}, "shortlog") . " | " .
5917 $cgi->a({-href => href(action=>"log", hash=>$ref{'fullname'})}, "log") . " | " .
Giuseppe Bilotta9e70e152010-11-11 13:26:08 +01005918 $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'fullname'})}, "tree") .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005919 "</td>\n" .
5920 "</tr>";
5921 }
5922 if (defined $extra) {
5923 print "<tr>\n" .
5924 "<td colspan=\"3\">$extra</td>\n" .
5925 "</tr>\n";
5926 }
5927 print "</table>\n";
5928}
5929
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01005930# Display a single remote block
5931sub git_remote_block {
5932 my ($remote, $rdata, $limit, $head) = @_;
5933
5934 my $heads = $rdata->{'heads'};
5935 my $fetch = $rdata->{'fetch'};
5936 my $push = $rdata->{'push'};
5937
5938 my $urls_table = "<table class=\"projects_list\">\n" ;
5939
5940 if (defined $fetch) {
5941 if ($fetch eq $push) {
5942 $urls_table .= format_repo_url("URL", $fetch);
5943 } else {
5944 $urls_table .= format_repo_url("Fetch URL", $fetch);
5945 $urls_table .= format_repo_url("Push URL", $push) if defined $push;
5946 }
5947 } elsif (defined $push) {
5948 $urls_table .= format_repo_url("Push URL", $push);
5949 } else {
5950 $urls_table .= format_repo_url("", "No remote URL");
5951 }
5952
5953 $urls_table .= "</table>\n";
5954
5955 my $dots;
5956 if (defined $limit && $limit < @$heads) {
5957 $dots = $cgi->a({-href => href(action=>"remotes", hash=>$remote)}, "...");
5958 }
5959
5960 print $urls_table;
5961 git_heads_body($heads, $head, 0, $limit, $dots);
5962}
5963
5964# Display a list of remote names with the respective fetch and push URLs
5965sub git_remotes_list {
5966 my ($remotedata, $limit) = @_;
5967 print "<table class=\"heads\">\n";
5968 my $alternate = 1;
5969 my @remotes = sort keys %$remotedata;
5970
5971 my $limited = $limit && $limit < @remotes;
5972
5973 $#remotes = $limit - 1 if $limited;
5974
5975 while (my $remote = shift @remotes) {
5976 my $rdata = $remotedata->{$remote};
5977 my $fetch = $rdata->{'fetch'};
5978 my $push = $rdata->{'push'};
5979 if ($alternate) {
5980 print "<tr class=\"dark\">\n";
5981 } else {
5982 print "<tr class=\"light\">\n";
5983 }
5984 $alternate ^= 1;
5985 print "<td>" .
5986 $cgi->a({-href=> href(action=>'remotes', hash=>$remote),
5987 -class=> "list name"},esc_html($remote)) .
5988 "</td>";
5989 print "<td class=\"link\">" .
5990 (defined $fetch ? $cgi->a({-href=> $fetch}, "fetch") : "fetch") .
5991 " | " .
5992 (defined $push ? $cgi->a({-href=> $push}, "push") : "push") .
5993 "</td>";
5994
5995 print "</tr>\n";
5996 }
5997
5998 if ($limited) {
5999 print "<tr>\n" .
6000 "<td colspan=\"3\">" .
6001 $cgi->a({-href => href(action=>"remotes")}, "...") .
6002 "</td>\n" . "</tr>\n";
6003 }
6004
6005 print "</table>";
6006}
6007
6008# Display remote heads grouped by remote, unless there are too many
6009# remotes, in which case we only display the remote names
6010sub git_remotes_body {
6011 my ($remotedata, $limit, $head) = @_;
6012 if ($limit and $limit < keys %$remotedata) {
6013 git_remotes_list($remotedata, $limit);
6014 } else {
6015 fill_remote_heads($remotedata);
6016 while (my ($remote, $rdata) = each %$remotedata) {
6017 git_print_section({-class=>"remote", -id=>$remote},
6018 ["remotes", $remote, $remote], sub {
6019 git_remote_block($remote, $rdata, $limit, $head);
6020 });
6021 }
6022 }
6023}
6024
Jakub Narebski16f20722011-06-22 17:28:53 +02006025sub git_search_message {
6026 my %co = @_;
6027
6028 my $greptype;
6029 if ($searchtype eq 'commit') {
6030 $greptype = "--grep=";
6031 } elsif ($searchtype eq 'author') {
6032 $greptype = "--author=";
6033 } elsif ($searchtype eq 'committer') {
6034 $greptype = "--committer=";
6035 }
6036 $greptype .= $searchtext;
6037 my @commitlist = parse_commits($hash, 101, (100 * $page), undef,
6038 $greptype, '--regexp-ignore-case',
6039 $search_use_regexp ? '--extended-regexp' : '--fixed-strings');
6040
6041 my $paging_nav = '';
6042 if ($page > 0) {
6043 $paging_nav .=
Jakub Narebski882541b2011-06-22 17:28:54 +02006044 $cgi->a({-href => href(-replay=>1, page=>undef)},
6045 "first") .
6046 " &sdot; " .
Jakub Narebski16f20722011-06-22 17:28:53 +02006047 $cgi->a({-href => href(-replay=>1, page=>$page-1),
6048 -accesskey => "p", -title => "Alt-p"}, "prev");
6049 } else {
Jakub Narebski882541b2011-06-22 17:28:54 +02006050 $paging_nav .= "first &sdot; prev";
Jakub Narebski16f20722011-06-22 17:28:53 +02006051 }
6052 my $next_link = '';
6053 if ($#commitlist >= 100) {
6054 $next_link =
6055 $cgi->a({-href => href(-replay=>1, page=>$page+1),
6056 -accesskey => "n", -title => "Alt-n"}, "next");
6057 $paging_nav .= " &sdot; $next_link";
6058 } else {
6059 $paging_nav .= " &sdot; next";
6060 }
6061
Jakub Narebski1ae05be2011-06-22 17:28:55 +02006062 git_header_html();
6063
Jakub Narebski16f20722011-06-22 17:28:53 +02006064 git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
6065 git_print_header_div('commit', esc_html($co{'title'}), $hash);
6066 if ($page == 0 && !@commitlist) {
6067 print "<p>No match.</p>\n";
6068 } else {
6069 git_search_grep_body(\@commitlist, 0, 99, $next_link);
6070 }
Jakub Narebski1ae05be2011-06-22 17:28:55 +02006071
6072 git_footer_html();
Jakub Narebski16f20722011-06-22 17:28:53 +02006073}
6074
6075sub git_search_changes {
6076 my %co = @_;
6077
Jakub Narebski1ae05be2011-06-22 17:28:55 +02006078 local $/ = "\n";
6079 open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts,
6080 '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext",
6081 ($search_use_regexp ? '--pickaxe-regex' : ())
6082 or die_error(500, "Open git-log failed");
6083
6084 git_header_html();
6085
Jakub Narebski16f20722011-06-22 17:28:53 +02006086 git_print_page_nav('','', $hash,$co{'tree'},$hash);
6087 git_print_header_div('commit', esc_html($co{'title'}), $hash);
6088
6089 print "<table class=\"pickaxe search\">\n";
6090 my $alternate = 1;
Jakub Narebski16f20722011-06-22 17:28:53 +02006091 undef %co;
6092 my @files;
6093 while (my $line = <$fd>) {
6094 chomp $line;
6095 next unless $line;
6096
6097 my %set = parse_difftree_raw_line($line);
6098 if (defined $set{'commit'}) {
6099 # finish previous commit
6100 if (%co) {
6101 print "</td>\n" .
6102 "<td class=\"link\">" .
Jakub Narebski882541b2011-06-22 17:28:54 +02006103 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})},
6104 "commit") .
Jakub Narebski16f20722011-06-22 17:28:53 +02006105 " | " .
Jakub Narebski882541b2011-06-22 17:28:54 +02006106 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'},
6107 hash_base=>$co{'id'})},
6108 "tree") .
6109 "</td>\n" .
Jakub Narebski16f20722011-06-22 17:28:53 +02006110 "</tr>\n";
6111 }
6112
6113 if ($alternate) {
6114 print "<tr class=\"dark\">\n";
6115 } else {
6116 print "<tr class=\"light\">\n";
6117 }
6118 $alternate ^= 1;
6119 %co = parse_commit($set{'commit'});
6120 my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
6121 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
6122 "<td><i>$author</i></td>\n" .
6123 "<td>" .
6124 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
6125 -class => "list subject"},
6126 chop_and_escape_str($co{'title'}, 50) . "<br/>");
6127 } elsif (defined $set{'to_id'}) {
6128 next if ($set{'to_id'} =~ m/^0{40}$/);
6129
6130 print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
6131 hash=>$set{'to_id'}, file_name=>$set{'to_file'}),
6132 -class => "list"},
6133 "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
6134 "<br/>\n";
6135 }
6136 }
6137 close $fd;
6138
6139 # finish last commit (warning: repetition!)
6140 if (%co) {
6141 print "</td>\n" .
6142 "<td class=\"link\">" .
Jakub Narebski882541b2011-06-22 17:28:54 +02006143 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})},
6144 "commit") .
Jakub Narebski16f20722011-06-22 17:28:53 +02006145 " | " .
Jakub Narebski882541b2011-06-22 17:28:54 +02006146 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'},
6147 hash_base=>$co{'id'})},
6148 "tree") .
6149 "</td>\n" .
Jakub Narebski16f20722011-06-22 17:28:53 +02006150 "</tr>\n";
6151 }
6152
6153 print "</table>\n";
Jakub Narebski1ae05be2011-06-22 17:28:55 +02006154
6155 git_footer_html();
Jakub Narebski16f20722011-06-22 17:28:53 +02006156}
6157
6158sub git_search_files {
6159 my %co = @_;
6160
Jakub Narebski1ae05be2011-06-22 17:28:55 +02006161 local $/ = "\n";
Jakub Narebski8e09fd12012-01-05 21:32:56 +01006162 open my $fd, "-|", git_cmd(), 'grep', '-n', '-z',
Jakub Narebski1ae05be2011-06-22 17:28:55 +02006163 $search_use_regexp ? ('-E', '-i') : '-F',
6164 $searchtext, $co{'tree'}
6165 or die_error(500, "Open git-grep failed");
6166
6167 git_header_html();
6168
Jakub Narebski16f20722011-06-22 17:28:53 +02006169 git_print_page_nav('','', $hash,$co{'tree'},$hash);
6170 git_print_header_div('commit', esc_html($co{'title'}), $hash);
6171
6172 print "<table class=\"grep_search\">\n";
6173 my $alternate = 1;
6174 my $matches = 0;
Jakub Narebski16f20722011-06-22 17:28:53 +02006175 my $lastfile = '';
Jakub Narebskifc8fcd22012-02-15 17:37:06 +01006176 my $file_href;
Jakub Narebski16f20722011-06-22 17:28:53 +02006177 while (my $line = <$fd>) {
6178 chomp $line;
Jakub Narebskifc8fcd22012-02-15 17:37:06 +01006179 my ($file, $lno, $ltext, $binary);
Jakub Narebski16f20722011-06-22 17:28:53 +02006180 last if ($matches++ > 1000);
6181 if ($line =~ /^Binary file (.+) matches$/) {
6182 $file = $1;
6183 $binary = 1;
6184 } else {
Jakub Narebski8e09fd12012-01-05 21:32:56 +01006185 ($file, $lno, $ltext) = split(/\0/, $line, 3);
6186 $file =~ s/^$co{'tree'}://;
Jakub Narebski16f20722011-06-22 17:28:53 +02006187 }
6188 if ($file ne $lastfile) {
6189 $lastfile and print "</td></tr>\n";
6190 if ($alternate++) {
6191 print "<tr class=\"dark\">\n";
6192 } else {
6193 print "<tr class=\"light\">\n";
6194 }
Jakub Narebskiff7f2182012-01-05 21:26:48 +01006195 $file_href = href(action=>"blob", hash_base=>$co{'id'},
6196 file_name=>$file);
Jakub Narebski16f20722011-06-22 17:28:53 +02006197 print "<td class=\"list\">".
Jakub Narebskiff7f2182012-01-05 21:26:48 +01006198 $cgi->a({-href => $file_href, -class => "list"}, esc_path($file));
Jakub Narebski16f20722011-06-22 17:28:53 +02006199 print "</td><td>\n";
6200 $lastfile = $file;
6201 }
6202 if ($binary) {
6203 print "<div class=\"binary\">Binary file</div>\n";
6204 } else {
6205 $ltext = untabify($ltext);
6206 if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) {
6207 $ltext = esc_html($1, -nbsp=>1);
6208 $ltext .= '<span class="match">';
6209 $ltext .= esc_html($2, -nbsp=>1);
6210 $ltext .= '</span>';
6211 $ltext .= esc_html($3, -nbsp=>1);
6212 } else {
6213 $ltext = esc_html($ltext, -nbsp=>1);
6214 }
6215 print "<div class=\"pre\">" .
Jakub Narebskiff7f2182012-01-05 21:26:48 +01006216 $cgi->a({-href => $file_href.'#l'.$lno,
6217 -class => "linenr"}, sprintf('%4i', $lno)) .
6218 ' ' . $ltext . "</div>\n";
Jakub Narebski16f20722011-06-22 17:28:53 +02006219 }
6220 }
6221 if ($lastfile) {
6222 print "</td></tr>\n";
6223 if ($matches > 1000) {
6224 print "<div class=\"diff nodifferences\">Too many matches, listing trimmed</div>\n";
6225 }
6226 } else {
6227 print "<div class=\"diff nodifferences\">No matches found</div>\n";
6228 }
6229 close $fd;
6230
6231 print "</table>\n";
Jakub Narebski1ae05be2011-06-22 17:28:55 +02006232
6233 git_footer_html();
Jakub Narebski16f20722011-06-22 17:28:53 +02006234}
6235
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006236sub git_search_grep_body {
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00006237 my ($commitlist, $from, $to, $extra) = @_;
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006238 $from = 0 unless defined $from;
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00006239 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006240
Jakub Narebski591ebf62007-11-19 14:16:11 +01006241 print "<table class=\"commit_search\">\n";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006242 my $alternate = 1;
6243 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00006244 my %co = %{$commitlist->[$i]};
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006245 if (!%co) {
6246 next;
6247 }
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00006248 my $commit = $co{'id'};
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006249 if ($alternate) {
6250 print "<tr class=\"dark\">\n";
6251 } else {
6252 print "<tr class=\"light\">\n";
6253 }
6254 $alternate ^= 1;
6255 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02006256 format_author_html('td', \%co, 15, 5) .
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006257 "<td>" .
Junio C Hamanobe8b9062008-02-22 17:33:47 +01006258 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
6259 -class => "list subject"},
6260 chop_and_escape_str($co{'title'}, 50) . "<br/>");
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006261 my $comment = $co{'comment'};
6262 foreach my $line (@$comment) {
Jakub Narebski6dfbb302008-03-02 16:57:14 +01006263 if ($line =~ m/^(.*?)($search_regexp)(.*)$/i) {
Junio C Hamanobe8b9062008-02-22 17:33:47 +01006264 my ($lead, $match, $trail) = ($1, $2, $3);
Jakub Narebskib8d97d02008-02-25 21:07:57 +01006265 $match = chop_str($match, 70, 5, 'center');
6266 my $contextlen = int((80 - length($match))/2);
6267 $contextlen = 30 if ($contextlen > 30);
6268 $lead = chop_str($lead, $contextlen, 10, 'left');
6269 $trail = chop_str($trail, $contextlen, 10, 'right');
Junio C Hamanobe8b9062008-02-22 17:33:47 +01006270
6271 $lead = esc_html($lead);
6272 $match = esc_html($match);
6273 $trail = esc_html($trail);
6274
6275 print "$lead<span class=\"match\">$match</span>$trail<br />";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006276 }
6277 }
6278 print "</td>\n" .
6279 "<td class=\"link\">" .
6280 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
6281 " | " .
Denis Chengf1fe8f52007-11-26 20:42:06 +08006282 $cgi->a({-href => href(action=>"commitdiff", hash=>$co{'id'})}, "commitdiff") .
6283 " | " .
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006284 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
6285 print "</td>\n" .
6286 "</tr>\n";
6287 }
6288 if (defined $extra) {
6289 print "<tr>\n" .
6290 "<td colspan=\"3\">$extra</td>\n" .
6291 "</tr>\n";
6292 }
6293 print "</table>\n";
6294}
6295
Jakub Narebski717b8312006-07-31 21:22:15 +02006296## ======================================================================
6297## ======================================================================
6298## actions
6299
Jakub Narebski717b8312006-07-31 21:22:15 +02006300sub git_project_list {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02006301 my $order = $input_params{'order'};
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02006302 if (defined $order && $order !~ m/none|project|descr|owner|age/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006303 die_error(400, "Unknown order parameter");
Jakub Narebski6326b602006-08-01 02:59:12 +02006304 }
6305
Bernhard R. Link19d2d232012-01-30 21:07:37 +01006306 my @list = git_get_projects_list($project_filter, $strict_export);
Jakub Narebski717b8312006-07-31 21:22:15 +02006307 if (!@list) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006308 die_error(404, "No projects found");
Jakub Narebski717b8312006-07-31 21:22:15 +02006309 }
Jakub Narebski6326b602006-08-01 02:59:12 +02006310
Jakub Narebski717b8312006-07-31 21:22:15 +02006311 git_header_html();
John 'Warthog9' Hawley24d4afc2010-01-30 23:30:41 +01006312 if (defined $home_text && -f $home_text) {
Jakub Narebski717b8312006-07-31 21:22:15 +02006313 print "<div class=\"index_include\">\n";
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01006314 insert_file($home_text);
Jakub Narebski717b8312006-07-31 21:22:15 +02006315 print "</div>\n";
6316 }
Jakub Narebskia1e1b2d2012-01-31 01:20:54 +01006317
6318 git_project_search_form($searchtext, $search_use_regexp);
Petr Baudise30496d2006-10-24 05:33:17 +02006319 git_project_list_body(\@list, $order);
6320 git_footer_html();
6321}
6322
6323sub git_forks {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02006324 my $order = $input_params{'order'};
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02006325 if (defined $order && $order !~ m/none|project|descr|owner|age/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006326 die_error(400, "Unknown order parameter");
Jakub Narebski717b8312006-07-31 21:22:15 +02006327 }
Petr Baudise30496d2006-10-24 05:33:17 +02006328
Bernhard R. Link4c7cd172012-01-30 21:05:47 +01006329 my $filter = $project;
6330 $filter =~ s/\.git$//;
6331 my @list = git_get_projects_list($filter);
Petr Baudise30496d2006-10-24 05:33:17 +02006332 if (!@list) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006333 die_error(404, "No forks found");
Jakub Narebski717b8312006-07-31 21:22:15 +02006334 }
Petr Baudise30496d2006-10-24 05:33:17 +02006335
6336 git_header_html();
6337 git_print_page_nav('','');
6338 git_print_header_div('summary', "$project forks");
6339 git_project_list_body(\@list, $order);
Jakub Narebski717b8312006-07-31 21:22:15 +02006340 git_footer_html();
6341}
6342
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02006343sub git_project_index {
Bernhard R. Link19d2d232012-01-30 21:07:37 +01006344 my @projects = git_get_projects_list($project_filter, $strict_export);
Jakub Narebski12b14432011-04-29 19:51:56 +02006345 if (!@projects) {
6346 die_error(404, "No projects found");
6347 }
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02006348
6349 print $cgi->header(
6350 -type => 'text/plain',
6351 -charset => 'utf-8',
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02006352 -content_disposition => 'inline; filename="index.aux"');
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02006353
6354 foreach my $pr (@projects) {
6355 if (!exists $pr->{'owner'}) {
Miklos Vajna76e4f5d2007-07-04 00:11:23 +02006356 $pr->{'owner'} = git_get_project_owner("$pr->{'path'}");
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02006357 }
6358
6359 my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'});
6360 # quote as in CGI::Util::encode, but keep the slash, and use '+' for ' '
6361 $path =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
6362 $owner =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
6363 $path =~ s/ /\+/g;
6364 $owner =~ s/ /\+/g;
6365
6366 print "$path $owner\n";
6367 }
6368}
6369
Kay Sieversede5e102005-08-07 20:23:12 +02006370sub git_summary {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006371 my $descr = git_get_project_description($project) || "none";
Robert Fitzsimonsa979d122006-12-22 19:38:15 +00006372 my %co = parse_commit("HEAD");
Jakub Narebski785cdea2007-05-13 12:39:22 +02006373 my %cd = %co ? parse_date($co{'committer_epoch'}, $co{'committer_tz'}) : ();
Robert Fitzsimonsa979d122006-12-22 19:38:15 +00006374 my $head = $co{'id'};
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01006375 my $remote_heads = gitweb_check_feature('remote_heads');
Kay Sieversede5e102005-08-07 20:23:12 +02006376
Jakub Narebski1e0cf032006-08-14 02:10:06 +02006377 my $owner = git_get_project_owner($project);
Kay Sieversede5e102005-08-07 20:23:12 +02006378
Jakub Narebskicd146402006-11-02 20:23:11 +01006379 my $refs = git_get_references();
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00006380 # These get_*_list functions return one more to allow us to see if
6381 # there are more ...
6382 my @taglist = git_get_tags_list(16);
6383 my @headlist = git_get_heads_list(16);
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006384 my %remotedata = $remote_heads ? git_get_remotes_list() : ();
Petr Baudise30496d2006-10-24 05:33:17 +02006385 my @forklist;
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006386 my $check_forks = gitweb_check_feature('forks');
Junio C Hamano5dd5ed02006-11-07 22:00:45 -08006387
6388 if ($check_forks) {
Jakub Narebski12b14432011-04-29 19:51:56 +02006389 # find forks of a project
Bernhard R. Link4c7cd172012-01-30 21:05:47 +01006390 my $filter = $project;
6391 $filter =~ s/\.git$//;
6392 @forklist = git_get_projects_list($filter);
Jakub Narebski12b14432011-04-29 19:51:56 +02006393 # filter out forks of forks
6394 @forklist = filter_forks_from_projects_list(\@forklist)
6395 if (@forklist);
Petr Baudise30496d2006-10-24 05:33:17 +02006396 }
Jakub Narebski120ddde2006-09-19 14:33:22 +02006397
Kay Sieversede5e102005-08-07 20:23:12 +02006398 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02006399 git_print_page_nav('summary','', $head);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006400
Kay Sievers19806692005-08-07 20:26:27 +02006401 print "<div class=\"title\">&nbsp;</div>\n";
Jakub Narebski591ebf62007-11-19 14:16:11 +01006402 print "<table class=\"projects_list\">\n" .
Petr Baudisa4761422008-10-02 16:25:05 +02006403 "<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
6404 "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
Jakub Narebski785cdea2007-05-13 12:39:22 +02006405 if (defined $cd{'rfc2822'}) {
Jakub Narebski256b7b42011-04-28 21:04:07 +02006406 print "<tr id=\"metadata_lchange\"><td>last change</td>" .
6407 "<td>".format_timestamp_html(\%cd)."</td></tr>\n";
Jakub Narebski785cdea2007-05-13 12:39:22 +02006408 }
6409
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02006410 # use per project git URL list in $projectroot/$project/cloneurl
6411 # or make project git URL from git base URL and project name
Jakub Narebski19a87212006-08-15 23:03:17 +02006412 my $url_tag = "URL";
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02006413 my @url_list = git_get_project_url_list($project);
6414 @url_list = map { "$_/$project" } @git_base_url_list unless @url_list;
6415 foreach my $git_url (@url_list) {
6416 next unless $git_url;
Giuseppe Bilotta0e656992010-11-11 13:26:15 +01006417 print format_repo_url($url_tag, $git_url);
Jakub Narebski19a87212006-08-15 23:03:17 +02006418 $url_tag = "";
6419 }
Petr Baudisaed93de2008-10-02 17:13:02 +02006420
6421 # Tag cloud
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006422 my $show_ctags = gitweb_check_feature('ctags');
Petr Baudisaed93de2008-10-02 17:13:02 +02006423 if ($show_ctags) {
6424 my $ctags = git_get_project_ctags($project);
Jakub Narebski0368c492011-04-29 19:51:57 +02006425 if (%$ctags) {
6426 # without ability to add tags, don't show if there are none
6427 my $cloud = git_populate_project_tagcloud($ctags);
6428 print "<tr id=\"metadata_ctags\">" .
6429 "<td>content tags</td>" .
6430 "<td>".git_show_project_tagcloud($cloud, 48)."</td>" .
6431 "</tr>\n";
6432 }
Petr Baudisaed93de2008-10-02 17:13:02 +02006433 }
6434
Jakub Narebski19a87212006-08-15 23:03:17 +02006435 print "</table>\n";
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006436
Matt McCutchen7e1100e2009-02-07 19:00:09 -05006437 # If XSS prevention is on, we don't include README.html.
6438 # TODO: Allow a readme in some safe format.
6439 if (!$prevent_xss && -s "$projectroot/$project/README.html") {
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01006440 print "<div class=\"title\">readme</div>\n" .
6441 "<div class=\"readme\">\n";
6442 insert_file("$projectroot/$project/README.html");
6443 print "\n</div>\n"; # class="readme"
Petr Baudis447ef092006-10-24 05:23:46 +02006444 }
6445
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00006446 # we need to request one more than 16 (0..15) to check if
6447 # those 16 are all
Jakub Narebski785cdea2007-05-13 12:39:22 +02006448 my @commitlist = $head ? parse_commits($head, 17) : ();
6449 if (@commitlist) {
6450 git_print_header_div('shortlog');
6451 git_shortlog_body(\@commitlist, 0, 15, $refs,
6452 $#commitlist <= 15 ? undef :
6453 $cgi->a({-href => href(action=>"shortlog")}, "..."));
6454 }
Kay Sieversede5e102005-08-07 20:23:12 +02006455
Jakub Narebski120ddde2006-09-19 14:33:22 +02006456 if (@taglist) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006457 git_print_header_div('tags');
Jakub Narebski120ddde2006-09-19 14:33:22 +02006458 git_tags_body(\@taglist, 0, 15,
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00006459 $#taglist <= 15 ? undef :
Martin Waitz1c2a4f52006-08-16 00:24:30 +02006460 $cgi->a({-href => href(action=>"tags")}, "..."));
Kay Sieversede5e102005-08-07 20:23:12 +02006461 }
Kay Sievers0db37972005-08-07 20:24:35 +02006462
Jakub Narebski120ddde2006-09-19 14:33:22 +02006463 if (@headlist) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006464 git_print_header_div('heads');
Jakub Narebski120ddde2006-09-19 14:33:22 +02006465 git_heads_body(\@headlist, $head, 0, 15,
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00006466 $#headlist <= 15 ? undef :
Martin Waitz1c2a4f52006-08-16 00:24:30 +02006467 $cgi->a({-href => href(action=>"heads")}, "..."));
Kay Sievers0db37972005-08-07 20:24:35 +02006468 }
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006469
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006470 if (%remotedata) {
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01006471 git_print_header_div('remotes');
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006472 git_remotes_body(\%remotedata, 15, $head);
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01006473 }
6474
Petr Baudise30496d2006-10-24 05:33:17 +02006475 if (@forklist) {
6476 git_print_header_div('forks');
Mike Ralphsonf04f27e2008-09-25 18:48:48 +02006477 git_project_list_body(\@forklist, 'age', 0, 15,
Robert Fitzsimonsaaca9672006-12-22 19:38:12 +00006478 $#forklist <= 15 ? undef :
Petr Baudise30496d2006-10-24 05:33:17 +02006479 $cgi->a({-href => href(action=>"forks")}, "..."),
Mike Ralphsonf04f27e2008-09-25 18:48:48 +02006480 'no_header');
Petr Baudise30496d2006-10-24 05:33:17 +02006481 }
6482
Kay Sieversede5e102005-08-07 20:23:12 +02006483 git_footer_html();
6484}
6485
Kay Sieversd8a20ba2005-08-07 20:28:53 +02006486sub git_tag {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006487 my %tag = parse_tag($hash);
Jakub Narebski198a2a82007-05-12 21:16:34 +02006488
6489 if (! %tag) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006490 die_error(404, "Unknown tag object");
Jakub Narebski198a2a82007-05-12 21:16:34 +02006491 }
6492
Anders Kaseorgd8a94802010-08-27 13:38:16 -04006493 my $head = git_get_head_hash($project);
6494 git_header_html();
6495 git_print_page_nav('','', $head,undef,$head);
Jakub Narebski847e01f2006-08-14 02:05:47 +02006496 git_print_header_div('commit', esc_html($tag{'name'}), $hash);
Kay Sieversd8a20ba2005-08-07 20:28:53 +02006497 print "<div class=\"title_text\">\n" .
Jakub Narebski591ebf62007-11-19 14:16:11 +01006498 "<table class=\"object_header\">\n" .
Kay Sieverse4669df2005-08-08 00:02:39 +02006499 "<tr>\n" .
6500 "<td>object</td>\n" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02006501 "<td>" . $cgi->a({-class => "list", -href => href(action=>$tag{'type'}, hash=>$tag{'object'})},
6502 $tag{'object'}) . "</td>\n" .
6503 "<td class=\"link\">" . $cgi->a({-href => href(action=>$tag{'type'}, hash=>$tag{'object'})},
6504 $tag{'type'}) . "</td>\n" .
Kay Sieverse4669df2005-08-08 00:02:39 +02006505 "</tr>\n";
Kay Sieversd8a20ba2005-08-07 20:28:53 +02006506 if (defined($tag{'author'})) {
Giuseppe Bilottaba924732009-06-30 00:00:50 +02006507 git_print_authorship_rows(\%tag, 'author');
Kay Sieversd8a20ba2005-08-07 20:28:53 +02006508 }
6509 print "</table>\n\n" .
6510 "</div>\n";
6511 print "<div class=\"page_body\">";
6512 my $comment = $tag{'comment'};
6513 foreach my $line (@$comment) {
Junio C Hamano70022432006-11-24 14:04:01 -08006514 chomp $line;
Jakub Narebski793c4002006-11-24 22:25:50 +01006515 print esc_html($line, -nbsp=>1) . "<br/>\n";
Kay Sieversd8a20ba2005-08-07 20:28:53 +02006516 }
6517 print "</div>\n";
6518 git_footer_html();
6519}
6520
Jakub Narebski4af819d2009-09-01 13:39:17 +02006521sub git_blame_common {
6522 my $format = shift || 'porcelain';
Jakub Narebski84d9e2d2012-02-03 13:44:54 +01006523 if ($format eq 'porcelain' && $input_params{'javascript'}) {
Jakub Narebskic4ccf612009-09-01 13:39:19 +02006524 $format = 'incremental';
6525 $action = 'blame_incremental'; # for page title etc
6526 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02006527
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006528 # permissions
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006529 gitweb_check_feature('blame')
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006530 or die_error(403, "Blame view not allowed");
Lea Wiemann074afaa2008-06-19 22:03:21 +02006531
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006532 # error checking
Lea Wiemann074afaa2008-06-19 22:03:21 +02006533 die_error(400, "No file name given") unless $file_name;
Jakub Narebski847e01f2006-08-14 02:05:47 +02006534 $hash_base ||= git_get_head_hash($project);
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006535 die_error(404, "Couldn't find base commit") unless $hash_base;
Jakub Narebski847e01f2006-08-14 02:05:47 +02006536 my %co = parse_commit($hash_base)
Lea Wiemann074afaa2008-06-19 22:03:21 +02006537 or die_error(404, "Commit not found");
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006538 my $ftype = "blob";
Luben Tuikov1f2857e2006-07-23 13:34:55 -07006539 if (!defined $hash) {
6540 $hash = git_get_hash_by_path($hash_base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02006541 or die_error(404, "Error looking up file");
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006542 } else {
6543 $ftype = git_get_type($hash);
6544 if ($ftype !~ "blob") {
6545 die_error(400, "Object is not a blob");
6546 }
Luben Tuikov1f2857e2006-07-23 13:34:55 -07006547 }
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006548
Jakub Narebski4af819d2009-09-01 13:39:17 +02006549 my $fd;
6550 if ($format eq 'incremental') {
6551 # get file contents (as base)
6552 open $fd, "-|", git_cmd(), 'cat-file', 'blob', $hash
6553 or die_error(500, "Open git-cat-file failed");
6554 } elsif ($format eq 'data') {
6555 # run git-blame --incremental
6556 open $fd, "-|", git_cmd(), "blame", "--incremental",
6557 $hash_base, "--", $file_name
6558 or die_error(500, "Open git-blame --incremental failed");
6559 } else {
6560 # run git-blame --porcelain
6561 open $fd, "-|", git_cmd(), "blame", '-p',
6562 $hash_base, '--', $file_name
6563 or die_error(500, "Open git-blame --porcelain failed");
6564 }
6565
6566 # incremental blame data returns early
6567 if ($format eq 'data') {
6568 print $cgi->header(
6569 -type=>"text/plain", -charset => "utf-8",
6570 -status=> "200 OK");
6571 local $| = 1; # output autoflush
Jürgen Kreileder57cf4ad2011-12-17 10:22:23 +01006572 while (my $line = <$fd>) {
6573 print to_utf8($line);
6574 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02006575 close $fd
6576 or print "ERROR $!\n";
6577
6578 print 'END';
6579 if (defined $t0 && gitweb_check_feature('timed')) {
6580 print ' '.
Jakub Narebski3962f1d72010-11-09 19:27:54 +01006581 tv_interval($t0, [ gettimeofday() ]).
Jakub Narebski4af819d2009-09-01 13:39:17 +02006582 ' '.$number_of_git_cmds;
6583 }
6584 print "\n";
6585
6586 return;
6587 }
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006588
6589 # page header
Luben Tuikov1f2857e2006-07-23 13:34:55 -07006590 git_header_html();
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02006591 my $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01006592 $cgi->a({-href => href(action=>"blob", -replay=>1)},
Jakub Narebski952c65f2006-08-22 16:52:50 +02006593 "blob") .
Jakub Narebski87e573f2009-12-01 17:54:26 +01006594 " | ";
6595 if ($format eq 'incremental') {
6596 $formats_nav .=
6597 $cgi->a({-href => href(action=>"blame", javascript=>0, -replay=>1)},
6598 "blame") . " (non-incremental)";
6599 } else {
6600 $formats_nav .=
6601 $cgi->a({-href => href(action=>"blame_incremental", -replay=>1)},
6602 "blame") . " (incremental)";
6603 }
6604 $formats_nav .=
Jakub Narebski952c65f2006-08-22 16:52:50 +02006605 " | " .
Jakub Narebskia3823e52007-11-01 13:06:29 +01006606 $cgi->a({-href => href(action=>"history", -replay=>1)},
6607 "history") .
Petr Baudiscae18622006-09-22 03:19:41 +02006608 " | " .
Jakub Narebski4af819d2009-09-01 13:39:17 +02006609 $cgi->a({-href => href(action=>$action, file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02006610 "HEAD");
Jakub Narebski847e01f2006-08-14 02:05:47 +02006611 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
6612 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
Luben Tuikov59fb1c92006-08-17 10:39:29 -07006613 git_print_page_path($file_name, $ftype, $hash_base);
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006614
6615 # page body
Jakub Narebski4af819d2009-09-01 13:39:17 +02006616 if ($format eq 'incremental') {
6617 print "<noscript>\n<div class=\"error\"><center><b>\n".
6618 "This page requires JavaScript to run.\n Use ".
Jakub Narebskic4ccf612009-09-01 13:39:19 +02006619 $cgi->a({-href => href(action=>'blame',javascript=>0,-replay=>1)},
Jakub Narebski4af819d2009-09-01 13:39:17 +02006620 'this page').
6621 " instead.\n".
6622 "</b></center></div>\n</noscript>\n";
6623
6624 print qq!<div id="progress_bar" style="width: 100%; background-color: yellow"></div>\n!;
6625 }
6626
6627 print qq!<div class="page_body">\n!;
6628 print qq!<div id="progress_info">... / ...</div>\n!
6629 if ($format eq 'incremental');
6630 print qq!<table id="blame_table" class="blame" width="100%">\n!.
6631 #qq!<col width="5.5em" /><col width="2.5em" /><col width="*" />\n!.
6632 qq!<thead>\n!.
6633 qq!<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n!.
6634 qq!</thead>\n!.
6635 qq!<tbody>\n!;
6636
Jakub Narebskiaef37682009-07-25 00:44:06 +02006637 my @rev_color = qw(light dark);
Luben Tuikovcc1bf972006-07-23 13:37:53 -07006638 my $num_colors = scalar(@rev_color);
6639 my $current_color = 0;
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006640
Jakub Narebski4af819d2009-09-01 13:39:17 +02006641 if ($format eq 'incremental') {
6642 my $color_class = $rev_color[$current_color];
6643
6644 #contents of a file
6645 my $linenr = 0;
6646 LINE:
6647 while (my $line = <$fd>) {
6648 chomp $line;
6649 $linenr++;
6650
6651 print qq!<tr id="l$linenr" class="$color_class">!.
6652 qq!<td class="sha1"><a href=""> </a></td>!.
6653 qq!<td class="linenr">!.
6654 qq!<a class="linenr" href="">$linenr</a></td>!;
6655 print qq!<td class="pre">! . esc_html($line) . "</td>\n";
6656 print qq!</tr>\n!;
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07006657 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02006658
6659 } else { # porcelain, i.e. ordinary blame
6660 my %metainfo = (); # saves information about commits
6661
6662 # blame data
6663 LINE:
6664 while (my $line = <$fd>) {
6665 chomp $line;
6666 # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
6667 # no <lines in group> for subsequent lines in group of lines
6668 my ($full_rev, $orig_lineno, $lineno, $group_size) =
6669 ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
6670 if (!exists $metainfo{$full_rev}) {
6671 $metainfo{$full_rev} = { 'nprevious' => 0 };
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07006672 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02006673 my $meta = $metainfo{$full_rev};
6674 my $data;
6675 while ($data = <$fd>) {
6676 chomp $data;
6677 last if ($data =~ s/^\t//); # contents of line
6678 if ($data =~ /^(\S+)(?: (.*))?$/) {
6679 $meta->{$1} = $2 unless exists $meta->{$1};
6680 }
6681 if ($data =~ /^previous /) {
6682 $meta->{'nprevious'}++;
Jakub Narebskia36817b2009-07-25 00:44:05 +02006683 }
6684 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02006685 my $short_rev = substr($full_rev, 0, 8);
6686 my $author = $meta->{'author'};
6687 my %date =
6688 parse_date($meta->{'author-time'}, $meta->{'author-tz'});
6689 my $date = $date{'iso-tz'};
6690 if ($group_size) {
6691 $current_color = ($current_color + 1) % $num_colors;
6692 }
6693 my $tr_class = $rev_color[$current_color];
6694 $tr_class .= ' boundary' if (exists $meta->{'boundary'});
6695 $tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0);
6696 $tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1);
6697 print "<tr id=\"l$lineno\" class=\"$tr_class\">\n";
6698 if ($group_size) {
6699 print "<td class=\"sha1\"";
6700 print " title=\"". esc_html($author) . ", $date\"";
6701 print " rowspan=\"$group_size\"" if ($group_size > 1);
6702 print ">";
6703 print $cgi->a({-href => href(action=>"commit",
6704 hash=>$full_rev,
6705 file_name=>$file_name)},
6706 esc_html($short_rev));
6707 if ($group_size >= 2) {
6708 my @author_initials = ($author =~ /\b([[:upper:]])\B/g);
6709 if (@author_initials) {
6710 print "<br />" .
6711 esc_html(join('', @author_initials));
6712 # or join('.', ...)
6713 }
6714 }
6715 print "</td>\n";
6716 }
6717 # 'previous' <sha1 of parent commit> <filename at commit>
6718 if (exists $meta->{'previous'} &&
6719 $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) {
6720 $meta->{'parent'} = $1;
6721 $meta->{'file_parent'} = unquote($2);
6722 }
6723 my $linenr_commit =
6724 exists($meta->{'parent'}) ?
6725 $meta->{'parent'} : $full_rev;
6726 my $linenr_filename =
6727 exists($meta->{'file_parent'}) ?
6728 $meta->{'file_parent'} : unquote($meta->{'filename'});
6729 my $blamed = href(action => 'blame',
6730 file_name => $linenr_filename,
6731 hash_base => $linenr_commit);
6732 print "<td class=\"linenr\">";
6733 print $cgi->a({ -href => "$blamed#l$orig_lineno",
6734 -class => "linenr" },
6735 esc_html($lineno));
6736 print "</td>";
6737 print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
6738 print "</tr>\n";
6739 } # end while
6740
Luben Tuikov1f2857e2006-07-23 13:34:55 -07006741 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02006742
6743 # footer
6744 print "</tbody>\n".
6745 "</table>\n"; # class="blame"
6746 print "</div>\n"; # class="blame_body"
Jakub Narebski952c65f2006-08-22 16:52:50 +02006747 close $fd
6748 or print "Reading blob failed\n";
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01006749
Luben Tuikov1f2857e2006-07-23 13:34:55 -07006750 git_footer_html();
6751}
6752
Jakub Narebski4af819d2009-09-01 13:39:17 +02006753sub git_blame {
6754 git_blame_common();
6755}
6756
6757sub git_blame_incremental {
6758 git_blame_common('incremental');
6759}
6760
6761sub git_blame_data {
6762 git_blame_common('data');
6763}
6764
Kay Sieversede5e102005-08-07 20:23:12 +02006765sub git_tags {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006766 my $head = git_get_head_hash($project);
Kay Sieversede5e102005-08-07 20:23:12 +02006767 git_header_html();
Giuseppe Bilotta11e7bec2010-11-11 13:26:12 +01006768 git_print_page_nav('','', $head,undef,$head,format_ref_views('tags'));
Jakub Narebski847e01f2006-08-14 02:05:47 +02006769 git_print_header_div('summary', $project);
Jakub Narebski27fb8c42006-07-30 20:32:01 +02006770
Jakub Narebskicd146402006-11-02 20:23:11 +01006771 my @tagslist = git_get_tags_list();
6772 if (@tagslist) {
6773 git_tags_body(\@tagslist);
Kay Sieversede5e102005-08-07 20:23:12 +02006774 }
Kay Sieversede5e102005-08-07 20:23:12 +02006775 git_footer_html();
6776}
6777
Kay Sieversd8f1c5c2005-10-04 01:12:47 +02006778sub git_heads {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006779 my $head = git_get_head_hash($project);
Kay Sievers0db37972005-08-07 20:24:35 +02006780 git_header_html();
Giuseppe Bilotta11e7bec2010-11-11 13:26:12 +01006781 git_print_page_nav('','', $head,undef,$head,format_ref_views('heads'));
Jakub Narebski847e01f2006-08-14 02:05:47 +02006782 git_print_header_div('summary', $project);
Jakub Narebski27fb8c42006-07-30 20:32:01 +02006783
Jakub Narebskicd146402006-11-02 20:23:11 +01006784 my @headslist = git_get_heads_list();
6785 if (@headslist) {
6786 git_heads_body(\@headslist, $head);
Kay Sievers0db37972005-08-07 20:24:35 +02006787 }
Kay Sievers0db37972005-08-07 20:24:35 +02006788 git_footer_html();
6789}
6790
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006791# used both for single remote view and for list of all the remotes
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01006792sub git_remotes {
6793 gitweb_check_feature('remote_heads')
6794 or die_error(403, "Remote heads view is disabled");
6795
6796 my $head = git_get_head_hash($project);
Giuseppe Bilottabb607762010-11-11 13:26:14 +01006797 my $remote = $input_params{'hash'};
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01006798
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006799 my $remotedata = git_get_remotes_list($remote);
6800 die_error(500, "Unable to get remote information") unless defined $remotedata;
Giuseppe Bilottabb607762010-11-11 13:26:14 +01006801
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006802 unless (%$remotedata) {
6803 die_error(404, defined $remote ?
6804 "Remote $remote not found" :
6805 "No remotes found");
Giuseppe Bilottabb607762010-11-11 13:26:14 +01006806 }
6807
6808 git_header_html(undef, undef, -action_extra => $remote);
6809 git_print_page_nav('', '', $head, undef, $head,
6810 format_ref_views($remote ? '' : 'remotes'));
6811
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006812 fill_remote_heads($remotedata);
Giuseppe Bilottabb607762010-11-11 13:26:14 +01006813 if (defined $remote) {
6814 git_print_header_div('remotes', "$remote remote for $project");
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006815 git_remote_block($remote, $remotedata->{$remote}, undef, $head);
Giuseppe Bilottabb607762010-11-11 13:26:14 +01006816 } else {
6817 git_print_header_div('summary', "$project remotes");
Giuseppe Bilotta9d0d42f2010-11-11 13:26:17 +01006818 git_remotes_body($remotedata, undef, $head);
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01006819 }
Giuseppe Bilottabb607762010-11-11 13:26:14 +01006820
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01006821 git_footer_html();
6822}
6823
Luben Tuikov930cf7d2006-07-09 20:18:57 -07006824sub git_blob_plain {
Jakub Narebski7f718e82008-06-03 16:47:10 +02006825 my $type = shift;
Jakub Narebskif2e73302006-08-26 19:14:25 +02006826 my $expires;
Jakub Narebskif2e73302006-08-26 19:14:25 +02006827
Luben Tuikovcff07712006-07-23 13:28:55 -07006828 if (!defined $hash) {
Jakub Narebski5be01bc2006-07-29 22:43:40 +02006829 if (defined $file_name) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006830 my $base = $hash_base || git_get_head_hash($project);
Jakub Narebski5be01bc2006-07-29 22:43:40 +02006831 $hash = git_get_hash_by_path($base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02006832 or die_error(404, "Cannot find file");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02006833 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006834 die_error(400, "No file name defined");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02006835 }
Martin Waitz800764c2006-09-16 23:09:02 +02006836 } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
6837 # blobs defined by non-textual hash id's can be cached
6838 $expires = "+1d";
Jakub Narebski5be01bc2006-07-29 22:43:40 +02006839 }
Martin Waitz800764c2006-09-16 23:09:02 +02006840
Dennis Stosberg25691fb2006-08-28 17:49:58 +02006841 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
Lea Wiemann074afaa2008-06-19 22:03:21 +02006842 or die_error(500, "Open git-cat-file blob '$hash' failed");
Luben Tuikov930cf7d2006-07-09 20:18:57 -07006843
Jakub Narebski7f718e82008-06-03 16:47:10 +02006844 # content-type (can include charset)
6845 $type = blob_contenttype($fd, $file_name, $type);
Luben Tuikov930cf7d2006-07-09 20:18:57 -07006846
Jakub Narebski7f718e82008-06-03 16:47:10 +02006847 # "save as" filename, even when no $file_name is given
Luben Tuikov930cf7d2006-07-09 20:18:57 -07006848 my $save_as = "$hash";
6849 if (defined $file_name) {
6850 $save_as = $file_name;
6851 } elsif ($type =~ m/^text\//) {
6852 $save_as .= '.txt';
6853 }
6854
Matt McCutchen7e1100e2009-02-07 19:00:09 -05006855 # With XSS prevention on, blobs of all types except a few known safe
6856 # ones are served with "Content-Disposition: attachment" to make sure
6857 # they don't run in our security domain. For certain image types,
6858 # blob view writes an <img> tag referring to blob_plain view, and we
6859 # want to be sure not to break that by serving the image as an
6860 # attachment (though Firefox 3 doesn't seem to care).
6861 my $sandbox = $prevent_xss &&
Jakub Narebski86afbd02011-06-30 11:39:20 +02006862 $type !~ m!^(?:text/[a-z]+|image/(?:gif|png|jpeg))(?:[ ;]|$)!;
6863
6864 # serve text/* as text/plain
6865 if ($prevent_xss &&
Jakub Narebskie8c35312011-06-30 11:39:21 +02006866 ($type =~ m!^text/[a-z]+\b(.*)$! ||
6867 ($type =~ m!^[a-z]+/[a-z]\+xml\b(.*)$! && -T $fd))) {
Jakub Narebski86afbd02011-06-30 11:39:20 +02006868 my $rest = $1;
6869 $rest = defined $rest ? $rest : '';
6870 $type = "text/plain$rest";
6871 }
Matt McCutchen7e1100e2009-02-07 19:00:09 -05006872
Jakub Narebskif2e73302006-08-26 19:14:25 +02006873 print $cgi->header(
Jakub Narebski7f718e82008-06-03 16:47:10 +02006874 -type => $type,
6875 -expires => $expires,
Matt McCutchen7e1100e2009-02-07 19:00:09 -05006876 -content_disposition =>
6877 ($sandbox ? 'attachment' : 'inline')
6878 . '; filename="' . $save_as . '"');
Jakub Narebski34122b52009-05-11 03:29:40 +02006879 local $/ = undef;
Luben Tuikov930cf7d2006-07-09 20:18:57 -07006880 binmode STDOUT, ':raw';
6881 print <$fd>;
6882 binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
Luben Tuikov930cf7d2006-07-09 20:18:57 -07006883 close $fd;
6884}
6885
Kay Sievers09bd7892005-08-07 20:21:23 +02006886sub git_blob {
Jakub Narebskif2e73302006-08-26 19:14:25 +02006887 my $expires;
Jakub Narebskif2e73302006-08-26 19:14:25 +02006888
Luben Tuikovcff07712006-07-23 13:28:55 -07006889 if (!defined $hash) {
Jakub Narebski5be01bc2006-07-29 22:43:40 +02006890 if (defined $file_name) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006891 my $base = $hash_base || git_get_head_hash($project);
Jakub Narebski5be01bc2006-07-29 22:43:40 +02006892 $hash = git_get_hash_by_path($base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02006893 or die_error(404, "Cannot find file");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02006894 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006895 die_error(400, "No file name defined");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02006896 }
Martin Waitz800764c2006-09-16 23:09:02 +02006897 } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
6898 # blobs defined by non-textual hash id's can be cached
6899 $expires = "+1d";
Jakub Narebski5be01bc2006-07-29 22:43:40 +02006900 }
Martin Waitz800764c2006-09-16 23:09:02 +02006901
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006902 my $have_blame = gitweb_check_feature('blame');
Dennis Stosberg25691fb2006-08-28 17:49:58 +02006903 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
Lea Wiemann074afaa2008-06-19 22:03:21 +02006904 or die_error(500, "Couldn't cat $file_name, $hash");
Jakub Narebski847e01f2006-08-14 02:05:47 +02006905 my $mimetype = blob_mimetype($fd, $file_name);
Johannes Schindelinb331fe52010-04-27 21:34:44 +02006906 # use 'blob_plain' (aka 'raw') view for files that cannot be displayed
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01006907 if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)! && -B $fd) {
Luben Tuikov930cf7d2006-07-09 20:18:57 -07006908 close $fd;
6909 return git_blob_plain($mimetype);
6910 }
Jakub Narebski5a4cf332006-12-04 23:47:22 +01006911 # we can have blame only for text/* mimetype
6912 $have_blame &&= ($mimetype =~ m!^text/!);
6913
Jakub Narebski592ea412010-04-27 21:34:45 +02006914 my $highlight = gitweb_check_feature('highlight');
6915 my $syntax = guess_file_syntax($highlight, $mimetype, $file_name);
6916 $fd = run_highlighter($fd, $highlight, $syntax)
6917 if $syntax;
Johannes Schindelinb331fe52010-04-27 21:34:44 +02006918
Jakub Narebskif2e73302006-08-26 19:14:25 +02006919 git_header_html(undef, $expires);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02006920 my $formats_nav = '';
Jakub Narebski847e01f2006-08-14 02:05:47 +02006921 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
Kay Sievers93129442005-10-17 03:27:54 +02006922 if (defined $file_name) {
Florian Forster5996ca02006-06-12 10:31:57 +02006923 if ($have_blame) {
Jakub Narebski952c65f2006-08-22 16:52:50 +02006924 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01006925 $cgi->a({-href => href(action=>"blame", -replay=>1)},
Jakub Narebski952c65f2006-08-22 16:52:50 +02006926 "blame") .
6927 " | ";
Florian Forster5996ca02006-06-12 10:31:57 +02006928 }
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02006929 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01006930 $cgi->a({-href => href(action=>"history", -replay=>1)},
Petr Baudiscae18622006-09-22 03:19:41 +02006931 "history") .
6932 " | " .
Jakub Narebskia3823e52007-11-01 13:06:29 +01006933 $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
Petr Baudis35329cc2006-09-22 03:19:50 +02006934 "raw") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02006935 " | " .
6936 $cgi->a({-href => href(action=>"blob",
6937 hash_base=>"HEAD", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02006938 "HEAD");
Kay Sievers93129442005-10-17 03:27:54 +02006939 } else {
Jakub Narebski952c65f2006-08-22 16:52:50 +02006940 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01006941 $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
6942 "raw");
Kay Sievers93129442005-10-17 03:27:54 +02006943 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02006944 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
6945 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
Kay Sievers09bd7892005-08-07 20:21:23 +02006946 } else {
6947 print "<div class=\"page_nav\">\n" .
6948 "<br/><br/></div>\n" .
Jakub Narebski3017ed62010-12-15 00:34:01 +01006949 "<div class=\"title\">".esc_html($hash)."</div>\n";
Kay Sievers09bd7892005-08-07 20:21:23 +02006950 }
Luben Tuikov59fb1c92006-08-17 10:39:29 -07006951 git_print_page_path($file_name, "blob", $hash_base);
Kay Sieversc07ad4b2005-08-07 20:22:44 +02006952 print "<div class=\"page_body\">\n";
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01006953 if ($mimetype =~ m!^image/!) {
Jakub Narebski3017ed62010-12-15 00:34:01 +01006954 print qq!<img type="!.esc_attr($mimetype).qq!"!;
Jakub Narebski5a4cf332006-12-04 23:47:22 +01006955 if ($file_name) {
Jakub Narebski3017ed62010-12-15 00:34:01 +01006956 print qq! alt="!.esc_attr($file_name).qq!" title="!.esc_attr($file_name).qq!"!;
Jakub Narebski5a4cf332006-12-04 23:47:22 +01006957 }
6958 print qq! src="! .
6959 href(action=>"blob_plain", hash=>$hash,
6960 hash_base=>$hash_base, file_name=>$file_name) .
6961 qq!" />\n!;
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01006962 } else {
6963 my $nr;
6964 while (my $line = <$fd>) {
6965 chomp $line;
6966 $nr++;
6967 $line = untabify($line);
Jakub Narebski592ea412010-04-27 21:34:45 +02006968 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 +02006969 $nr, esc_attr(href(-replay => 1)), $nr, $nr,
6970 $syntax ? sanitize($line) : esc_html($line, -nbsp=>1);
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01006971 }
Kay Sievers161332a2005-08-07 19:49:46 +02006972 }
Jakub Narebski952c65f2006-08-22 16:52:50 +02006973 close $fd
6974 or print "Reading blob failed.\n";
Kay Sieversfbb592a2005-08-07 20:12:11 +02006975 print "</div>";
Kay Sievers12a88f22005-08-07 20:02:47 +02006976 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02006977}
6978
6979sub git_tree {
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07006980 if (!defined $hash_base) {
6981 $hash_base = "HEAD";
6982 }
Kay Sieversb87d78d2005-08-07 20:21:04 +02006983 if (!defined $hash) {
Kay Sievers09bd7892005-08-07 20:21:23 +02006984 if (defined $file_name) {
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07006985 $hash = git_get_hash_by_path($hash_base, $file_name, "tree");
6986 } else {
6987 $hash = $hash_base;
Kay Sievers10dba282005-08-07 20:25:27 +02006988 }
Kay Sieverse925f382005-08-07 20:23:35 +02006989 }
Jakub Narebski2d7a3532008-10-02 16:50:04 +02006990 die_error(404, "No such tree") unless defined($hash);
Jakub Narebski34122b52009-05-11 03:29:40 +02006991
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02006992 my $show_sizes = gitweb_check_feature('show-sizes');
6993 my $have_blame = gitweb_check_feature('blame');
6994
Jakub Narebski34122b52009-05-11 03:29:40 +02006995 my @entries = ();
6996 {
6997 local $/ = "\0";
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02006998 open my $fd, "-|", git_cmd(), "ls-tree", '-z',
6999 ($show_sizes ? '-l' : ()), @extra_options, $hash
Jakub Narebski34122b52009-05-11 03:29:40 +02007000 or die_error(500, "Open git-ls-tree failed");
7001 @entries = map { chomp; $_ } <$fd>;
7002 close $fd
7003 or die_error(404, "Reading tree failed");
7004 }
Kay Sieversd63577d2005-08-07 20:18:13 +02007005
Jakub Narebski847e01f2006-08-14 02:05:47 +02007006 my $refs = git_get_references();
7007 my $ref = format_ref_marker($refs, $hash_base);
Kay Sievers12a88f22005-08-07 20:02:47 +02007008 git_header_html();
Jakub Narebski300454f2006-10-21 17:53:09 +02007009 my $basedir = '';
Jakub Narebski847e01f2006-08-14 02:05:47 +02007010 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
Petr Baudiscae18622006-09-22 03:19:41 +02007011 my @views_nav = ();
7012 if (defined $file_name) {
7013 push @views_nav,
Jakub Narebskia3823e52007-11-01 13:06:29 +01007014 $cgi->a({-href => href(action=>"history", -replay=>1)},
Petr Baudiscae18622006-09-22 03:19:41 +02007015 "history"),
7016 $cgi->a({-href => href(action=>"tree",
7017 hash_base=>"HEAD", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02007018 "HEAD"),
Petr Baudiscae18622006-09-22 03:19:41 +02007019 }
Matt McCutchena3c8ab32007-07-22 01:30:27 +02007020 my $snapshot_links = format_snapshot_links($hash);
7021 if (defined $snapshot_links) {
Petr Baudiscae18622006-09-22 03:19:41 +02007022 # FIXME: Should be available when we have no hash base as well.
Matt McCutchena3c8ab32007-07-22 01:30:27 +02007023 push @views_nav, $snapshot_links;
Petr Baudiscae18622006-09-22 03:19:41 +02007024 }
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02007025 git_print_page_nav('tree','', $hash_base, undef, undef,
7026 join(' | ', @views_nav));
Jakub Narebski847e01f2006-08-14 02:05:47 +02007027 git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
Kay Sieversd63577d2005-08-07 20:18:13 +02007028 } else {
Jakub Narebskifa702002006-08-31 00:35:07 +02007029 undef $hash_base;
Kay Sieversd63577d2005-08-07 20:18:13 +02007030 print "<div class=\"page_nav\">\n";
7031 print "<br/><br/></div>\n";
Jakub Narebski3017ed62010-12-15 00:34:01 +01007032 print "<div class=\"title\">".esc_html($hash)."</div>\n";
Kay Sieversd63577d2005-08-07 20:18:13 +02007033 }
Kay Sievers09bd7892005-08-07 20:21:23 +02007034 if (defined $file_name) {
Jakub Narebski300454f2006-10-21 17:53:09 +02007035 $basedir = $file_name;
7036 if ($basedir ne '' && substr($basedir, -1) ne '/') {
7037 $basedir .= '/';
7038 }
Jakub Narebski2d7a3532008-10-02 16:50:04 +02007039 git_print_page_path($file_name, 'tree', $hash_base);
Kay Sievers09bd7892005-08-07 20:21:23 +02007040 }
Kay Sieversfbb592a2005-08-07 20:12:11 +02007041 print "<div class=\"page_body\">\n";
Jakub Narebski591ebf62007-11-19 14:16:11 +01007042 print "<table class=\"tree\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07007043 my $alternate = 1;
Jakub Narebskib6b7fc72006-10-21 17:54:44 +02007044 # '..' (top directory) link if possible
7045 if (defined $hash_base &&
7046 defined $file_name && $file_name =~ m![^/]+$!) {
7047 if ($alternate) {
7048 print "<tr class=\"dark\">\n";
7049 } else {
7050 print "<tr class=\"light\">\n";
7051 }
7052 $alternate ^= 1;
7053
7054 my $up = $file_name;
7055 $up =~ s!/?[^/]+$!!;
7056 undef $up unless $up;
7057 # based on git_print_tree_entry
7058 print '<td class="mode">' . mode_str('040000') . "</td>\n";
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02007059 print '<td class="size">&nbsp;</td>'."\n" if $show_sizes;
Jakub Narebskib6b7fc72006-10-21 17:54:44 +02007060 print '<td class="list">';
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02007061 print $cgi->a({-href => href(action=>"tree",
7062 hash_base=>$hash_base,
Jakub Narebskib6b7fc72006-10-21 17:54:44 +02007063 file_name=>$up)},
7064 "..");
7065 print "</td>\n";
7066 print "<td class=\"link\"></td>\n";
7067
7068 print "</tr>\n";
7069 }
Kay Sievers161332a2005-08-07 19:49:46 +02007070 foreach my $line (@entries) {
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02007071 my %t = parse_ls_tree_line($line, -z => 1, -l => $show_sizes);
Jakub Narebskicb849b42006-08-31 00:32:15 +02007072
Kay Sieversbddec012005-08-07 20:25:42 +02007073 if ($alternate) {
Kay Sieversc994d622005-08-07 20:27:18 +02007074 print "<tr class=\"dark\">\n";
Kay Sieversbddec012005-08-07 20:25:42 +02007075 } else {
Kay Sieversc994d622005-08-07 20:27:18 +02007076 print "<tr class=\"light\">\n";
Kay Sieversbddec012005-08-07 20:25:42 +02007077 }
7078 $alternate ^= 1;
Jakub Narebskicb849b42006-08-31 00:32:15 +02007079
Jakub Narebski300454f2006-10-21 17:53:09 +02007080 git_print_tree_entry(\%t, $basedir, $hash_base, $have_blame);
Jakub Narebskifa702002006-08-31 00:35:07 +02007081
Kay Sievers42f7eb92005-08-07 20:21:46 +02007082 print "</tr>\n";
Kay Sievers161332a2005-08-07 19:49:46 +02007083 }
Kay Sievers42f7eb92005-08-07 20:21:46 +02007084 print "</table>\n" .
7085 "</div>";
Kay Sievers12a88f22005-08-07 20:02:47 +02007086 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02007087}
7088
Mark Radab6292752009-11-07 16:13:29 +01007089sub snapshot_name {
7090 my ($project, $hash) = @_;
7091
7092 # path/to/project.git -> project
7093 # path/to/project/.git -> project
7094 my $name = to_utf8($project);
7095 $name =~ s,([^/])/*\.git$,$1,;
7096 $name = basename($name);
7097 # sanitize name
7098 $name =~ s/[[:cntrl:]]/?/g;
7099
7100 my $ver = $hash;
7101 if ($hash =~ /^[0-9a-fA-F]+$/) {
7102 # shorten SHA-1 hash
7103 my $full_hash = git_get_full_hash($project, $hash);
7104 if ($full_hash =~ /^$hash/ && length($hash) > 7) {
7105 $ver = git_get_short_hash($project, $hash);
7106 }
7107 } elsif ($hash =~ m!^refs/tags/(.*)$!) {
7108 # tags don't need shortened SHA-1 hash
7109 $ver = $1;
7110 } else {
7111 # branches and other need shortened SHA-1 hash
7112 if ($hash =~ m!^refs/(?:heads|remotes)/(.*)$!) {
7113 $ver = $1;
7114 }
7115 $ver .= '-' . git_get_short_hash($project, $hash);
7116 }
7117 # in case of hierarchical branch names
7118 $ver =~ s!/!.!g;
7119
7120 # name = project-version_string
7121 $name = "$name-$ver";
7122
7123 return wantarray ? ($name, $name) : $name;
7124}
7125
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05307126sub git_snapshot {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02007127 my $format = $input_params{'snapshot_format'};
Giuseppe Bilotta5e166842008-11-02 10:21:37 +01007128 if (!@snapshot_fmts) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007129 die_error(403, "Snapshots not allowed");
Jakub Narebski3473e7d2007-07-25 01:19:58 +02007130 }
7131 # default to first supported snapshot format
Giuseppe Bilotta5e166842008-11-02 10:21:37 +01007132 $format ||= $snapshot_fmts[0];
Jakub Narebski3473e7d2007-07-25 01:19:58 +02007133 if ($format !~ m/^[a-z0-9]+$/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007134 die_error(400, "Invalid snapshot format parameter");
Jakub Narebski3473e7d2007-07-25 01:19:58 +02007135 } elsif (!exists($known_snapshot_formats{$format})) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007136 die_error(400, "Unknown snapshot format");
Mark A Rada1bfd3632009-08-06 10:25:39 -04007137 } elsif ($known_snapshot_formats{$format}{'disabled'}) {
7138 die_error(403, "Snapshot format not allowed");
Mark Rada34b31a82009-08-25 00:59:48 -04007139 } elsif (!grep($_ eq $format, @snapshot_fmts)) {
7140 die_error(403, "Unsupported snapshot format");
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05307141 }
7142
Mark Radafdb0c362009-09-26 13:46:08 -04007143 my $type = git_get_type("$hash^{}");
7144 if (!$type) {
7145 die_error(404, 'Object does not exist');
7146 } elsif ($type eq 'blob') {
7147 die_error(400, 'Object is not a tree-ish');
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05307148 }
7149
Mark Radab6292752009-11-07 16:13:29 +01007150 my ($name, $prefix) = snapshot_name($project, $hash);
7151 my $filename = "$name$known_snapshot_formats{$format}{'suffix'}";
7152 my $cmd = quote_command(
Lea Wiemann516381d2008-06-17 23:46:35 +02007153 git_cmd(), 'archive',
7154 "--format=$known_snapshot_formats{$format}{'format'}",
Mark Radab6292752009-11-07 16:13:29 +01007155 "--prefix=$prefix/", $hash);
Matt McCutchena3c8ab32007-07-22 01:30:27 +02007156 if (exists $known_snapshot_formats{$format}{'compressor'}) {
Lea Wiemann516381d2008-06-17 23:46:35 +02007157 $cmd .= ' | ' . quote_command(@{$known_snapshot_formats{$format}{'compressor'}});
Mark Levedahl072570e2007-05-20 11:46:46 -04007158 }
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05307159
Mark Radab6292752009-11-07 16:13:29 +01007160 $filename =~ s/(["\\])/\\$1/g;
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02007161 print $cgi->header(
Matt McCutchena3c8ab32007-07-22 01:30:27 +02007162 -type => $known_snapshot_formats{$format}{'type'},
Mark Radab6292752009-11-07 16:13:29 +01007163 -content_disposition => 'inline; filename="' . $filename . '"',
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02007164 -status => '200 OK');
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05307165
Mark Levedahl072570e2007-05-20 11:46:46 -04007166 open my $fd, "-|", $cmd
Lea Wiemann074afaa2008-06-19 22:03:21 +02007167 or die_error(500, "Execute git-archive failed");
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05307168 binmode STDOUT, ':raw';
7169 print <$fd>;
7170 binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
7171 close $fd;
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05307172}
7173
Jakub Narebski15f0b112009-11-13 02:02:13 +01007174sub git_log_generic {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007175 my ($fmt_name, $body_subr, $base, $parent, $file_name, $file_hash) = @_;
Jakub Narebski15f0b112009-11-13 02:02:13 +01007176
Jakub Narebski847e01f2006-08-14 02:05:47 +02007177 my $head = git_get_head_hash($project);
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007178 if (!defined $base) {
7179 $base = $head;
Kay Sievers0db37972005-08-07 20:24:35 +02007180 }
Kay Sieversea4a6df2005-08-07 20:26:49 +02007181 if (!defined $page) {
7182 $page = 0;
Kay Sieversb87d78d2005-08-07 20:21:04 +02007183 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02007184 my $refs = git_get_references();
Kay Sieversea4a6df2005-08-07 20:26:49 +02007185
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007186 my $commit_hash = $base;
7187 if (defined $parent) {
7188 $commit_hash = "$parent..$base";
Jakub Narebski15f0b112009-11-13 02:02:13 +01007189 }
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007190 my @commitlist =
7191 parse_commits($commit_hash, 101, (100 * $page),
7192 defined $file_name ? ($file_name, "--full-history") : ());
Kay Sieversea4a6df2005-08-07 20:26:49 +02007193
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007194 my $ftype;
7195 if (!defined $file_hash && defined $file_name) {
7196 # some commits could have deleted file in question,
7197 # and not have it in tree, but one of them has to have it
7198 for (my $i = 0; $i < @commitlist; $i++) {
7199 $file_hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name);
7200 last if defined $file_hash;
7201 }
7202 }
7203 if (defined $file_hash) {
7204 $ftype = git_get_type($file_hash);
7205 }
7206 if (defined $file_name && !defined $ftype) {
7207 die_error(500, "Unknown type of object");
7208 }
7209 my %co;
7210 if (defined $file_name) {
7211 %co = parse_commit($base)
7212 or die_error(404, "Unknown commit object");
7213 }
7214
7215
7216 my $paging_nav = format_paging_nav($fmt_name, $page, $#commitlist >= 100);
Jakub Narebski15f0b112009-11-13 02:02:13 +01007217 my $next_link = '';
Jakub Narebski42671ca2009-11-13 02:02:12 +01007218 if ($#commitlist >= 100) {
7219 $next_link =
7220 $cgi->a({-href => href(-replay=>1, page=>$page+1),
7221 -accesskey => "n", -title => "Alt-n"}, "next");
7222 }
Jakub Narebski15f0b112009-11-13 02:02:13 +01007223 my $patch_max = gitweb_get_feature('patches');
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007224 if ($patch_max && !defined $file_name) {
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01007225 if ($patch_max < 0 || @commitlist <= $patch_max) {
7226 $paging_nav .= " &sdot; " .
7227 $cgi->a({-href => href(action=>"patches", -replay=>1)},
7228 "patches");
7229 }
7230 }
7231
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02007232 git_header_html();
Jakub Narebski15f0b112009-11-13 02:02:13 +01007233 git_print_page_nav($fmt_name,'', $hash,$hash,$hash, $paging_nav);
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007234 if (defined $file_name) {
7235 git_print_header_div('commit', esc_html($co{'title'}), $base);
7236 } else {
7237 git_print_header_div('summary', $project)
7238 }
7239 git_print_page_path($file_name, $ftype, $hash_base)
7240 if (defined $file_name);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02007241
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007242 $body_subr->(\@commitlist, 0, 99, $refs, $next_link,
7243 $file_name, $file_hash, $ftype);
Jakub Narebski42671ca2009-11-13 02:02:12 +01007244
Kay Sievers034df392005-08-07 20:20:07 +02007245 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02007246}
7247
Jakub Narebski15f0b112009-11-13 02:02:13 +01007248sub git_log {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007249 git_log_generic('log', \&git_log_body,
7250 $hash, $hash_parent);
Jakub Narebski15f0b112009-11-13 02:02:13 +01007251}
7252
Kay Sievers09bd7892005-08-07 20:21:23 +02007253sub git_commit {
Jakub Narebski9954f772006-11-18 23:35:41 +01007254 $hash ||= $hash_base || "HEAD";
Lea Wiemann074afaa2008-06-19 22:03:21 +02007255 my %co = parse_commit($hash)
7256 or die_error(404, "Unknown commit object");
Kay Sievers161332a2005-08-07 19:49:46 +02007257
Jakub Narebskic9d193d2006-12-15 21:57:16 +01007258 my $parent = $co{'parent'};
7259 my $parents = $co{'parents'}; # listref
7260
7261 # we need to prepare $formats_nav before any parameter munging
7262 my $formats_nav;
7263 if (!defined $parent) {
7264 # --root commitdiff
7265 $formats_nav .= '(initial)';
7266 } elsif (@$parents == 1) {
7267 # single parent commit
7268 $formats_nav .=
7269 '(parent: ' .
7270 $cgi->a({-href => href(action=>"commit",
7271 hash=>$parent)},
7272 esc_html(substr($parent, 0, 7))) .
7273 ')';
7274 } else {
7275 # merge commit
7276 $formats_nav .=
7277 '(merge: ' .
7278 join(' ', map {
Jakub Narebskif9308a12007-03-23 21:04:31 +01007279 $cgi->a({-href => href(action=>"commit",
Jakub Narebskic9d193d2006-12-15 21:57:16 +01007280 hash=>$_)},
7281 esc_html(substr($_, 0, 7)));
7282 } @$parents ) .
7283 ')';
7284 }
Jakub Narebski1655c982009-10-09 14:26:44 +02007285 if (gitweb_check_feature('patches') && @$parents <= 1) {
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01007286 $formats_nav .= " | " .
7287 $cgi->a({-href => href(action=>"patch", -replay=>1)},
7288 "patch");
7289 }
Jakub Narebskic9d193d2006-12-15 21:57:16 +01007290
Kay Sieversd8a20ba2005-08-07 20:28:53 +02007291 if (!defined $parent) {
Jakub Narebskib9182982006-07-30 18:28:34 -07007292 $parent = "--root";
Kay Sievers6191f8e2005-08-07 20:19:56 +02007293 }
Jakub Narebski549ab4a2006-12-15 17:53:45 +01007294 my @difftree;
Jakub Narebski208ecb22007-05-07 01:10:08 +02007295 open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
7296 @diff_opts,
7297 (@$parents <= 1 ? $parent : '-c'),
7298 $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02007299 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski208ecb22007-05-07 01:10:08 +02007300 @difftree = map { chomp; $_ } <$fd>;
Lea Wiemann074afaa2008-06-19 22:03:21 +02007301 close $fd or die_error(404, "Reading git-diff-tree failed");
Kay Sievers11044292005-10-19 03:18:45 +02007302
7303 # non-textual hash id's can be cached
7304 my $expires;
7305 if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
7306 $expires = "+1d";
7307 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02007308 my $refs = git_get_references();
7309 my $ref = format_ref_marker($refs, $co{'id'});
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05307310
Jakub Narebski594e2122006-07-31 02:21:52 +02007311 git_header_html(undef, $expires);
Petr Baudisa1441542006-10-06 18:59:33 +02007312 git_print_page_nav('commit', '',
Jakub Narebski952c65f2006-08-22 16:52:50 +02007313 $hash, $co{'tree'}, $hash,
Jakub Narebskic9d193d2006-12-15 21:57:16 +01007314 $formats_nav);
Luben Tuikov4f7b34c2006-07-23 13:36:32 -07007315
Kay Sieversb87d78d2005-08-07 20:21:04 +02007316 if (defined $co{'parent'}) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02007317 git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);
Kay Sieversb87d78d2005-08-07 20:21:04 +02007318 } else {
Jakub Narebski847e01f2006-08-14 02:05:47 +02007319 git_print_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash);
Kay Sieversb87d78d2005-08-07 20:21:04 +02007320 }
Kay Sievers6191f8e2005-08-07 20:19:56 +02007321 print "<div class=\"title_text\">\n" .
Jakub Narebski591ebf62007-11-19 14:16:11 +01007322 "<table class=\"object_header\">\n";
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02007323 git_print_authorship_rows(\%co);
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00007324 print "<tr><td>commit</td><td class=\"sha1\">$co{'id'}</td></tr>\n";
Kay Sieversbddec012005-08-07 20:25:42 +02007325 print "<tr>" .
7326 "<td>tree</td>" .
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00007327 "<td class=\"sha1\">" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02007328 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash),
7329 class => "list"}, $co{'tree'}) .
Kay Sievers19806692005-08-07 20:26:27 +02007330 "</td>" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02007331 "<td class=\"link\">" .
7332 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)},
7333 "tree");
Matt McCutchena3c8ab32007-07-22 01:30:27 +02007334 my $snapshot_links = format_snapshot_links($hash);
7335 if (defined $snapshot_links) {
7336 print " | " . $snapshot_links;
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05307337 }
7338 print "</td>" .
Kay Sieversbddec012005-08-07 20:25:42 +02007339 "</tr>\n";
Jakub Narebski549ab4a2006-12-15 17:53:45 +01007340
Kay Sievers3e029292005-08-07 20:05:15 +02007341 foreach my $par (@$parents) {
Kay Sieversbddec012005-08-07 20:25:42 +02007342 print "<tr>" .
7343 "<td>parent</td>" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02007344 "<td class=\"sha1\">" .
7345 $cgi->a({-href => href(action=>"commit", hash=>$par),
7346 class => "list"}, $par) .
7347 "</td>" .
Kay Sieversbddec012005-08-07 20:25:42 +02007348 "<td class=\"link\">" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02007349 $cgi->a({-href => href(action=>"commit", hash=>$par)}, "commit") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02007350 " | " .
Jakub Narebskif2e60942006-09-03 23:43:03 +02007351 $cgi->a({-href => href(action=>"commitdiff", hash=>$hash, hash_parent=>$par)}, "diff") .
Kay Sieversbddec012005-08-07 20:25:42 +02007352 "</td>" .
7353 "</tr>\n";
Kay Sievers3e029292005-08-07 20:05:15 +02007354 }
Jakub Narebski7a9b4c52006-06-21 09:48:02 +02007355 print "</table>".
Kay Sieversb87d78d2005-08-07 20:21:04 +02007356 "</div>\n";
Jakub Narebskid16d0932006-08-17 11:21:23 +02007357
Kay Sieversfbb592a2005-08-07 20:12:11 +02007358 print "<div class=\"page_body\">\n";
Jakub Narebskid16d0932006-08-17 11:21:23 +02007359 git_print_log($co{'comment'});
Kay Sievers927dcec2005-08-07 20:18:44 +02007360 print "</div>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02007361
Jakub Narebski208ecb22007-05-07 01:10:08 +02007362 git_difftree_body(\@difftree, $hash, @$parents);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02007363
Kay Sievers12a88f22005-08-07 20:02:47 +02007364 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02007365}
7366
Jakub Narebskica946012006-12-10 13:25:47 +01007367sub git_object {
7368 # object is defined by:
7369 # - hash or hash_base alone
7370 # - hash_base and file_name
7371 my $type;
7372
7373 # - hash or hash_base alone
7374 if ($hash || ($hash_base && !defined $file_name)) {
7375 my $object_id = $hash || $hash_base;
7376
Lea Wiemann516381d2008-06-17 23:46:35 +02007377 open my $fd, "-|", quote_command(
7378 git_cmd(), 'cat-file', '-t', $object_id) . ' 2> /dev/null'
Lea Wiemann074afaa2008-06-19 22:03:21 +02007379 or die_error(404, "Object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01007380 $type = <$fd>;
7381 chomp $type;
7382 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02007383 or die_error(404, "Object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01007384
7385 # - hash_base and file_name
7386 } elsif ($hash_base && defined $file_name) {
7387 $file_name =~ s,/+$,,;
7388
7389 system(git_cmd(), "cat-file", '-e', $hash_base) == 0
Lea Wiemann074afaa2008-06-19 22:03:21 +02007390 or die_error(404, "Base object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01007391
7392 # here errors should not hapen
7393 open my $fd, "-|", git_cmd(), "ls-tree", $hash_base, "--", $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02007394 or die_error(500, "Open git-ls-tree failed");
Jakub Narebskica946012006-12-10 13:25:47 +01007395 my $line = <$fd>;
7396 close $fd;
7397
7398 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
7399 unless ($line && $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007400 die_error(404, "File or directory for given base does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01007401 }
7402 $type = $2;
7403 $hash = $3;
7404 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007405 die_error(400, "Not enough information to find object");
Jakub Narebskica946012006-12-10 13:25:47 +01007406 }
7407
7408 print $cgi->redirect(-uri => href(action=>$type, -full=>1,
7409 hash=>$hash, hash_base=>$hash_base,
7410 file_name=>$file_name),
7411 -status => '302 Found');
7412}
7413
Kay Sievers09bd7892005-08-07 20:21:23 +02007414sub git_blobdiff {
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007415 my $format = shift || 'html';
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01007416 my $diff_style = $input_params{'diff_style'} || 'inline';
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007417
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007418 my $fd;
7419 my @difftree;
7420 my %diffinfo;
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007421 my $expires;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007422
7423 # preparing $fd and %diffinfo for git_patchset_body
7424 # new style URI
7425 if (defined $hash_base && defined $hash_parent_base) {
7426 if (defined $file_name) {
7427 # read raw output
Jakub Narebski45bd0c82006-10-30 22:29:06 +01007428 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
7429 $hash_parent_base, $hash_base,
Jakub Narebski5ae917a2007-03-30 23:41:26 +02007430 "--", (defined $file_parent ? $file_parent : ()), $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02007431 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007432 @difftree = map { chomp; $_ } <$fd>;
7433 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02007434 or die_error(404, "Reading git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007435 @difftree
Lea Wiemann074afaa2008-06-19 22:03:21 +02007436 or die_error(404, "Blob diff not found");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007437
Jakub Narebski0aea3372006-08-27 23:45:26 +02007438 } elsif (defined $hash &&
7439 $hash =~ /[0-9a-fA-F]{40}/) {
7440 # try to find filename from $hash
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007441
7442 # read filtered raw output
Jakub Narebski45bd0c82006-10-30 22:29:06 +01007443 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
7444 $hash_parent_base, $hash_base, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02007445 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007446 @difftree =
7447 # ':100644 100644 03b21826... 3b93d5e7... M ls-files.c'
7448 # $hash == to_id
7449 grep { /^:[0-7]{6} [0-7]{6} [0-9a-fA-F]{40} $hash/ }
7450 map { chomp; $_ } <$fd>;
7451 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02007452 or die_error(404, "Reading git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007453 @difftree
Lea Wiemann074afaa2008-06-19 22:03:21 +02007454 or die_error(404, "Blob diff not found");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007455
7456 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007457 die_error(400, "Missing one of the blob diff parameters");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007458 }
7459
7460 if (@difftree > 1) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007461 die_error(400, "Ambiguous blob diff specification");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007462 }
7463
7464 %diffinfo = parse_difftree_raw_line($difftree[0]);
Jakub Narebski9d301452007-11-01 12:38:08 +01007465 $file_parent ||= $diffinfo{'from_file'} || $file_name;
7466 $file_name ||= $diffinfo{'to_file'};
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007467
7468 $hash_parent ||= $diffinfo{'from_id'};
7469 $hash ||= $diffinfo{'to_id'};
7470
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007471 # non-textual hash id's can be cached
7472 if ($hash_base =~ m/^[0-9a-fA-F]{40}$/ &&
7473 $hash_parent_base =~ m/^[0-9a-fA-F]{40}$/) {
7474 $expires = '+1d';
7475 }
7476
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007477 # open patch output
Dennis Stosberg25691fb2006-08-28 17:49:58 +02007478 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebski957d6ea2007-04-05 13:45:41 +02007479 '-p', ($format eq 'html' ? "--full-index" : ()),
7480 $hash_parent_base, $hash_base,
Jakub Narebski5ae917a2007-03-30 23:41:26 +02007481 "--", (defined $file_parent ? $file_parent : ()), $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02007482 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007483 }
7484
Junio C Hamanob54dc9f2008-12-16 19:42:02 -08007485 # old/legacy style URI -- not generated anymore since 1.4.3.
7486 if (!%diffinfo) {
7487 die_error('404 Not Found', "Missing one of the blob diff parameters")
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007488 }
7489
7490 # header
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007491 if ($format eq 'html') {
7492 my $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01007493 $cgi->a({-href => href(action=>"blobdiff_plain", -replay=>1)},
Petr Baudis35329cc2006-09-22 03:19:50 +02007494 "raw");
Kato Kazuyoshi6ae683c2011-10-31 00:36:27 +01007495 $formats_nav .= diff_style_nav($diff_style);
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007496 git_header_html(undef, $expires);
7497 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
7498 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
7499 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
7500 } else {
7501 print "<div class=\"page_nav\"><br/>$formats_nav<br/></div>\n";
Jakub Narebski3017ed62010-12-15 00:34:01 +01007502 print "<div class=\"title\">".esc_html("$hash vs $hash_parent")."</div>\n";
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007503 }
7504 if (defined $file_name) {
7505 git_print_page_path($file_name, "blob", $hash_base);
7506 } else {
7507 print "<div class=\"page_path\"></div>\n";
7508 }
7509
7510 } elsif ($format eq 'plain') {
7511 print $cgi->header(
7512 -type => 'text/plain',
7513 -charset => 'utf-8',
7514 -expires => $expires,
Luben Tuikova2a3bf72006-09-28 16:51:43 -07007515 -content_disposition => 'inline; filename="' . "$file_name" . '.patch"');
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007516
7517 print "X-Git-Url: " . $cgi->self_url() . "\n\n";
7518
Kay Sievers09bd7892005-08-07 20:21:23 +02007519 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007520 die_error(400, "Unknown blobdiff format");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007521 }
7522
7523 # patch
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007524 if ($format eq 'html') {
7525 print "<div class=\"page_body\">\n";
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007526
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01007527 git_patchset_body($fd, $diff_style,
7528 [ \%diffinfo ], $hash_base, $hash_parent_base);
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007529 close $fd;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02007530
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007531 print "</div>\n"; # class="page_body"
7532 git_footer_html();
7533
7534 } else {
7535 while (my $line = <$fd>) {
Jakub Narebski403d0902006-11-08 11:48:56 +01007536 $line =~ s!a/($hash|$hash_parent)!'a/'.esc_path($diffinfo{'from_file'})!eg;
7537 $line =~ s!b/($hash|$hash_parent)!'b/'.esc_path($diffinfo{'to_file'})!eg;
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007538
7539 print $line;
7540
7541 last if $line =~ m!^\+\+\+!;
7542 }
7543 local $/ = undef;
7544 print <$fd>;
7545 close $fd;
7546 }
Kay Sievers09bd7892005-08-07 20:21:23 +02007547}
7548
Kay Sievers19806692005-08-07 20:26:27 +02007549sub git_blobdiff_plain {
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02007550 git_blobdiff('plain');
Kay Sievers19806692005-08-07 20:26:27 +02007551}
7552
Kato Kazuyoshi6ae683c2011-10-31 00:36:27 +01007553# assumes that it is added as later part of already existing navigation,
7554# so it returns "| foo | bar" rather than just "foo | bar"
7555sub diff_style_nav {
7556 my ($diff_style, $is_combined) = @_;
7557 $diff_style ||= 'inline';
7558
7559 return "" if ($is_combined);
7560
7561 my @styles = (inline => 'inline', 'sidebyside' => 'side by side');
7562 my %styles = @styles;
7563 @styles =
7564 @styles[ map { $_ * 2 } 0..$#styles/2 ];
7565
7566 return join '',
7567 map { " | ".$_ }
7568 map {
7569 $_ eq $diff_style ? $styles{$_} :
7570 $cgi->a({-href => href(-replay=>1, diff_style => $_)}, $styles{$_})
7571 } @styles;
7572}
7573
Kay Sievers09bd7892005-08-07 20:21:23 +02007574sub git_commitdiff {
Giuseppe Bilotta20209852008-12-18 08:13:17 +01007575 my %params = @_;
7576 my $format = $params{-format} || 'html';
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01007577 my $diff_style = $input_params{'diff_style'} || 'inline';
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01007578
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01007579 my ($patch_max) = gitweb_get_feature('patches');
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01007580 if ($format eq 'patch') {
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01007581 die_error(403, "Patch view not allowed") unless $patch_max;
7582 }
7583
Jakub Narebski9954f772006-11-18 23:35:41 +01007584 $hash ||= $hash_base || "HEAD";
Lea Wiemann074afaa2008-06-19 22:03:21 +02007585 my %co = parse_commit($hash)
7586 or die_error(404, "Unknown commit object");
Jakub Narebski151602d2006-10-23 00:37:56 +02007587
Jakub Narebskicd030c32007-06-08 13:33:28 +02007588 # choose format for commitdiff for merge
7589 if (! defined $hash_parent && @{$co{'parents'}} > 1) {
7590 $hash_parent = '--cc';
7591 }
7592 # we need to prepare $formats_nav before almost any parameter munging
Jakub Narebski151602d2006-10-23 00:37:56 +02007593 my $formats_nav;
7594 if ($format eq 'html') {
7595 $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01007596 $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
Jakub Narebski151602d2006-10-23 00:37:56 +02007597 "raw");
Jakub Narebski1655c982009-10-09 14:26:44 +02007598 if ($patch_max && @{$co{'parents'}} <= 1) {
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01007599 $formats_nav .= " | " .
7600 $cgi->a({-href => href(action=>"patch", -replay=>1)},
7601 "patch");
7602 }
Kato Kazuyoshi6ae683c2011-10-31 00:36:27 +01007603 $formats_nav .= diff_style_nav($diff_style, @{$co{'parents'}} > 1);
Jakub Narebski151602d2006-10-23 00:37:56 +02007604
Jakub Narebskicd030c32007-06-08 13:33:28 +02007605 if (defined $hash_parent &&
7606 $hash_parent ne '-c' && $hash_parent ne '--cc') {
Jakub Narebski151602d2006-10-23 00:37:56 +02007607 # commitdiff with two commits given
7608 my $hash_parent_short = $hash_parent;
7609 if ($hash_parent =~ m/^[0-9a-fA-F]{40}$/) {
7610 $hash_parent_short = substr($hash_parent, 0, 7);
7611 }
7612 $formats_nav .=
Jakub Narebskiada3e1f2007-06-08 13:26:31 +02007613 ' (from';
7614 for (my $i = 0; $i < @{$co{'parents'}}; $i++) {
7615 if ($co{'parents'}[$i] eq $hash_parent) {
7616 $formats_nav .= ' parent ' . ($i+1);
7617 last;
7618 }
7619 }
7620 $formats_nav .= ': ' .
Jakub Narebskid0e6e292011-10-31 00:36:26 +01007621 $cgi->a({-href => href(-replay=>1,
7622 hash=>$hash_parent, hash_base=>undef)},
Jakub Narebski151602d2006-10-23 00:37:56 +02007623 esc_html($hash_parent_short)) .
7624 ')';
7625 } elsif (!$co{'parent'}) {
7626 # --root commitdiff
7627 $formats_nav .= ' (initial)';
7628 } elsif (scalar @{$co{'parents'}} == 1) {
7629 # single parent commit
7630 $formats_nav .=
7631 ' (parent: ' .
Jakub Narebskid0e6e292011-10-31 00:36:26 +01007632 $cgi->a({-href => href(-replay=>1,
7633 hash=>$co{'parent'}, hash_base=>undef)},
Jakub Narebski151602d2006-10-23 00:37:56 +02007634 esc_html(substr($co{'parent'}, 0, 7))) .
7635 ')';
7636 } else {
7637 # merge commit
Jakub Narebskicd030c32007-06-08 13:33:28 +02007638 if ($hash_parent eq '--cc') {
7639 $formats_nav .= ' | ' .
Jakub Narebskid0e6e292011-10-31 00:36:26 +01007640 $cgi->a({-href => href(-replay=>1,
Jakub Narebskicd030c32007-06-08 13:33:28 +02007641 hash=>$hash, hash_parent=>'-c')},
7642 'combined');
7643 } else { # $hash_parent eq '-c'
7644 $formats_nav .= ' | ' .
Jakub Narebskid0e6e292011-10-31 00:36:26 +01007645 $cgi->a({-href => href(-replay=>1,
Jakub Narebskicd030c32007-06-08 13:33:28 +02007646 hash=>$hash, hash_parent=>'--cc')},
7647 'compact');
7648 }
Jakub Narebski151602d2006-10-23 00:37:56 +02007649 $formats_nav .=
7650 ' (merge: ' .
7651 join(' ', map {
Jakub Narebskid0e6e292011-10-31 00:36:26 +01007652 $cgi->a({-href => href(-replay=>1,
7653 hash=>$_, hash_base=>undef)},
Jakub Narebski151602d2006-10-23 00:37:56 +02007654 esc_html(substr($_, 0, 7)));
7655 } @{$co{'parents'}} ) .
7656 ')';
7657 }
7658 }
7659
Jakub Narebskifb1dde42007-05-07 01:10:07 +02007660 my $hash_parent_param = $hash_parent;
Jakub Narebskicd030c32007-06-08 13:33:28 +02007661 if (!defined $hash_parent_param) {
7662 # --cc for multiple parents, --root for parentless
Jakub Narebskifb1dde42007-05-07 01:10:07 +02007663 $hash_parent_param =
Jakub Narebskicd030c32007-06-08 13:33:28 +02007664 @{$co{'parents'}} > 1 ? '--cc' : $co{'parent'} || '--root';
Kay Sieversbddec012005-08-07 20:25:42 +02007665 }
Jakub Narebskieee08902006-08-24 00:15:14 +02007666
7667 # read commitdiff
7668 my $fd;
7669 my @difftree;
Jakub Narebskieee08902006-08-24 00:15:14 +02007670 if ($format eq 'html') {
Dennis Stosberg25691fb2006-08-28 17:49:58 +02007671 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebski45bd0c82006-10-30 22:29:06 +01007672 "--no-commit-id", "--patch-with-raw", "--full-index",
Jakub Narebskifb1dde42007-05-07 01:10:07 +02007673 $hash_parent_param, $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02007674 or die_error(500, "Open git-diff-tree failed");
Jakub Narebskieee08902006-08-24 00:15:14 +02007675
Jakub Narebski04408c32006-11-18 23:35:38 +01007676 while (my $line = <$fd>) {
7677 chomp $line;
Jakub Narebskieee08902006-08-24 00:15:14 +02007678 # empty line ends raw part of diff-tree output
7679 last unless $line;
Jakub Narebski493e01d2007-05-07 01:10:06 +02007680 push @difftree, scalar parse_difftree_raw_line($line);
Jakub Narebskieee08902006-08-24 00:15:14 +02007681 }
Jakub Narebskieee08902006-08-24 00:15:14 +02007682
Jakub Narebskieee08902006-08-24 00:15:14 +02007683 } elsif ($format eq 'plain') {
Dennis Stosberg25691fb2006-08-28 17:49:58 +02007684 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebskifb1dde42007-05-07 01:10:07 +02007685 '-p', $hash_parent_param, $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02007686 or die_error(500, "Open git-diff-tree failed");
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01007687 } elsif ($format eq 'patch') {
7688 # For commit ranges, we limit the output to the number of
7689 # patches specified in the 'patches' feature.
7690 # For single commits, we limit the output to a single patch,
7691 # diverging from the git-format-patch default.
7692 my @commit_spec = ();
7693 if ($hash_parent) {
7694 if ($patch_max > 0) {
7695 push @commit_spec, "-$patch_max";
7696 }
7697 push @commit_spec, '-n', "$hash_parent..$hash";
7698 } else {
Giuseppe Bilottaa3411f82008-12-18 08:13:18 +01007699 if ($params{-single}) {
7700 push @commit_spec, '-1';
7701 } else {
7702 if ($patch_max > 0) {
7703 push @commit_spec, "-$patch_max";
7704 }
7705 push @commit_spec, "-n";
7706 }
7707 push @commit_spec, '--root', $hash;
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01007708 }
Pavan Kumar Sunkara04794fd2010-05-10 18:41:35 +02007709 open $fd, "-|", git_cmd(), "format-patch", @diff_opts,
7710 '--encoding=utf8', '--stdout', @commit_spec
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01007711 or die_error(500, "Open git-format-patch failed");
Jakub Narebskieee08902006-08-24 00:15:14 +02007712 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007713 die_error(400, "Unknown commitdiff format");
Jakub Narebskieee08902006-08-24 00:15:14 +02007714 }
Kay Sievers4c02e3c2005-08-07 19:52:52 +02007715
Kay Sievers11044292005-10-19 03:18:45 +02007716 # non-textual hash id's can be cached
7717 my $expires;
7718 if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
7719 $expires = "+1d";
7720 }
Kay Sievers09bd7892005-08-07 20:21:23 +02007721
Jakub Narebskieee08902006-08-24 00:15:14 +02007722 # write commit message
7723 if ($format eq 'html') {
7724 my $refs = git_get_references();
7725 my $ref = format_ref_marker($refs, $co{'id'});
Kay Sievers19806692005-08-07 20:26:27 +02007726
Jakub Narebskieee08902006-08-24 00:15:14 +02007727 git_header_html(undef, $expires);
7728 git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
7729 git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
Giuseppe Bilottaf88bafa2009-06-30 00:00:49 +02007730 print "<div class=\"title_text\">\n" .
7731 "<table class=\"object_header\">\n";
7732 git_print_authorship_rows(\%co);
7733 print "</table>".
7734 "</div>\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02007735 print "<div class=\"page_body\">\n";
Jakub Narebski82560982006-10-24 13:55:33 +02007736 if (@{$co{'comment'}} > 1) {
7737 print "<div class=\"log\">\n";
7738 git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1);
7739 print "</div>\n"; # class="log"
7740 }
Kay Sievers1b1cd422005-08-07 20:28:01 +02007741
Jakub Narebskieee08902006-08-24 00:15:14 +02007742 } elsif ($format eq 'plain') {
7743 my $refs = git_get_references("tags");
Jakub Narebskiedf735a2006-08-24 19:45:30 +02007744 my $tagname = git_get_rev_name_tags($hash);
Jakub Narebskieee08902006-08-24 00:15:14 +02007745 my $filename = basename($project) . "-$hash.patch";
7746
7747 print $cgi->header(
7748 -type => 'text/plain',
7749 -charset => 'utf-8',
7750 -expires => $expires,
Luben Tuikova2a3bf72006-09-28 16:51:43 -07007751 -content_disposition => 'inline; filename="' . "$filename" . '"');
Jakub Narebskieee08902006-08-24 00:15:14 +02007752 my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
Yasushi SHOJI77202242008-01-29 21:16:02 +09007753 print "From: " . to_utf8($co{'author'}) . "\n";
7754 print "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n";
7755 print "Subject: " . to_utf8($co{'title'}) . "\n";
7756
Jakub Narebskiedf735a2006-08-24 19:45:30 +02007757 print "X-Git-Tag: $tagname\n" if $tagname;
Jakub Narebskieee08902006-08-24 00:15:14 +02007758 print "X-Git-Url: " . $cgi->self_url() . "\n\n";
Jakub Narebskiedf735a2006-08-24 19:45:30 +02007759
Jakub Narebskieee08902006-08-24 00:15:14 +02007760 foreach my $line (@{$co{'comment'}}) {
Yasushi SHOJI77202242008-01-29 21:16:02 +09007761 print to_utf8($line) . "\n";
Kay Sievers19806692005-08-07 20:26:27 +02007762 }
Jakub Narebskieee08902006-08-24 00:15:14 +02007763 print "---\n\n";
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01007764 } elsif ($format eq 'patch') {
7765 my $filename = basename($project) . "-$hash.patch";
7766
7767 print $cgi->header(
7768 -type => 'text/plain',
7769 -charset => 'utf-8',
7770 -expires => $expires,
7771 -content_disposition => 'inline; filename="' . "$filename" . '"');
Kay Sievers19806692005-08-07 20:26:27 +02007772 }
Jakub Narebskieee08902006-08-24 00:15:14 +02007773
7774 # write patch
7775 if ($format eq 'html') {
Jakub Narebskicd030c32007-06-08 13:33:28 +02007776 my $use_parents = !defined $hash_parent ||
7777 $hash_parent eq '-c' || $hash_parent eq '--cc';
7778 git_difftree_body(\@difftree, $hash,
7779 $use_parents ? @{$co{'parents'}} : $hash_parent);
Jakub Narebskib4657e72006-08-28 14:48:14 +02007780 print "<br/>\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02007781
Kato Kazuyoshi6ba1eb52011-10-31 00:36:22 +01007782 git_patchset_body($fd, $diff_style,
7783 \@difftree, $hash,
Jakub Narebskicd030c32007-06-08 13:33:28 +02007784 $use_parents ? @{$co{'parents'}} : $hash_parent);
Jakub Narebski157e43b2006-08-24 19:34:36 +02007785 close $fd;
Jakub Narebskieee08902006-08-24 00:15:14 +02007786 print "</div>\n"; # class="page_body"
7787 git_footer_html();
7788
7789 } elsif ($format eq 'plain') {
7790 local $/ = undef;
7791 print <$fd>;
7792 close $fd
7793 or print "Reading git-diff-tree failed\n";
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01007794 } elsif ($format eq 'patch') {
7795 local $/ = undef;
7796 print <$fd>;
7797 close $fd
7798 or print "Reading git-format-patch failed\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02007799 }
7800}
7801
7802sub git_commitdiff_plain {
Giuseppe Bilotta20209852008-12-18 08:13:17 +01007803 git_commitdiff(-format => 'plain');
Kay Sievers19806692005-08-07 20:26:27 +02007804}
7805
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01007806# format-patch-style patches
7807sub git_patch {
Jakub Narebski1655c982009-10-09 14:26:44 +02007808 git_commitdiff(-format => 'patch', -single => 1);
Giuseppe Bilottaa3411f82008-12-18 08:13:18 +01007809}
7810
7811sub git_patches {
Giuseppe Bilotta20209852008-12-18 08:13:17 +01007812 git_commitdiff(-format => 'patch');
Kay Sievers09bd7892005-08-07 20:21:23 +02007813}
7814
7815sub git_history {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007816 git_log_generic('history', \&git_history_body,
7817 $hash_base, $hash_parent_base,
7818 $file_name, $hash);
Kay Sievers161332a2005-08-07 19:49:46 +02007819}
Kay Sievers19806692005-08-07 20:26:27 +02007820
7821sub git_search {
Jakub Narebskie0ca3642011-06-22 17:28:52 +02007822 $searchtype ||= 'commit';
7823
7824 # check if appropriate features are enabled
7825 gitweb_check_feature('search')
7826 or die_error(403, "Search is disabled");
7827 if ($searchtype eq 'pickaxe') {
7828 # pickaxe may take all resources of your box and run for several minutes
7829 # with every query - so decide by yourself how public you make this feature
7830 gitweb_check_feature('pickaxe')
7831 or die_error(403, "Pickaxe search is disabled");
7832 }
7833 if ($searchtype eq 'grep') {
7834 # grep search might be potentially CPU-intensive, too
7835 gitweb_check_feature('grep')
7836 or die_error(403, "Grep search is disabled");
7837 }
7838
Kay Sievers19806692005-08-07 20:26:27 +02007839 if (!defined $searchtext) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007840 die_error(400, "Text field is empty");
Kay Sievers19806692005-08-07 20:26:27 +02007841 }
7842 if (!defined $hash) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02007843 $hash = git_get_head_hash($project);
Kay Sievers19806692005-08-07 20:26:27 +02007844 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02007845 my %co = parse_commit($hash);
Kay Sievers19806692005-08-07 20:26:27 +02007846 if (!%co) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007847 die_error(404, "Unknown commit object");
Kay Sievers19806692005-08-07 20:26:27 +02007848 }
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00007849 if (!defined $page) {
7850 $page = 0;
7851 }
Jakub Narebski04f7a942006-09-11 00:29:27 +02007852
Jakub Narebski16f20722011-06-22 17:28:53 +02007853 if ($searchtype eq 'commit' ||
7854 $searchtype eq 'author' ||
7855 $searchtype eq 'committer') {
7856 git_search_message(%co);
7857 } elsif ($searchtype eq 'pickaxe') {
7858 git_search_changes(%co);
7859 } elsif ($searchtype eq 'grep') {
7860 git_search_files(%co);
Jakub Narebski1ae05be2011-06-22 17:28:55 +02007861 } else {
7862 die_error(400, "Unknown search type");
Kay Sieversc994d622005-08-07 20:27:18 +02007863 }
Kay Sievers19806692005-08-07 20:26:27 +02007864}
7865
Petr Baudis88ad7292006-10-24 05:15:46 +02007866sub git_search_help {
7867 git_header_html();
7868 git_print_page_nav('','', $hash,$hash,$hash);
7869 print <<EOT;
Petr Baudis0e559912008-02-26 13:22:08 +01007870<p><strong>Pattern</strong> is by default a normal string that is matched precisely (but without
7871regard to case, except in the case of pickaxe). However, when you check the <em>re</em> checkbox,
7872the pattern entered is recognized as the POSIX extended
7873<a href="http://en.wikipedia.org/wiki/Regular_expression">regular expression</a> (also case
7874insensitive).</p>
Petr Baudis88ad7292006-10-24 05:15:46 +02007875<dl>
7876<dt><b>commit</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01007877<dd>The commit messages and authorship information will be scanned for the given pattern.</dd>
Petr Baudise7738552007-05-17 04:31:12 +02007878EOT
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08007879 my $have_grep = gitweb_check_feature('grep');
Petr Baudise7738552007-05-17 04:31:12 +02007880 if ($have_grep) {
7881 print <<EOT;
7882<dt><b>grep</b></dt>
7883<dd>All files in the currently selected tree (HEAD unless you are explicitly browsing
Petr Baudis0e559912008-02-26 13:22:08 +01007884 a different one) are searched for the given pattern. On large trees, this search can take
7885a while and put some strain on the server, so please use it with some consideration. Note that
7886due to git-grep peculiarity, currently if regexp mode is turned off, the matches are
7887case-sensitive.</dd>
Petr Baudise7738552007-05-17 04:31:12 +02007888EOT
7889 }
7890 print <<EOT;
Petr Baudis88ad7292006-10-24 05:15:46 +02007891<dt><b>author</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01007892<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 +02007893<dt><b>committer</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01007894<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 +02007895EOT
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08007896 my $have_pickaxe = gitweb_check_feature('pickaxe');
Petr Baudis88ad7292006-10-24 05:15:46 +02007897 if ($have_pickaxe) {
7898 print <<EOT;
7899<dt><b>pickaxe</b></dt>
7900<dd>All commits that caused the string to appear or disappear from any file (changes that
7901added, removed or "modified" the string) will be listed. This search can take a while and
Petr Baudis0e559912008-02-26 13:22:08 +01007902takes a lot of strain on the server, so please use it wisely. Note that since you may be
7903interested even in changes just changing the case as well, this search is case sensitive.</dd>
Petr Baudis88ad7292006-10-24 05:15:46 +02007904EOT
7905 }
7906 print "</dl>\n";
7907 git_footer_html();
7908}
7909
Kay Sievers19806692005-08-07 20:26:27 +02007910sub git_shortlog {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01007911 git_log_generic('shortlog', \&git_shortlog_body,
7912 $hash, $hash_parent);
Kay Sievers19806692005-08-07 20:26:27 +02007913}
Jakub Narebski717b8312006-07-31 21:22:15 +02007914
7915## ......................................................................
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01007916## feeds (RSS, Atom; OPML)
Jakub Narebski717b8312006-07-31 21:22:15 +02007917
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01007918sub git_feed {
7919 my $format = shift || 'atom';
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08007920 my $have_blame = gitweb_check_feature('blame');
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01007921
7922 # Atom: http://www.atomenabled.org/developers/syndication/
7923 # RSS: http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
7924 if ($format ne 'rss' && $format ne 'atom') {
Lea Wiemann074afaa2008-06-19 22:03:21 +02007925 die_error(400, "Unknown web feed format");
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01007926 }
7927
7928 # log/feed of current (HEAD) branch, log of given branch, history of file/directory
7929 my $head = $hash || 'HEAD';
Jakub Narebski311e5522008-02-26 13:22:06 +01007930 my @commitlist = parse_commits($head, 150, 0, $file_name);
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01007931
7932 my %latest_commit;
7933 my %latest_date;
7934 my $content_type = "application/$format+xml";
7935 if (defined $cgi->http('HTTP_ACCEPT') &&
7936 $cgi->Accept('text/xml') > $cgi->Accept($content_type)) {
7937 # browser (feed reader) prefers text/xml
7938 $content_type = 'text/xml';
7939 }
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00007940 if (defined($commitlist[0])) {
7941 %latest_commit = %{$commitlist[0]};
Giuseppe Bilottacd956c72009-01-26 12:50:16 +01007942 my $latest_epoch = $latest_commit{'committer_epoch'};
Jakub Narebski6368d9f2011-03-19 23:53:55 +01007943 %latest_date = parse_date($latest_epoch, $latest_commit{'comitter_tz'});
Giuseppe Bilottacd956c72009-01-26 12:50:16 +01007944 my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
7945 if (defined $if_modified) {
7946 my $since;
7947 if (eval { require HTTP::Date; 1; }) {
7948 $since = HTTP::Date::str2time($if_modified);
7949 } elsif (eval { require Time::ParseDate; 1; }) {
7950 $since = Time::ParseDate::parsedate($if_modified, GMT => 1);
7951 }
7952 if (defined $since && $latest_epoch <= $since) {
7953 print $cgi->header(
7954 -type => $content_type,
7955 -charset => 'utf-8',
7956 -last_modified => $latest_date{'rfc2822'},
7957 -status => '304 Not Modified');
7958 return;
7959 }
7960 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01007961 print $cgi->header(
7962 -type => $content_type,
7963 -charset => 'utf-8',
7964 -last_modified => $latest_date{'rfc2822'});
7965 } else {
7966 print $cgi->header(
7967 -type => $content_type,
7968 -charset => 'utf-8');
7969 }
7970
7971 # Optimization: skip generating the body if client asks only
7972 # for Last-Modified date.
7973 return if ($cgi->request_method() eq 'HEAD');
7974
7975 # header variables
7976 my $title = "$site_name - $project/$action";
7977 my $feed_type = 'log';
7978 if (defined $hash) {
7979 $title .= " - '$hash'";
7980 $feed_type = 'branch log';
7981 if (defined $file_name) {
7982 $title .= " :: $file_name";
7983 $feed_type = 'history';
7984 }
7985 } elsif (defined $file_name) {
7986 $title .= " - $file_name";
7987 $feed_type = 'history';
7988 }
7989 $title .= " $feed_type";
7990 my $descr = git_get_project_description($project);
7991 if (defined $descr) {
7992 $descr = esc_html($descr);
7993 } else {
7994 $descr = "$project " .
7995 ($format eq 'rss' ? 'RSS' : 'Atom') .
7996 " feed";
7997 }
7998 my $owner = git_get_project_owner($project);
7999 $owner = esc_html($owner);
8000
8001 #header
8002 my $alt_url;
8003 if (defined $file_name) {
8004 $alt_url = href(-full=>1, action=>"history", hash=>$hash, file_name=>$file_name);
8005 } elsif (defined $hash) {
8006 $alt_url = href(-full=>1, action=>"log", hash=>$hash);
8007 } else {
8008 $alt_url = href(-full=>1, action=>"summary");
8009 }
8010 print qq!<?xml version="1.0" encoding="utf-8"?>\n!;
8011 if ($format eq 'rss') {
8012 print <<XML;
Jakub Narebski59b9f612006-08-22 23:42:53 +02008013<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
8014<channel>
Jakub Narebski59b9f612006-08-22 23:42:53 +02008015XML
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008016 print "<title>$title</title>\n" .
8017 "<link>$alt_url</link>\n" .
8018 "<description>$descr</description>\n" .
Giuseppe Bilotta3ac109a2009-01-26 12:50:13 +01008019 "<language>en</language>\n" .
8020 # project owner is responsible for 'editorial' content
8021 "<managingEditor>$owner</managingEditor>\n";
Giuseppe Bilotta1ba68ce2009-01-26 12:50:11 +01008022 if (defined $logo || defined $favicon) {
8023 # prefer the logo to the favicon, since RSS
8024 # doesn't allow both
8025 my $img = esc_url($logo || $favicon);
8026 print "<image>\n" .
8027 "<url>$img</url>\n" .
8028 "<title>$title</title>\n" .
8029 "<link>$alt_url</link>\n" .
8030 "</image>\n";
8031 }
Giuseppe Bilotta0cf31282009-01-26 12:50:14 +01008032 if (%latest_date) {
8033 print "<pubDate>$latest_date{'rfc2822'}</pubDate>\n";
8034 print "<lastBuildDate>$latest_date{'rfc2822'}</lastBuildDate>\n";
8035 }
Giuseppe Bilottaad59a7a2009-01-26 12:50:12 +01008036 print "<generator>gitweb v.$version/$git_version</generator>\n";
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008037 } elsif ($format eq 'atom') {
8038 print <<XML;
8039<feed xmlns="http://www.w3.org/2005/Atom">
8040XML
8041 print "<title>$title</title>\n" .
8042 "<subtitle>$descr</subtitle>\n" .
8043 '<link rel="alternate" type="text/html" href="' .
8044 $alt_url . '" />' . "\n" .
8045 '<link rel="self" type="' . $content_type . '" href="' .
8046 $cgi->self_url() . '" />' . "\n" .
8047 "<id>" . href(-full=>1) . "</id>\n" .
8048 # use project owner for feed author
8049 "<author><name>$owner</name></author>\n";
8050 if (defined $favicon) {
8051 print "<icon>" . esc_url($favicon) . "</icon>\n";
8052 }
Jonathan Nieder9d9f5e72010-09-03 19:44:39 -05008053 if (defined $logo) {
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008054 # not twice as wide as tall: 72 x 27 pixels
Jakub Narebskie1147262006-12-04 14:09:43 +01008055 print "<logo>" . esc_url($logo) . "</logo>\n";
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008056 }
8057 if (! %latest_date) {
8058 # dummy date to keep the feed valid until commits trickle in:
8059 print "<updated>1970-01-01T00:00:00Z</updated>\n";
8060 } else {
8061 print "<updated>$latest_date{'iso-8601'}</updated>\n";
8062 }
Giuseppe Bilottaad59a7a2009-01-26 12:50:12 +01008063 print "<generator version='$version/$git_version'>gitweb</generator>\n";
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008064 }
Jakub Narebski717b8312006-07-31 21:22:15 +02008065
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008066 # contents
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00008067 for (my $i = 0; $i <= $#commitlist; $i++) {
8068 my %co = %{$commitlist[$i]};
8069 my $commit = $co{'id'};
Jakub Narebski717b8312006-07-31 21:22:15 +02008070 # we read 150, we always show 30 and the ones more recent than 48 hours
Jakub Narebski91fd2bf2006-11-25 15:54:34 +01008071 if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02008072 last;
8073 }
Jakub Narebski6368d9f2011-03-19 23:53:55 +01008074 my %cd = parse_date($co{'author_epoch'}, $co{'author_tz'});
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008075
8076 # get list of changed files
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00008077 open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebskic906b182007-05-19 02:47:51 +02008078 $co{'parent'} || "--root",
8079 $co{'id'}, "--", (defined $file_name ? $file_name : ())
Jakub Narebski6bcf4b42006-08-27 23:49:36 +02008080 or next;
Jakub Narebski717b8312006-07-31 21:22:15 +02008081 my @difftree = map { chomp; $_ } <$fd>;
Jakub Narebski6bcf4b42006-08-27 23:49:36 +02008082 close $fd
8083 or next;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008084
8085 # print element (entry, item)
Florian La Rochee62a6412008-02-03 12:38:46 +01008086 my $co_url = href(-full=>1, action=>"commitdiff", hash=>$commit);
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008087 if ($format eq 'rss') {
8088 print "<item>\n" .
8089 "<title>" . esc_html($co{'title'}) . "</title>\n" .
8090 "<author>" . esc_html($co{'author'}) . "</author>\n" .
8091 "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
8092 "<guid isPermaLink=\"true\">$co_url</guid>\n" .
8093 "<link>$co_url</link>\n" .
8094 "<description>" . esc_html($co{'title'}) . "</description>\n" .
8095 "<content:encoded>" .
8096 "<![CDATA[\n";
8097 } elsif ($format eq 'atom') {
8098 print "<entry>\n" .
8099 "<title type=\"html\">" . esc_html($co{'title'}) . "</title>\n" .
8100 "<updated>$cd{'iso-8601'}</updated>\n" .
Jakub Narebskiab23c192006-11-25 15:54:33 +01008101 "<author>\n" .
8102 " <name>" . esc_html($co{'author_name'}) . "</name>\n";
8103 if ($co{'author_email'}) {
8104 print " <email>" . esc_html($co{'author_email'}) . "</email>\n";
8105 }
8106 print "</author>\n" .
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008107 # use committer for contributor
Jakub Narebskiab23c192006-11-25 15:54:33 +01008108 "<contributor>\n" .
8109 " <name>" . esc_html($co{'committer_name'}) . "</name>\n";
8110 if ($co{'committer_email'}) {
8111 print " <email>" . esc_html($co{'committer_email'}) . "</email>\n";
8112 }
8113 print "</contributor>\n" .
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008114 "<published>$cd{'iso-8601'}</published>\n" .
8115 "<link rel=\"alternate\" type=\"text/html\" href=\"$co_url\" />\n" .
8116 "<id>$co_url</id>\n" .
8117 "<content type=\"xhtml\" xml:base=\"" . esc_url($my_url) . "\">\n" .
8118 "<div xmlns=\"http://www.w3.org/1999/xhtml\">\n";
8119 }
Jakub Narebski717b8312006-07-31 21:22:15 +02008120 my $comment = $co{'comment'};
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008121 print "<pre>\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02008122 foreach my $line (@$comment) {
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008123 $line = esc_html($line);
8124 print "$line\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02008125 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008126 print "</pre><ul>\n";
8127 foreach my $difftree_line (@difftree) {
8128 my %difftree = parse_difftree_raw_line($difftree_line);
8129 next if !$difftree{'from_id'};
8130
8131 my $file = $difftree{'file'} || $difftree{'to_file'};
8132
8133 print "<li>" .
8134 "[" .
8135 $cgi->a({-href => href(-full=>1, action=>"blobdiff",
8136 hash=>$difftree{'to_id'}, hash_parent=>$difftree{'from_id'},
8137 hash_base=>$co{'id'}, hash_parent_base=>$co{'parent'},
8138 file_name=>$file, file_parent=>$difftree{'from_file'}),
8139 -title => "diff"}, 'D');
8140 if ($have_blame) {
8141 print $cgi->a({-href => href(-full=>1, action=>"blame",
8142 file_name=>$file, hash_base=>$commit),
8143 -title => "blame"}, 'B');
Jakub Narebski717b8312006-07-31 21:22:15 +02008144 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008145 # if this is not a feed of a file history
8146 if (!defined $file_name || $file_name ne $file) {
8147 print $cgi->a({-href => href(-full=>1, action=>"history",
8148 file_name=>$file, hash=>$commit),
8149 -title => "history"}, 'H');
8150 }
8151 $file = esc_path($file);
8152 print "] ".
8153 "$file</li>\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02008154 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008155 if ($format eq 'rss') {
8156 print "</ul>]]>\n" .
8157 "</content:encoded>\n" .
8158 "</item>\n";
8159 } elsif ($format eq 'atom') {
8160 print "</ul>\n</div>\n" .
8161 "</content>\n" .
8162 "</entry>\n";
8163 }
Jakub Narebski717b8312006-07-31 21:22:15 +02008164 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008165
8166 # end of feed
8167 if ($format eq 'rss') {
8168 print "</channel>\n</rss>\n";
Jakub Narebski3278fbc2009-05-11 19:37:28 +02008169 } elsif ($format eq 'atom') {
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01008170 print "</feed>\n";
8171 }
8172}
8173
8174sub git_rss {
8175 git_feed('rss');
8176}
8177
8178sub git_atom {
8179 git_feed('atom');
Jakub Narebski717b8312006-07-31 21:22:15 +02008180}
8181
8182sub git_opml {
Bernhard R. Link19d2d232012-01-30 21:07:37 +01008183 my @list = git_get_projects_list($project_filter, $strict_export);
Jakub Narebski12b14432011-04-29 19:51:56 +02008184 if (!@list) {
8185 die_error(404, "No projects found");
8186 }
Jakub Narebski717b8312006-07-31 21:22:15 +02008187
Giuseppe Bilottaae357852009-01-02 13:49:30 +01008188 print $cgi->header(
8189 -type => 'text/xml',
8190 -charset => 'utf-8',
8191 -content_disposition => 'inline; filename="opml.xml"');
8192
Jürgen Kreileder5d791052011-12-17 10:22:22 +01008193 my $title = esc_html($site_name);
Bernhard R. Link19d2d232012-01-30 21:07:37 +01008194 my $filter = " within subdirectory ";
8195 if (defined $project_filter) {
8196 $filter .= esc_html($project_filter);
8197 } else {
8198 $filter = "";
8199 }
Jakub Narebski59b9f612006-08-22 23:42:53 +02008200 print <<XML;
8201<?xml version="1.0" encoding="utf-8"?>
8202<opml version="1.0">
8203<head>
Bernhard R. Link19d2d232012-01-30 21:07:37 +01008204 <title>$title OPML Export$filter</title>
Jakub Narebski59b9f612006-08-22 23:42:53 +02008205</head>
8206<body>
8207<outline text="git RSS feeds">
8208XML
Jakub Narebski717b8312006-07-31 21:22:15 +02008209
8210 foreach my $pr (@list) {
8211 my %proj = %$pr;
Jakub Narebski847e01f2006-08-14 02:05:47 +02008212 my $head = git_get_head_hash($proj{'path'});
Jakub Narebski717b8312006-07-31 21:22:15 +02008213 if (!defined $head) {
8214 next;
8215 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02008216 $git_dir = "$projectroot/$proj{'path'}";
Jakub Narebski847e01f2006-08-14 02:05:47 +02008217 my %co = parse_commit($head);
Jakub Narebski717b8312006-07-31 21:22:15 +02008218 if (!%co) {
8219 next;
8220 }
8221
8222 my $path = esc_html(chop_str($proj{'path'}, 25, 5));
Giuseppe Bilottadf63fbb2009-01-02 13:15:28 +01008223 my $rss = href('project' => $proj{'path'}, 'action' => 'rss', -full => 1);
8224 my $html = href('project' => $proj{'path'}, 'action' => 'summary', -full => 1);
Jakub Narebski717b8312006-07-31 21:22:15 +02008225 print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";
8226 }
Jakub Narebski59b9f612006-08-22 23:42:53 +02008227 print <<XML;
8228</outline>
8229</body>
8230</opml>
8231XML
Jakub Narebski717b8312006-07-31 21:22:15 +02008232}