blob: 7c481811af6be216140a06db2818bcd9e0caedbc [file] [log] [blame]
Kay Sievers161332a2005-08-07 19:49:46 +02001#!/usr/bin/perl
2
Kay Sieversc994d622005-08-07 20:27:18 +02003# gitweb - simple web interface to track changes in git repositories
Kay Sievers22fafb92005-08-07 19:56:59 +02004#
Kay Sievers00cd0792006-05-22 14:30:47 +02005# (C) 2005-2006, Kay Sievers <kay.sievers@vrfy.org>
6# (C) 2005, Christian Gierke
Kay Sievers823d5dc2005-08-07 19:57:58 +02007#
Kay Sieversd8f1c5c2005-10-04 01:12:47 +02008# This program is licensed under the GPLv2
Kay Sievers161332a2005-08-07 19:49:46 +02009
10use strict;
11use warnings;
Kay Sievers19806692005-08-07 20:26:27 +020012use CGI qw(:standard :escapeHTML -nosticky);
Kay Sievers7403d502005-08-07 20:23:49 +020013use CGI::Util qw(unescape);
Kay Sievers161332a2005-08-07 19:49:46 +020014use CGI::Carp qw(fatalsToBrowser);
Kay Sievers40c13812005-11-19 17:41:29 +010015use Encode;
Kay Sieversb87d78d2005-08-07 20:21:04 +020016use Fcntl ':mode';
Junio C Hamano7a13b992006-07-31 19:18:34 -070017use File::Find qw();
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +053018use File::Basename qw(basename);
Kay Sievers10bb9032005-11-23 04:26:40 +010019binmode STDOUT, ':utf8';
Kay Sievers161332a2005-08-07 19:49:46 +020020
Jakub Narebskib1f5f642006-12-28 00:00:52 +010021BEGIN {
Jakub Narebski3be8e722007-04-01 22:22:21 +020022 CGI->compile() if $ENV{'MOD_PERL'};
Jakub Narebskib1f5f642006-12-28 00:00:52 +010023}
24
Dennis Stosberg4a87b432006-06-21 15:07:08 +020025our $cgi = new CGI;
Junio C Hamano06c084d2006-08-02 13:50:20 -070026our $version = "++GIT_VERSION++";
Dennis Stosberg4a87b432006-06-21 15:07:08 +020027our $my_url = $cgi->url();
28our $my_uri = $cgi->url(-absolute => 1);
Kay Sievers44ad2972005-08-07 19:59:24 +020029
Giuseppe Bilotta81d3fe92009-02-15 10:18:36 +010030# Base URL for relative URLs in gitweb ($logo, $favicon, ...),
31# needed and used only for URLs with nonempty PATH_INFO
32our $base_url = $my_url;
33
34# When the script is used as DirectoryIndex, the URL does not contain the name
35# of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
36# have to do it ourselves. We make $path_info global because it's also used
37# later on.
38#
39# Another issue with the script being the DirectoryIndex is that the resulting
40# $my_url data is not the full script URL: this is good, because we want
41# generated links to keep implying the script name if it wasn't explicitly
42# indicated in the URL we're handling, but it means that $my_url cannot be used
43# as base URL.
44# Therefore, if we needed to strip PATH_INFO, then we know that we have
45# to build the base URL ourselves:
Alexander Gavrilovdde80d92008-11-06 01:10:07 +030046our $path_info = $ENV{"PATH_INFO"};
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +020047if ($path_info) {
Giuseppe Bilotta81d3fe92009-02-15 10:18:36 +010048 if ($my_url =~ s,\Q$path_info\E$,, &&
49 $my_uri =~ s,\Q$path_info\E$,, &&
50 defined $ENV{'SCRIPT_NAME'}) {
51 $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
52 }
Giuseppe Bilottab65910f2008-09-29 15:07:42 +020053}
54
Alp Tokere130dda2006-07-12 23:55:10 +010055# core git executable to use
56# this can just be "git" if your webserver has a sensible PATH
Junio C Hamano06c084d2006-08-02 13:50:20 -070057our $GIT = "++GIT_BINDIR++/git";
Jakub Narebski3f7f27102006-06-21 09:48:04 +020058
Kay Sieversb87d78d2005-08-07 20:21:04 +020059# absolute fs-path which will be prepended to the project path
Dennis Stosberg4a87b432006-06-21 15:07:08 +020060#our $projectroot = "/pub/scm";
Junio C Hamano06c084d2006-08-02 13:50:20 -070061our $projectroot = "++GITWEB_PROJECTROOT++";
Kay Sieversb87d78d2005-08-07 20:21:04 +020062
Luke Luca5e9492007-10-16 20:45:25 -070063# fs traversing limit for getting project list
64# the number is relative to the projectroot
65our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++";
66
Kay Sieversb87d78d2005-08-07 20:21:04 +020067# target of the home link on top of all pages
Martin Waitz6132b7e2006-08-17 00:28:39 +020068our $home_link = $my_uri || "/";
Kay Sieversb87d78d2005-08-07 20:21:04 +020069
Yasushi SHOJI2de21fa2006-08-15 07:50:49 +090070# string of the home link on top of all pages
71our $home_link_str = "++GITWEB_HOME_LINK_STR++";
72
Alp Toker49da1da2006-07-11 21:10:26 +010073# name of your site or organization to appear in page titles
74# replace this with something more descriptive for clearer bookmarks
Petr Baudis8be28902006-10-24 05:18:39 +020075our $site_name = "++GITWEB_SITENAME++"
76 || ($ENV{'SERVER_NAME'} || "Untitled") . " Git";
Alp Toker49da1da2006-07-11 21:10:26 +010077
Alan Chandlerb2d34762006-10-03 13:49:03 +010078# filename of html text to include at top of each page
79our $site_header = "++GITWEB_SITE_HEADER++";
Kay Sievers8ab1da22005-08-07 20:22:53 +020080# html text to include at home page
Junio C Hamano06c084d2006-08-02 13:50:20 -070081our $home_text = "++GITWEB_HOMETEXT++";
Alan Chandlerb2d34762006-10-03 13:49:03 +010082# filename of html text to include at bottom of each page
83our $site_footer = "++GITWEB_SITE_FOOTER++";
Kay Sievers8ab1da22005-08-07 20:22:53 +020084
Alan Chandlerb2d34762006-10-03 13:49:03 +010085# URI of stylesheets
86our @stylesheets = ("++GITWEB_CSS++");
Petr Baudis887a6122006-10-26 14:41:25 +020087# URI of a single stylesheet, which can be overridden in GITWEB_CONFIG.
88our $stylesheet = undef;
Jakub Narebski9a7a62f2006-10-06 12:31:05 +020089# URI of GIT logo (72x27 size)
Junio C Hamano06c084d2006-08-02 13:50:20 -070090our $logo = "++GITWEB_LOGO++";
Jakub Narebski0b5deba2006-09-04 20:32:13 +020091# URI of GIT favicon, assumed to be image/png type
92our $favicon = "++GITWEB_FAVICON++";
Jakub Narebskiaedd9422006-06-17 11:23:56 +020093
Jakub Narebski9a7a62f2006-10-06 12:31:05 +020094# URI and label (title) of GIT logo link
95#our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/";
96#our $logo_label = "git documentation";
97our $logo_url = "http://git.or.cz/";
98our $logo_label = "git homepage";
Junio C Hamano51a7c662006-09-23 12:36:01 -070099
Kay Sievers09bd7892005-08-07 20:21:23 +0200100# source of projects list
Junio C Hamano06c084d2006-08-02 13:50:20 -0700101our $projects_list = "++GITWEB_LIST++";
Kay Sieversb87d78d2005-08-07 20:21:04 +0200102
Michael Hendricks55feb122007-07-04 18:36:48 -0600103# the width (in characters) of the projects list "Description" column
104our $projects_list_description_width = 25;
105
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +0200106# default order of projects list
107# valid values are none, project, descr, owner, and age
108our $default_projects_order = "project";
109
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +0200110# show repository only if this file exists
111# (only effective if this variable evaluates to true)
112our $export_ok = "++GITWEB_EXPORT_OK++";
113
Alexander Gavrilovdd7f5f12008-11-06 01:36:23 +0300114# show repository only if this subroutine returns true
115# when given the path to the project, for example:
116# sub { return -e "$_[0]/git-daemon-export-ok"; }
117our $export_auth_hook = undef;
118
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +0200119# only allow viewing of repositories also shown on the overview page
120our $strict_export = "++GITWEB_STRICT_EXPORT++";
121
Jakub Narebski19a87212006-08-15 23:03:17 +0200122# list of git base URLs used for URL to where fetch project from,
123# i.e. full URL is "$git_base_url/$project"
Jakub Narebskid6b7e0b2006-10-26 12:26:44 +0200124our @git_base_url_list = grep { $_ ne '' } ("++GITWEB_BASE_URL++");
Jakub Narebski19a87212006-08-15 23:03:17 +0200125
Jakub Narebskif5aa79d2006-06-17 13:32:15 +0200126# default blob_plain mimetype and default charset for text/plain blob
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200127our $default_blob_plain_mimetype = 'text/plain';
128our $default_text_plain_charset = undef;
Jakub Narebskif5aa79d2006-06-17 13:32:15 +0200129
Petr Baudis2d007372006-06-18 00:01:06 +0200130# file to use for guessing MIME types before trying /etc/mime.types
131# (relative to the current git repository)
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200132our $mimetypes_file = undef;
Petr Baudis2d007372006-06-18 00:01:06 +0200133
Martin Koegler00f429a2007-06-03 17:42:44 +0200134# assume this charset if line contains non-UTF-8 characters;
135# it should be valid encoding (see Encoding::Supported(3pm) for list),
136# for which encoding all byte sequences are valid, for example
137# 'iso-8859-1' aka 'latin1' (it is decoded without checking, so it
138# could be even 'utf-8' for the old behavior)
139our $fallback_encoding = 'latin1';
140
Jakub Narebski69a9b412007-07-20 02:15:09 +0200141# rename detection options for git-diff and git-diff-tree
142# - default is '-M', with the cost proportional to
143# (number of removed files) * (number of new files).
144# - more costly is '-C' (which implies '-M'), with the cost proportional to
145# (number of changed files + number of removed files) * (number of new files)
146# - even more costly is '-C', '--find-copies-harder' with cost
147# (number of files in the original tree) * (number of new files)
148# - one might want to include '-B' option, e.g. '-B', '-M'
149our @diff_opts = ('-M'); # taken from git_commit
150
Matt McCutchen7e1100e2009-02-07 19:00:09 -0500151# Disables features that would allow repository owners to inject script into
152# the gitweb domain.
153our $prevent_xss = 0;
154
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200155# information about snapshot formats that gitweb is capable of serving
156our %known_snapshot_formats = (
157 # name => {
158 # 'display' => display name,
159 # 'type' => mime type,
160 # 'suffix' => filename suffix,
161 # 'format' => --format for git-archive,
162 # 'compressor' => [compressor command and arguments]
163 # (array reference, optional)}
164 #
165 'tgz' => {
166 'display' => 'tar.gz',
167 'type' => 'application/x-gzip',
168 'suffix' => '.tar.gz',
169 'format' => 'tar',
170 'compressor' => ['gzip']},
171
172 'tbz2' => {
173 'display' => 'tar.bz2',
174 'type' => 'application/x-bzip2',
175 'suffix' => '.tar.bz2',
176 'format' => 'tar',
177 'compressor' => ['bzip2']},
178
179 'zip' => {
180 'display' => 'zip',
181 'type' => 'application/x-zip',
182 'suffix' => '.zip',
183 'format' => 'zip'},
184);
185
186# Aliases so we understand old gitweb.snapshot values in repository
187# configuration.
188our %known_snapshot_format_aliases = (
189 'gzip' => 'tgz',
190 'bzip2' => 'tbz2',
191
192 # backward compatibility: legacy gitweb config support
193 'x-gzip' => undef, 'gz' => undef,
194 'x-bzip2' => undef, 'bz2' => undef,
195 'x-zip' => undef, '' => undef,
196);
197
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530198# You define site-wide feature defaults here; override them with
199# $GITWEB_CONFIG as necessary.
Jakub Narebski952c65f2006-08-22 16:52:50 +0200200our %feature = (
Jakub Narebski17848fc2006-08-26 19:14:22 +0200201 # feature => {
202 # 'sub' => feature-sub (subroutine),
203 # 'override' => allow-override (boolean),
204 # 'default' => [ default options...] (array reference)}
205 #
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200206 # if feature is overridable (it means that allow-override has true value),
Jakub Narebski17848fc2006-08-26 19:14:22 +0200207 # then feature-sub will be called with default options as parameters;
208 # return value of feature-sub indicates if to enable specified feature
209 #
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200210 # if there is no 'sub' key (no feature-sub), then feature cannot be
211 # overriden
212 #
Giuseppe Bilottaff3c0ff2008-12-02 14:57:28 -0800213 # use gitweb_get_feature(<feature>) to retrieve the <feature> value
214 # (an array) or gitweb_check_feature(<feature>) to check if <feature>
215 # is enabled
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530216
Petr Baudis45a3b122006-10-07 15:17:47 +0200217 # Enable the 'blame' blob view, showing the last commit that modified
218 # each line in the file. This can be very CPU-intensive.
219
220 # To enable system wide have in $GITWEB_CONFIG
221 # $feature{'blame'}{'default'} = [1];
222 # To have project specific config enable override in $GITWEB_CONFIG
223 # $feature{'blame'}{'override'} = 1;
224 # and in project config gitweb.blame = 0|1;
Jakub Narebski952c65f2006-08-22 16:52:50 +0200225 'blame' => {
Matt Kraaicdad8172008-12-15 22:16:19 -0800226 'sub' => sub { feature_bool('blame', @_) },
Jakub Narebski952c65f2006-08-22 16:52:50 +0200227 'override' => 0,
228 'default' => [0]},
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530229
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200230 # Enable the 'snapshot' link, providing a compressed archive of any
Petr Baudis45a3b122006-10-07 15:17:47 +0200231 # tree. This can potentially generate high traffic if you have large
232 # project.
233
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200234 # Value is a list of formats defined in %known_snapshot_formats that
235 # you wish to offer.
Petr Baudis45a3b122006-10-07 15:17:47 +0200236 # To disable system wide have in $GITWEB_CONFIG
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200237 # $feature{'snapshot'}{'default'} = [];
Petr Baudis45a3b122006-10-07 15:17:47 +0200238 # To have project specific config enable override in $GITWEB_CONFIG
Uwe Zeisbergerbbee1d92006-12-08 12:44:31 +0100239 # $feature{'snapshot'}{'override'} = 1;
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200240 # and in project config, a comma-separated list of formats or "none"
241 # to disable. Example: gitweb.snapshot = tbz2,zip;
Jakub Narebski952c65f2006-08-22 16:52:50 +0200242 'snapshot' => {
243 'sub' => \&feature_snapshot,
244 'override' => 0,
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200245 'default' => ['tgz']},
Jakub Narebski04f7a942006-09-11 00:29:27 +0200246
Robert Fitzsimons6be93512006-12-23 03:35:16 +0000247 # Enable text search, which will list the commits which match author,
248 # committer or commit text to a given string. Enabled by default.
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200249 # Project specific override is not supported.
Robert Fitzsimons6be93512006-12-23 03:35:16 +0000250 'search' => {
251 'override' => 0,
252 'default' => [1]},
253
Petr Baudise7738552007-05-17 04:31:12 +0200254 # Enable grep search, which will list the files in currently selected
255 # tree containing the given string. Enabled by default. This can be
256 # potentially CPU-intensive, of course.
257
258 # To enable system wide have in $GITWEB_CONFIG
259 # $feature{'grep'}{'default'} = [1];
260 # To have project specific config enable override in $GITWEB_CONFIG
261 # $feature{'grep'}{'override'} = 1;
262 # and in project config gitweb.grep = 0|1;
263 'grep' => {
Matt Kraaicdad8172008-12-15 22:16:19 -0800264 'sub' => sub { feature_bool('grep', @_) },
Petr Baudise7738552007-05-17 04:31:12 +0200265 'override' => 0,
266 'default' => [1]},
267
Petr Baudis45a3b122006-10-07 15:17:47 +0200268 # Enable the pickaxe search, which will list the commits that modified
269 # a given string in a file. This can be practical and quite faster
270 # alternative to 'blame', but still potentially CPU-intensive.
271
272 # To enable system wide have in $GITWEB_CONFIG
273 # $feature{'pickaxe'}{'default'} = [1];
274 # To have project specific config enable override in $GITWEB_CONFIG
275 # $feature{'pickaxe'}{'override'} = 1;
276 # and in project config gitweb.pickaxe = 0|1;
Jakub Narebski04f7a942006-09-11 00:29:27 +0200277 'pickaxe' => {
Matt Kraaicdad8172008-12-15 22:16:19 -0800278 'sub' => sub { feature_bool('pickaxe', @_) },
Jakub Narebski04f7a942006-09-11 00:29:27 +0200279 'override' => 0,
280 'default' => [1]},
Martin Waitz9e756902006-10-01 23:57:48 +0200281
Petr Baudis45a3b122006-10-07 15:17:47 +0200282 # Make gitweb use an alternative format of the URLs which can be
283 # more readable and natural-looking: project name is embedded
284 # directly in the path and the query string contains other
285 # auxiliary information. All gitweb installations recognize
286 # URL in either format; this configures in which formats gitweb
287 # generates links.
288
289 # To enable system wide have in $GITWEB_CONFIG
290 # $feature{'pathinfo'}{'default'} = [1];
291 # Project specific override is not supported.
292
293 # Note that you will need to change the default location of CSS,
294 # favicon, logo and possibly other files to an absolute URL. Also,
295 # if gitweb.cgi serves as your indexfile, you will need to force
296 # $my_uri to contain the script name in your $GITWEB_CONFIG.
Martin Waitz9e756902006-10-01 23:57:48 +0200297 'pathinfo' => {
298 'override' => 0,
299 'default' => [0]},
Petr Baudise30496d2006-10-24 05:33:17 +0200300
301 # Make gitweb consider projects in project root subdirectories
302 # to be forks of existing projects. Given project $projname.git,
303 # projects matching $projname/*.git will not be shown in the main
304 # projects list, instead a '+' mark will be added to $projname
305 # there and a 'forks' view will be enabled for the project, listing
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +0200306 # all the forks. If project list is taken from a file, forks have
307 # to be listed after the main project.
Petr Baudise30496d2006-10-24 05:33:17 +0200308
309 # To enable system wide have in $GITWEB_CONFIG
310 # $feature{'forks'}{'default'} = [1];
311 # Project specific override is not supported.
312 'forks' => {
313 'override' => 0,
314 'default' => [0]},
Petr Baudisd627f682008-10-02 16:36:52 +0200315
316 # Insert custom links to the action bar of all project pages.
317 # This enables you mainly to link to third-party scripts integrating
318 # into gitweb; e.g. git-browser for graphical history representation
319 # or custom web-based repository administration interface.
320
321 # The 'default' value consists of a list of triplets in the form
322 # (label, link, position) where position is the label after which
Jakub Narebski2b11e052008-10-12 00:02:32 +0200323 # to insert the link and link is a format string where %n expands
Petr Baudisd627f682008-10-02 16:36:52 +0200324 # to the project name, %f to the project path within the filesystem,
325 # %h to the current hash (h gitweb parameter) and %b to the current
Jakub Narebski2b11e052008-10-12 00:02:32 +0200326 # hash base (hb gitweb parameter); %% expands to %.
Petr Baudisd627f682008-10-02 16:36:52 +0200327
328 # To enable system wide have in $GITWEB_CONFIG e.g.
329 # $feature{'actions'}{'default'} = [('graphiclog',
330 # '/git-browser/by-commit.html?r=%n', 'summary')];
331 # Project specific override is not supported.
332 'actions' => {
333 'override' => 0,
334 'default' => []},
Shawn O. Pearce3e3d4ee2008-10-03 07:41:25 -0700335
Petr Baudisaed93de2008-10-02 17:13:02 +0200336 # Allow gitweb scan project content tags described in ctags/
337 # of project repository, and display the popular Web 2.0-ish
338 # "tag cloud" near the project list. Note that this is something
339 # COMPLETELY different from the normal Git tags.
340
341 # gitweb by itself can show existing tags, but it does not handle
342 # tagging itself; you need an external application for that.
343 # For an example script, check Girocco's cgi/tagproj.cgi.
344 # You may want to install the HTML::TagCloud Perl module to get
345 # a pretty tag cloud instead of just a list of tags.
346
347 # To enable system wide have in $GITWEB_CONFIG
348 # $feature{'ctags'}{'default'} = ['path_to_tag_script'];
349 # Project specific override is not supported.
350 'ctags' => {
351 'override' => 0,
352 'default' => [0]},
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +0100353
354 # The maximum number of patches in a patchset generated in patch
355 # view. Set this to 0 or undef to disable patch view, or to a
356 # negative number to remove any limit.
357
358 # To disable system wide have in $GITWEB_CONFIG
359 # $feature{'patches'}{'default'} = [0];
360 # To have project specific config enable override in $GITWEB_CONFIG
361 # $feature{'patches'}{'override'} = 1;
362 # and in project config gitweb.patches = 0|n;
363 # where n is the maximum number of patches allowed in a patchset.
364 'patches' => {
365 'sub' => \&feature_patches,
366 'override' => 0,
367 'default' => [16]},
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530368);
369
Junio C Hamanoa7c5a282008-11-29 13:02:08 -0800370sub gitweb_get_feature {
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530371 my ($name) = @_;
Jakub Narebskidd1ad5f2006-09-26 01:56:17 +0200372 return unless exists $feature{$name};
Jakub Narebski952c65f2006-08-22 16:52:50 +0200373 my ($sub, $override, @defaults) = (
374 $feature{$name}{'sub'},
375 $feature{$name}{'override'},
376 @{$feature{$name}{'default'}});
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530377 if (!$override) { return @defaults; }
Martin Waitza9455912006-10-03 20:07:43 +0200378 if (!defined $sub) {
379 warn "feature $name is not overrideable";
380 return @defaults;
381 }
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530382 return $sub->(@defaults);
383}
384
Giuseppe Bilotta25b27902008-11-29 13:07:29 -0800385# A wrapper to check if a given feature is enabled.
386# With this, you can say
387#
388# my $bool_feat = gitweb_check_feature('bool_feat');
389# gitweb_check_feature('bool_feat') or somecode;
390#
391# instead of
392#
393# my ($bool_feat) = gitweb_get_feature('bool_feat');
394# (gitweb_get_feature('bool_feat'))[0] or somecode;
395#
396sub gitweb_check_feature {
397 return (gitweb_get_feature(@_))[0];
398}
399
400
Matt Kraaicdad8172008-12-15 22:16:19 -0800401sub feature_bool {
402 my $key = shift;
403 my ($val) = git_get_project_config($key, '--bool');
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530404
405 if ($val eq 'true') {
Matt Kraaicdad8172008-12-15 22:16:19 -0800406 return (1);
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530407 } elsif ($val eq 'false') {
Matt Kraaicdad8172008-12-15 22:16:19 -0800408 return (0);
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530409 }
410
Matt Kraaicdad8172008-12-15 22:16:19 -0800411 return ($_[0]);
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530412}
413
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530414sub feature_snapshot {
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200415 my (@fmts) = @_;
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530416
417 my ($val) = git_get_project_config('snapshot');
418
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200419 if ($val) {
420 @fmts = ($val eq 'none' ? () : split /\s*[,\s]\s*/, $val);
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530421 }
422
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200423 return @fmts;
Luben Tuikovde9272f2006-09-28 16:49:21 -0700424}
425
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +0100426sub feature_patches {
427 my @val = (git_get_project_config('patches', '--int'));
428
429 if (@val) {
430 return @val;
431 }
432
433 return ($_[0]);
434}
435
Junio C Hamano2172ce42006-10-03 02:30:47 -0700436# checking HEAD file with -e is fragile if the repository was
437# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
438# and then pruned.
439sub check_head_link {
440 my ($dir) = @_;
441 my $headfile = "$dir/HEAD";
442 return ((-e $headfile) ||
443 (-l $headfile && readlink($headfile) =~ /^refs\/heads\//));
444}
445
446sub check_export_ok {
447 my ($dir) = @_;
448 return (check_head_link($dir) &&
Alexander Gavrilovdd7f5f12008-11-06 01:36:23 +0300449 (!$export_ok || -e "$dir/$export_ok") &&
450 (!$export_auth_hook || $export_auth_hook->($dir)));
Junio C Hamano2172ce42006-10-03 02:30:47 -0700451}
452
Jakub Narebskia7817852007-07-22 23:41:20 +0200453# process alternate names for backward compatibility
454# filter out unsupported (unknown) snapshot formats
455sub filter_snapshot_fmts {
456 my @fmts = @_;
457
458 @fmts = map {
459 exists $known_snapshot_format_aliases{$_} ?
460 $known_snapshot_format_aliases{$_} : $_} @fmts;
461 @fmts = grep(exists $known_snapshot_formats{$_}, @fmts);
462
463}
464
Junio C Hamano06c084d2006-08-02 13:50:20 -0700465our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
Gerrit Pape17a8b252008-03-26 18:11:19 +0000466if (-e $GITWEB_CONFIG) {
467 do $GITWEB_CONFIG;
468} else {
469 our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
470 do $GITWEB_CONFIG_SYSTEM if -e $GITWEB_CONFIG_SYSTEM;
471}
Jeff Kingc8d138a2006-08-02 15:23:34 -0400472
473# version of the core git binary
Jakub Narebski66115d32008-06-14 20:37:59 +0200474our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
Jeff Kingc8d138a2006-08-02 15:23:34 -0400475
476$projects_list ||= $projectroot;
Jeff Kingc8d138a2006-08-02 15:23:34 -0400477
Jakub Narebski154b4d72006-08-05 12:55:20 +0200478# ======================================================================
Kay Sievers09bd7892005-08-07 20:21:23 +0200479# input validation and dispatch
Kay Sieversb87d78d2005-08-07 20:21:04 +0200480
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200481# input parameters can be collected from a variety of sources (presently, CGI
482# and PATH_INFO), so we define an %input_params hash that collects them all
483# together during validation: this allows subsequent uses (e.g. href()) to be
484# agnostic of the parameter origin
Kay Sievers6191f8e2005-08-07 20:19:56 +0200485
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300486our %input_params = ();
Martin Waitz5c95fab2006-08-17 00:28:38 +0200487
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200488# input parameters are stored with the long parameter name as key. This will
489# also be used in the href subroutine to convert parameters to their CGI
490# equivalent, and since the href() usage is the most frequent one, we store
491# the name -> CGI key mapping here, instead of the reverse.
492#
493# XXX: Warning: If you touch this, check the search form for updating,
494# too.
Jakub Narebski24d06932006-09-26 01:57:02 +0200495
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300496our @cgi_param_mapping = (
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200497 project => "p",
498 action => "a",
499 file_name => "f",
500 file_parent => "fp",
501 hash => "h",
502 hash_parent => "hp",
503 hash_base => "hb",
504 hash_parent_base => "hpb",
505 page => "pg",
506 order => "o",
507 searchtext => "s",
508 searchtype => "st",
509 snapshot_format => "sf",
510 extra_options => "opt",
511 search_use_regexp => "sr",
Miklos Vajna868bc062007-07-12 20:39:27 +0200512);
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300513our %cgi_param_mapping = @cgi_param_mapping;
Miklos Vajna868bc062007-07-12 20:39:27 +0200514
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200515# we will also need to know the possible actions, for validation
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300516our %actions = (
Rafael Garcia-Suarez3a5b9192008-06-06 09:53:32 +0200517 "blame" => \&git_blame,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200518 "blobdiff" => \&git_blobdiff,
519 "blobdiff_plain" => \&git_blobdiff_plain,
520 "blob" => \&git_blob,
521 "blob_plain" => \&git_blob_plain,
522 "commitdiff" => \&git_commitdiff,
523 "commitdiff_plain" => \&git_commitdiff_plain,
524 "commit" => \&git_commit,
Petr Baudise30496d2006-10-24 05:33:17 +0200525 "forks" => \&git_forks,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200526 "heads" => \&git_heads,
527 "history" => \&git_history,
528 "log" => \&git_log,
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +0100529 "patch" => \&git_patch,
Giuseppe Bilottaa3411f82008-12-18 08:13:18 +0100530 "patches" => \&git_patches,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200531 "rss" => \&git_rss,
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +0100532 "atom" => \&git_atom,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200533 "search" => \&git_search,
Petr Baudis88ad7292006-10-24 05:15:46 +0200534 "search_help" => \&git_search_help,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200535 "shortlog" => \&git_shortlog,
536 "summary" => \&git_summary,
537 "tag" => \&git_tag,
538 "tags" => \&git_tags,
539 "tree" => \&git_tree,
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +0530540 "snapshot" => \&git_snapshot,
Jakub Narebskica946012006-12-10 13:25:47 +0100541 "object" => \&git_object,
Jakub Narebski77a153f2006-08-22 16:59:20 +0200542 # those below don't need $project
543 "opml" => \&git_opml,
544 "project_list" => \&git_project_list,
Jakub Narebskifc2b2be2006-09-15 04:56:03 +0200545 "project_index" => \&git_project_index,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200546);
547
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200548# finally, we have the hash of allowed extra_options for the commands that
549# allow them
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300550our %allowed_options = (
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200551 "--no-merges" => [ qw(rss atom log shortlog history) ],
552);
553
554# fill %input_params with the CGI parameters. All values except for 'opt'
555# should be single values, but opt can be an array. We should probably
556# build an array of parameters that can be multi-valued, but since for the time
557# being it's only this one, we just single it out
558while (my ($name, $symbol) = each %cgi_param_mapping) {
559 if ($symbol eq 'opt') {
560 $input_params{$name} = [ $cgi->param($symbol) ];
561 } else {
562 $input_params{$name} = $cgi->param($symbol);
563 }
564}
565
566# now read PATH_INFO and update the parameter list for missing parameters
567sub evaluate_path_info {
568 return if defined $input_params{'project'};
569 return if !$path_info;
570 $path_info =~ s,^/+,,;
571 return if !$path_info;
572
573 # find which part of PATH_INFO is project
574 my $project = $path_info;
575 $project =~ s,/+$,,;
576 while ($project && !check_head_link("$projectroot/$project")) {
577 $project =~ s,/*[^/]*$,,;
578 }
579 return unless $project;
580 $input_params{'project'} = $project;
581
582 # do not change any parameters if an action is given using the query string
583 return if $input_params{'action'};
584 $path_info =~ s,^\Q$project\E/*,,;
585
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200586 # next, check if we have an action
587 my $action = $path_info;
588 $action =~ s,/.*$,,;
589 if (exists $actions{$action}) {
590 $path_info =~ s,^$action/*,,;
591 $input_params{'action'} = $action;
592 }
593
594 # list of actions that want hash_base instead of hash, but can have no
595 # pathname (f) parameter
596 my @wants_base = (
597 'tree',
598 'history',
599 );
600
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200601 # we want to catch
602 # [$hash_parent_base[:$file_parent]..]$hash_parent[:$file_name]
603 my ($parentrefname, $parentpathname, $refname, $pathname) =
604 ($path_info =~ /^(?:(.+?)(?::(.+))?\.\.)?(.+?)(?::(.+))?$/);
605
606 # first, analyze the 'current' part
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200607 if (defined $pathname) {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200608 # we got "branch:filename" or "branch:dir/"
609 # we could use git_get_type(branch:pathname), but:
610 # - it needs $git_dir
611 # - it does a git() call
612 # - the convention of terminating directories with a slash
613 # makes it superfluous
614 # - embedding the action in the PATH_INFO would make it even
615 # more superfluous
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200616 $pathname =~ s,^/+,,;
617 if (!$pathname || substr($pathname, -1) eq "/") {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200618 $input_params{'action'} ||= "tree";
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200619 $pathname =~ s,/$,,;
620 } else {
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200621 # the default action depends on whether we had parent info
622 # or not
623 if ($parentrefname) {
624 $input_params{'action'} ||= "blobdiff_plain";
625 } else {
626 $input_params{'action'} ||= "blob_plain";
627 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200628 }
629 $input_params{'hash_base'} ||= $refname;
630 $input_params{'file_name'} ||= $pathname;
631 } elsif (defined $refname) {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200632 # we got "branch". In this case we have to choose if we have to
633 # set hash or hash_base.
634 #
635 # Most of the actions without a pathname only want hash to be
636 # set, except for the ones specified in @wants_base that want
637 # hash_base instead. It should also be noted that hand-crafted
638 # links having 'history' as an action and no pathname or hash
639 # set will fail, but that happens regardless of PATH_INFO.
640 $input_params{'action'} ||= "shortlog";
641 if (grep { $_ eq $input_params{'action'} } @wants_base) {
642 $input_params{'hash_base'} ||= $refname;
643 } else {
644 $input_params{'hash'} ||= $refname;
645 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200646 }
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200647
648 # next, handle the 'parent' part, if present
649 if (defined $parentrefname) {
650 # a missing pathspec defaults to the 'current' filename, allowing e.g.
651 # someproject/blobdiff/oldrev..newrev:/filename
652 if ($parentpathname) {
653 $parentpathname =~ s,^/+,,;
654 $parentpathname =~ s,/$,,;
655 $input_params{'file_parent'} ||= $parentpathname;
656 } else {
657 $input_params{'file_parent'} ||= $input_params{'file_name'};
658 }
659 # we assume that hash_parent_base is wanted if a path was specified,
660 # or if the action wants hash_base instead of hash
661 if (defined $input_params{'file_parent'} ||
662 grep { $_ eq $input_params{'action'} } @wants_base) {
663 $input_params{'hash_parent_base'} ||= $parentrefname;
664 } else {
665 $input_params{'hash_parent'} ||= $parentrefname;
666 }
667 }
Giuseppe Bilotta1ec2fb52008-11-02 10:21:38 +0100668
669 # for the snapshot action, we allow URLs in the form
670 # $project/snapshot/$hash.ext
671 # where .ext determines the snapshot and gets removed from the
672 # passed $refname to provide the $hash.
673 #
674 # To be able to tell that $refname includes the format extension, we
675 # require the following two conditions to be satisfied:
676 # - the hash input parameter MUST have been set from the $refname part
677 # of the URL (i.e. they must be equal)
678 # - the snapshot format MUST NOT have been defined already (e.g. from
679 # CGI parameter sf)
680 # It's also useless to try any matching unless $refname has a dot,
681 # so we check for that too
682 if (defined $input_params{'action'} &&
683 $input_params{'action'} eq 'snapshot' &&
684 defined $refname && index($refname, '.') != -1 &&
685 $refname eq $input_params{'hash'} &&
686 !defined $input_params{'snapshot_format'}) {
687 # We loop over the known snapshot formats, checking for
688 # extensions. Allowed extensions are both the defined suffix
689 # (which includes the initial dot already) and the snapshot
690 # format key itself, with a prepended dot
691 while (my ($fmt, %opt) = each %known_snapshot_formats) {
692 my $hash = $refname;
693 my $sfx;
694 $hash =~ s/(\Q$opt{'suffix'}\E|\Q.$fmt\E)$//;
695 next unless $sfx = $1;
696 # a valid suffix was found, so set the snapshot format
697 # and reset the hash parameter
698 $input_params{'snapshot_format'} = $fmt;
699 $input_params{'hash'} = $hash;
700 # we also set the format suffix to the one requested
701 # in the URL: this way a request for e.g. .tgz returns
702 # a .tgz instead of a .tar.gz
703 $known_snapshot_formats{$fmt}{'suffix'} = $sfx;
704 last;
705 }
706 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200707}
708evaluate_path_info();
709
710our $action = $input_params{'action'};
711if (defined $action) {
712 if (!validate_action($action)) {
713 die_error(400, "Invalid action parameter");
714 }
715}
716
717# parameters which are pathnames
718our $project = $input_params{'project'};
719if (defined $project) {
720 if (!validate_project($project)) {
721 undef $project;
722 die_error(404, "No such project");
723 }
724}
725
726our $file_name = $input_params{'file_name'};
727if (defined $file_name) {
728 if (!validate_pathname($file_name)) {
729 die_error(400, "Invalid file parameter");
730 }
731}
732
733our $file_parent = $input_params{'file_parent'};
734if (defined $file_parent) {
735 if (!validate_pathname($file_parent)) {
736 die_error(400, "Invalid file parent parameter");
737 }
738}
739
740# parameters which are refnames
741our $hash = $input_params{'hash'};
742if (defined $hash) {
743 if (!validate_refname($hash)) {
744 die_error(400, "Invalid hash parameter");
745 }
746}
747
748our $hash_parent = $input_params{'hash_parent'};
749if (defined $hash_parent) {
750 if (!validate_refname($hash_parent)) {
751 die_error(400, "Invalid hash parent parameter");
752 }
753}
754
755our $hash_base = $input_params{'hash_base'};
756if (defined $hash_base) {
757 if (!validate_refname($hash_base)) {
758 die_error(400, "Invalid hash base parameter");
759 }
760}
761
762our @extra_options = @{$input_params{'extra_options'}};
763# @extra_options is always defined, since it can only be (currently) set from
764# CGI, and $cgi->param() returns the empty array in array context if the param
765# is not set
766foreach my $opt (@extra_options) {
767 if (not exists $allowed_options{$opt}) {
768 die_error(400, "Invalid option parameter");
769 }
770 if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
771 die_error(400, "Invalid option parameter for this action");
772 }
773}
774
775our $hash_parent_base = $input_params{'hash_parent_base'};
776if (defined $hash_parent_base) {
777 if (!validate_refname($hash_parent_base)) {
778 die_error(400, "Invalid hash parent base parameter");
779 }
780}
781
782# other parameters
783our $page = $input_params{'page'};
784if (defined $page) {
785 if ($page =~ m/[^0-9]/) {
786 die_error(400, "Invalid page parameter");
787 }
788}
789
790our $searchtype = $input_params{'searchtype'};
791if (defined $searchtype) {
792 if ($searchtype =~ m/[^a-z]/) {
793 die_error(400, "Invalid searchtype parameter");
794 }
795}
796
797our $search_use_regexp = $input_params{'search_use_regexp'};
798
799our $searchtext = $input_params{'searchtext'};
800our $search_regexp;
801if (defined $searchtext) {
802 if (length($searchtext) < 2) {
803 die_error(403, "At least two characters are required for search parameter");
804 }
805 $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
806}
807
808# path to the current git repository
809our $git_dir;
810$git_dir = "$projectroot/$project" if $project;
811
Giuseppe Bilotta5e166842008-11-02 10:21:37 +0100812# list of supported snapshot formats
Junio C Hamanoa7c5a282008-11-29 13:02:08 -0800813our @snapshot_fmts = gitweb_get_feature('snapshot');
Giuseppe Bilotta5e166842008-11-02 10:21:37 +0100814@snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
815
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200816# dispatch
Gerrit Pape7f9778b2007-05-10 07:32:07 +0000817if (!defined $action) {
818 if (defined $hash) {
819 $action = git_get_type($hash);
820 } elsif (defined $hash_base && defined $file_name) {
821 $action = git_get_type("$hash_base:$file_name");
822 } elsif (defined $project) {
823 $action = 'summary';
824 } else {
825 $action = 'project_list';
826 }
Jakub Narebski77a153f2006-08-22 16:59:20 +0200827}
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200828if (!defined($actions{$action})) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200829 die_error(400, "Unknown action");
Kay Sievers09bd7892005-08-07 20:21:23 +0200830}
Jakub Narebskid04d3d42006-09-19 21:53:22 +0200831if ($action !~ m/^(opml|project_list|project_index)$/ &&
832 !$project) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200833 die_error(400, "Project needed");
Jakub Narebskid04d3d42006-09-19 21:53:22 +0200834}
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200835$actions{$action}->();
836exit;
Kay Sievers09bd7892005-08-07 20:21:23 +0200837
Jakub Narebski717b8312006-07-31 21:22:15 +0200838## ======================================================================
Martin Waitz06a9d862006-08-16 00:23:50 +0200839## action links
840
Jakub Narebski35621982008-04-20 22:09:48 +0200841sub href (%) {
Jakub Narebski498fe002006-08-22 19:05:25 +0200842 my %params = @_;
Jakub Narebskibd5d1e42006-11-19 15:05:21 +0100843 # default is to use -absolute url() i.e. $my_uri
844 my $href = $params{-full} ? $my_url : $my_uri;
Jakub Narebski498fe002006-08-22 19:05:25 +0200845
Jakub Narebskiafa9b622008-02-14 09:22:30 +0100846 $params{'project'} = $project unless exists $params{'project'};
847
Jakub Narebski1cad2832007-11-01 13:06:27 +0100848 if ($params{-replay}) {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200849 while (my ($name, $symbol) = each %cgi_param_mapping) {
Jakub Narebski1cad2832007-11-01 13:06:27 +0100850 if (!exists $params{$name}) {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200851 $params{$name} = $input_params{$name};
Jakub Narebski1cad2832007-11-01 13:06:27 +0100852 }
853 }
854 }
855
Giuseppe Bilotta25b27902008-11-29 13:07:29 -0800856 my $use_pathinfo = gitweb_check_feature('pathinfo');
Giuseppe Bilottafb098a92009-01-02 12:34:40 +0100857 if ($use_pathinfo and defined $params{'project'}) {
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +0200858 # try to put as many parameters as possible in PATH_INFO:
859 # - project name
860 # - action
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +0200861 # - hash_parent or hash_parent_base:/file_parent
Giuseppe Bilotta3550ea72008-10-21 21:34:52 +0200862 # - hash or hash_base:/filename
Giuseppe Bilottac752a0e2008-11-02 10:21:39 +0100863 # - the snapshot_format as an appropriate suffix
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +0200864
865 # When the script is the root DirectoryIndex for the domain,
866 # $href here would be something like http://gitweb.example.com/
867 # Thus, we strip any trailing / from $href, to spare us double
868 # slashes in the final URL
869 $href =~ s,/$,,;
870
871 # Then add the project name, if present
Giuseppe Bilottafb098a92009-01-02 12:34:40 +0100872 $href .= "/".esc_url($params{'project'});
Martin Waitz9e756902006-10-01 23:57:48 +0200873 delete $params{'project'};
874
Giuseppe Bilottac752a0e2008-11-02 10:21:39 +0100875 # since we destructively absorb parameters, we keep this
876 # boolean that remembers if we're handling a snapshot
877 my $is_snapshot = $params{'action'} eq 'snapshot';
878
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +0200879 # Summary just uses the project path URL, any other action is
880 # added to the URL
881 if (defined $params{'action'}) {
882 $href .= "/".esc_url($params{'action'}) unless $params{'action'} eq 'summary';
Martin Waitz9e756902006-10-01 23:57:48 +0200883 delete $params{'action'};
884 }
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +0200885
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +0200886 # Next, we put hash_parent_base:/file_parent..hash_base:/file_name,
887 # stripping nonexistent or useless pieces
888 $href .= "/" if ($params{'hash_base'} || $params{'hash_parent_base'}
889 || $params{'hash_parent'} || $params{'hash'});
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +0200890 if (defined $params{'hash_base'}) {
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +0200891 if (defined $params{'hash_parent_base'}) {
892 $href .= esc_url($params{'hash_parent_base'});
893 # skip the file_parent if it's the same as the file_name
894 delete $params{'file_parent'} if $params{'file_parent'} eq $params{'file_name'};
895 if (defined $params{'file_parent'} && $params{'file_parent'} !~ /\.\./) {
896 $href .= ":/".esc_url($params{'file_parent'});
897 delete $params{'file_parent'};
898 }
899 $href .= "..";
900 delete $params{'hash_parent'};
901 delete $params{'hash_parent_base'};
902 } elsif (defined $params{'hash_parent'}) {
903 $href .= esc_url($params{'hash_parent'}). "..";
904 delete $params{'hash_parent'};
905 }
906
907 $href .= esc_url($params{'hash_base'});
908 if (defined $params{'file_name'} && $params{'file_name'} !~ /\.\./) {
Giuseppe Bilotta3550ea72008-10-21 21:34:52 +0200909 $href .= ":/".esc_url($params{'file_name'});
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +0200910 delete $params{'file_name'};
911 }
912 delete $params{'hash'};
913 delete $params{'hash_base'};
914 } elsif (defined $params{'hash'}) {
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +0200915 $href .= esc_url($params{'hash'});
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +0200916 delete $params{'hash'};
917 }
Giuseppe Bilottac752a0e2008-11-02 10:21:39 +0100918
919 # If the action was a snapshot, we can absorb the
920 # snapshot_format parameter too
921 if ($is_snapshot) {
922 my $fmt = $params{'snapshot_format'};
923 # snapshot_format should always be defined when href()
924 # is called, but just in case some code forgets, we
925 # fall back to the default
926 $fmt ||= $snapshot_fmts[0];
927 $href .= $known_snapshot_formats{$fmt}{'suffix'};
928 delete $params{'snapshot_format'};
929 }
Martin Waitz9e756902006-10-01 23:57:48 +0200930 }
931
932 # now encode the parameters explicitly
Jakub Narebski498fe002006-08-22 19:05:25 +0200933 my @result = ();
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200934 for (my $i = 0; $i < @cgi_param_mapping; $i += 2) {
935 my ($name, $symbol) = ($cgi_param_mapping[$i], $cgi_param_mapping[$i+1]);
Jakub Narebski498fe002006-08-22 19:05:25 +0200936 if (defined $params{$name}) {
Jakub Narebskif22cca42007-07-29 01:04:09 +0200937 if (ref($params{$name}) eq "ARRAY") {
938 foreach my $par (@{$params{$name}}) {
939 push @result, $symbol . "=" . esc_param($par);
940 }
941 } else {
942 push @result, $symbol . "=" . esc_param($params{$name});
943 }
Jakub Narebski498fe002006-08-22 19:05:25 +0200944 }
945 }
Martin Waitz9e756902006-10-01 23:57:48 +0200946 $href .= "?" . join(';', @result) if scalar @result;
947
948 return $href;
Martin Waitz06a9d862006-08-16 00:23:50 +0200949}
950
951
952## ======================================================================
Jakub Narebski717b8312006-07-31 21:22:15 +0200953## validation, quoting/unquoting and escaping
954
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200955sub validate_action {
956 my $input = shift || return undef;
957 return undef unless exists $actions{$input};
958 return $input;
959}
960
961sub validate_project {
962 my $input = shift || return undef;
963 if (!validate_pathname($input) ||
964 !(-d "$projectroot/$input") ||
Alexander Gavrilovec26f092008-11-06 01:15:56 +0300965 !check_export_ok("$projectroot/$input") ||
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200966 ($strict_export && !project_in_list($input))) {
967 return undef;
968 } else {
969 return $input;
970 }
971}
972
Jakub Narebski24d06932006-09-26 01:57:02 +0200973sub validate_pathname {
974 my $input = shift || return undef;
Jakub Narebski717b8312006-07-31 21:22:15 +0200975
Jakub Narebski24d06932006-09-26 01:57:02 +0200976 # no '.' or '..' as elements of path, i.e. no '.' nor '..'
977 # at the beginning, at the end, and between slashes.
978 # also this catches doubled slashes
979 if ($input =~ m!(^|/)(|\.|\.\.)(/|$)!) {
980 return undef;
981 }
982 # no null characters
983 if ($input =~ m!\0!) {
984 return undef;
985 }
986 return $input;
987}
988
989sub validate_refname {
990 my $input = shift || return undef;
991
992 # textual hashes are O.K.
Jakub Narebski717b8312006-07-31 21:22:15 +0200993 if ($input =~ m/^[0-9a-fA-F]{40}$/) {
994 return $input;
995 }
Jakub Narebski24d06932006-09-26 01:57:02 +0200996 # it must be correct pathname
997 $input = validate_pathname($input)
998 or return undef;
999 # restrictions on ref name according to git-check-ref-format
1000 if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001001 return undef;
1002 }
1003 return $input;
1004}
1005
Martin Koegler00f429a2007-06-03 17:42:44 +02001006# decode sequences of octets in utf8 into Perl's internal form,
1007# which is utf-8 with utf8 flag set if needed. gitweb writes out
1008# in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning
1009sub to_utf8 {
1010 my $str = shift;
İsmail Dönmeze5d3de52007-12-04 10:55:41 +02001011 if (utf8::valid($str)) {
1012 utf8::decode($str);
1013 return $str;
Martin Koegler00f429a2007-06-03 17:42:44 +02001014 } else {
1015 return decode($fallback_encoding, $str, Encode::FB_DEFAULT);
1016 }
1017}
1018
Kay Sievers232ff552005-11-24 16:56:55 +01001019# quote unsafe chars, but keep the slash, even when it's not
1020# correct, but quoted slashes look too horrible in bookmarks
1021sub esc_param {
Kay Sievers353347b2005-11-14 05:47:18 +01001022 my $str = shift;
Petr Baudisa2f3db22006-09-24 00:18:41 +02001023 $str =~ s/([^A-Za-z0-9\-_.~()\/:@])/sprintf("%%%02X", ord($1))/eg;
Kay Sievers18216712005-11-14 06:10:07 +01001024 $str =~ s/\+/%2B/g;
Kay Sieversa9e60b72005-11-14 15:15:12 +01001025 $str =~ s/ /\+/g;
Kay Sievers353347b2005-11-14 05:47:18 +01001026 return $str;
1027}
1028
Jakub Narebskif93bff82006-09-26 01:58:41 +02001029# quote unsafe chars in whole URL, so some charactrs cannot be quoted
1030sub esc_url {
1031 my $str = shift;
1032 $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
1033 $str =~ s/\+/%2B/g;
1034 $str =~ s/ /\+/g;
1035 return $str;
1036}
1037
Kay Sievers232ff552005-11-24 16:56:55 +01001038# replace invalid utf8 character with SUBSTITUTION sequence
Jakub Narebski6255ef02006-11-01 14:33:21 +01001039sub esc_html ($;%) {
Kay Sievers40c13812005-11-19 17:41:29 +01001040 my $str = shift;
Jakub Narebski6255ef02006-11-01 14:33:21 +01001041 my %opts = @_;
1042
Martin Koegler00f429a2007-06-03 17:42:44 +02001043 $str = to_utf8($str);
Li Yangc390ae92007-03-06 11:58:56 +08001044 $str = $cgi->escapeHTML($str);
Jakub Narebski6255ef02006-11-01 14:33:21 +01001045 if ($opts{'-nbsp'}) {
1046 $str =~ s/ /&nbsp;/g;
1047 }
Junio C Hamano25ffbb22006-11-08 15:11:10 -08001048 $str =~ s|([[:cntrl:]])|(($1 ne "\t") ? quot_cec($1) : $1)|eg;
Kay Sievers40c13812005-11-19 17:41:29 +01001049 return $str;
1050}
1051
Jakub Narebski391862e2006-11-25 09:43:59 +01001052# quote control characters and escape filename to HTML
1053sub esc_path {
1054 my $str = shift;
1055 my %opts = @_;
1056
Martin Koegler00f429a2007-06-03 17:42:44 +02001057 $str = to_utf8($str);
Li Yangc390ae92007-03-06 11:58:56 +08001058 $str = $cgi->escapeHTML($str);
Jakub Narebski391862e2006-11-25 09:43:59 +01001059 if ($opts{'-nbsp'}) {
1060 $str =~ s/ /&nbsp;/g;
1061 }
1062 $str =~ s|([[:cntrl:]])|quot_cec($1)|eg;
1063 return $str;
1064}
1065
1066# Make control characters "printable", using character escape codes (CEC)
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001067sub quot_cec {
1068 my $cntrl = shift;
Jakub Narebskic84c4832008-02-17 18:48:13 +01001069 my %opts = @_;
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001070 my %es = ( # character escape codes, aka escape sequences
Jakub Narebskic84c4832008-02-17 18:48:13 +01001071 "\t" => '\t', # tab (HT)
1072 "\n" => '\n', # line feed (LF)
1073 "\r" => '\r', # carrige return (CR)
1074 "\f" => '\f', # form feed (FF)
1075 "\b" => '\b', # backspace (BS)
1076 "\a" => '\a', # alarm (bell) (BEL)
1077 "\e" => '\e', # escape (ESC)
1078 "\013" => '\v', # vertical tab (VT)
1079 "\000" => '\0', # nul character (NUL)
1080 );
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001081 my $chr = ( (exists $es{$cntrl})
1082 ? $es{$cntrl}
Petr Baudis25dfd172008-10-01 22:11:54 +02001083 : sprintf('\%2x', ord($cntrl)) );
Jakub Narebskic84c4832008-02-17 18:48:13 +01001084 if ($opts{-nohtml}) {
1085 return $chr;
1086 } else {
1087 return "<span class=\"cntrl\">$chr</span>";
1088 }
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001089}
1090
Jakub Narebski391862e2006-11-25 09:43:59 +01001091# Alternatively use unicode control pictures codepoints,
1092# Unicode "printable representation" (PR)
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001093sub quot_upr {
1094 my $cntrl = shift;
Jakub Narebskic84c4832008-02-17 18:48:13 +01001095 my %opts = @_;
1096
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001097 my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
Jakub Narebskic84c4832008-02-17 18:48:13 +01001098 if ($opts{-nohtml}) {
1099 return $chr;
1100 } else {
1101 return "<span class=\"cntrl\">$chr</span>";
1102 }
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001103}
1104
Kay Sievers232ff552005-11-24 16:56:55 +01001105# git may return quoted and escaped filenames
1106sub unquote {
1107 my $str = shift;
Jakub Narebski403d0902006-11-08 11:48:56 +01001108
1109 sub unq {
1110 my $seq = shift;
1111 my %es = ( # character escape codes, aka escape sequences
1112 't' => "\t", # tab (HT, TAB)
1113 'n' => "\n", # newline (NL)
1114 'r' => "\r", # return (CR)
1115 'f' => "\f", # form feed (FF)
1116 'b' => "\b", # backspace (BS)
1117 'a' => "\a", # alarm (bell) (BEL)
1118 'e' => "\e", # escape (ESC)
1119 'v' => "\013", # vertical tab (VT)
1120 );
1121
1122 if ($seq =~ m/^[0-7]{1,3}$/) {
1123 # octal char sequence
1124 return chr(oct($seq));
1125 } elsif (exists $es{$seq}) {
1126 # C escape sequence, aka character escape code
Jakub Narebskic84c4832008-02-17 18:48:13 +01001127 return $es{$seq};
Jakub Narebski403d0902006-11-08 11:48:56 +01001128 }
1129 # quoted ordinary character
1130 return $seq;
1131 }
1132
Kay Sievers232ff552005-11-24 16:56:55 +01001133 if ($str =~ m/^"(.*)"$/) {
Jakub Narebski403d0902006-11-08 11:48:56 +01001134 # needs unquoting
Kay Sievers232ff552005-11-24 16:56:55 +01001135 $str = $1;
Jakub Narebski403d0902006-11-08 11:48:56 +01001136 $str =~ s/\\([^0-7]|[0-7]{1,3})/unq($1)/eg;
Kay Sievers232ff552005-11-24 16:56:55 +01001137 }
1138 return $str;
1139}
1140
Jakub Narebskif16db172006-08-06 02:08:31 +02001141# escape tabs (convert tabs to spaces)
1142sub untabify {
1143 my $line = shift;
1144
1145 while ((my $pos = index($line, "\t")) != -1) {
1146 if (my $count = (8 - ($pos % 8))) {
1147 my $spaces = ' ' x $count;
1148 $line =~ s/\t/$spaces/;
1149 }
1150 }
1151
1152 return $line;
1153}
1154
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +02001155sub project_in_list {
1156 my $project = shift;
1157 my @list = git_get_projects_list();
1158 return @list && scalar(grep { $_->{'path'} eq $project } @list);
1159}
1160
Jakub Narebski717b8312006-07-31 21:22:15 +02001161## ----------------------------------------------------------------------
1162## HTML aware string manipulation
1163
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001164# Try to chop given string on a word boundary between position
1165# $len and $len+$add_len. If there is no word boundary there,
1166# chop at $len+$add_len. Do not chop if chopped part plus ellipsis
1167# (marking chopped part) would be longer than given string.
Jakub Narebski717b8312006-07-31 21:22:15 +02001168sub chop_str {
1169 my $str = shift;
1170 my $len = shift;
1171 my $add_len = shift || 10;
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001172 my $where = shift || 'right'; # 'left' | 'center' | 'right'
Jakub Narebski717b8312006-07-31 21:22:15 +02001173
Anders Waldenborgdee27752008-05-21 13:44:43 +02001174 # Make sure perl knows it is utf8 encoded so we don't
1175 # cut in the middle of a utf8 multibyte char.
1176 $str = to_utf8($str);
1177
Jakub Narebski717b8312006-07-31 21:22:15 +02001178 # allow only $len chars, but don't cut a word if it would fit in $add_len
1179 # 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 +01001180 # remove chopped character entities entirely
1181
1182 # when chopping in the middle, distribute $len into left and right part
1183 # return early if chopping wouldn't make string shorter
1184 if ($where eq 'center') {
1185 return $str if ($len + 5 >= length($str)); # filler is length 5
1186 $len = int($len/2);
1187 } else {
1188 return $str if ($len + 4 >= length($str)); # filler is length 4
Jakub Narebski717b8312006-07-31 21:22:15 +02001189 }
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001190
1191 # regexps: ending and beginning with word part up to $add_len
1192 my $endre = qr/.{$len}\w{0,$add_len}/;
1193 my $begre = qr/\w{0,$add_len}.{$len}/;
1194
1195 if ($where eq 'left') {
1196 $str =~ m/^(.*?)($begre)$/;
1197 my ($lead, $body) = ($1, $2);
1198 if (length($lead) > 4) {
1199 $body =~ s/^[^;]*;// if ($lead =~ m/&[^;]*$/);
1200 $lead = " ...";
1201 }
1202 return "$lead$body";
1203
1204 } elsif ($where eq 'center') {
1205 $str =~ m/^($endre)(.*)$/;
1206 my ($left, $str) = ($1, $2);
1207 $str =~ m/^(.*?)($begre)$/;
1208 my ($mid, $right) = ($1, $2);
1209 if (length($mid) > 5) {
1210 $left =~ s/&[^;]*$//;
1211 $right =~ s/^[^;]*;// if ($mid =~ m/&[^;]*$/);
1212 $mid = " ... ";
1213 }
1214 return "$left$mid$right";
1215
1216 } else {
1217 $str =~ m/^($endre)(.*)$/;
1218 my $body = $1;
1219 my $tail = $2;
1220 if (length($tail) > 4) {
1221 $body =~ s/&[^;]*$//;
1222 $tail = "... ";
1223 }
1224 return "$body$tail";
1225 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001226}
1227
David Symondsce58ec92007-10-23 11:31:22 +10001228# takes the same arguments as chop_str, but also wraps a <span> around the
1229# result with a title attribute if it does get chopped. Additionally, the
1230# string is HTML-escaped.
1231sub chop_and_escape_str {
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001232 my ($str) = @_;
David Symondsce58ec92007-10-23 11:31:22 +10001233
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001234 my $chopped = chop_str(@_);
David Symondsce58ec92007-10-23 11:31:22 +10001235 if ($chopped eq $str) {
1236 return esc_html($chopped);
1237 } else {
Jakub Narebski850b90a2008-02-16 23:07:46 +01001238 $str =~ s/([[:cntrl:]])/?/g;
1239 return $cgi->span({-title=>$str}, esc_html($chopped));
David Symondsce58ec92007-10-23 11:31:22 +10001240 }
1241}
1242
Jakub Narebski717b8312006-07-31 21:22:15 +02001243## ----------------------------------------------------------------------
1244## functions returning short strings
1245
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00001246# CSS class for given age value (in seconds)
1247sub age_class {
1248 my $age = shift;
1249
Jakub Narebski785cdea2007-05-13 12:39:22 +02001250 if (!defined $age) {
1251 return "noage";
1252 } elsif ($age < 60*60*2) {
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00001253 return "age0";
1254 } elsif ($age < 60*60*24*2) {
1255 return "age1";
1256 } else {
1257 return "age2";
1258 }
1259}
1260
Jakub Narebski717b8312006-07-31 21:22:15 +02001261# convert age in seconds to "nn units ago" string
1262sub age_string {
1263 my $age = shift;
1264 my $age_str;
1265
1266 if ($age > 60*60*24*365*2) {
1267 $age_str = (int $age/60/60/24/365);
1268 $age_str .= " years ago";
1269 } elsif ($age > 60*60*24*(365/12)*2) {
1270 $age_str = int $age/60/60/24/(365/12);
1271 $age_str .= " months ago";
1272 } elsif ($age > 60*60*24*7*2) {
1273 $age_str = int $age/60/60/24/7;
1274 $age_str .= " weeks ago";
1275 } elsif ($age > 60*60*24*2) {
1276 $age_str = int $age/60/60/24;
1277 $age_str .= " days ago";
1278 } elsif ($age > 60*60*2) {
1279 $age_str = int $age/60/60;
1280 $age_str .= " hours ago";
1281 } elsif ($age > 60*2) {
1282 $age_str = int $age/60;
1283 $age_str .= " min ago";
1284 } elsif ($age > 2) {
1285 $age_str = int $age;
1286 $age_str .= " sec ago";
1287 } else {
1288 $age_str .= " right now";
1289 }
1290 return $age_str;
1291}
1292
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001293use constant {
1294 S_IFINVALID => 0030000,
1295 S_IFGITLINK => 0160000,
1296};
1297
1298# submodule/subproject, a commit object reference
1299sub S_ISGITLINK($) {
1300 my $mode = shift;
1301
1302 return (($mode & S_IFMT) == S_IFGITLINK)
1303}
1304
Jakub Narebski717b8312006-07-31 21:22:15 +02001305# convert file mode in octal to symbolic file mode string
1306sub mode_str {
1307 my $mode = oct shift;
1308
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001309 if (S_ISGITLINK($mode)) {
1310 return 'm---------';
1311 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001312 return 'drwxr-xr-x';
1313 } elsif (S_ISLNK($mode)) {
1314 return 'lrwxrwxrwx';
1315 } elsif (S_ISREG($mode)) {
1316 # git cares only about the executable bit
1317 if ($mode & S_IXUSR) {
1318 return '-rwxr-xr-x';
1319 } else {
1320 return '-rw-r--r--';
1321 };
1322 } else {
1323 return '----------';
1324 }
1325}
1326
1327# convert file mode in octal to file type string
1328sub file_type {
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02001329 my $mode = shift;
1330
1331 if ($mode !~ m/^[0-7]+$/) {
1332 return $mode;
1333 } else {
1334 $mode = oct $mode;
1335 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001336
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001337 if (S_ISGITLINK($mode)) {
1338 return "submodule";
1339 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001340 return "directory";
1341 } elsif (S_ISLNK($mode)) {
1342 return "symlink";
1343 } elsif (S_ISREG($mode)) {
1344 return "file";
1345 } else {
1346 return "unknown";
1347 }
1348}
1349
Jakub Narebski744d0ac2006-11-08 17:59:41 +01001350# convert file mode in octal to file type description string
1351sub file_type_long {
1352 my $mode = shift;
1353
1354 if ($mode !~ m/^[0-7]+$/) {
1355 return $mode;
1356 } else {
1357 $mode = oct $mode;
1358 }
1359
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001360 if (S_ISGITLINK($mode)) {
1361 return "submodule";
1362 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski744d0ac2006-11-08 17:59:41 +01001363 return "directory";
1364 } elsif (S_ISLNK($mode)) {
1365 return "symlink";
1366 } elsif (S_ISREG($mode)) {
1367 if ($mode & S_IXUSR) {
1368 return "executable";
1369 } else {
1370 return "file";
1371 };
1372 } else {
1373 return "unknown";
1374 }
1375}
1376
1377
Jakub Narebski717b8312006-07-31 21:22:15 +02001378## ----------------------------------------------------------------------
1379## functions returning short HTML fragments, or transforming HTML fragments
Pavel Roskin3dff5372007-02-03 23:49:16 -05001380## which don't belong to other sections
Jakub Narebski717b8312006-07-31 21:22:15 +02001381
Junio C Hamano225932e2006-11-09 00:57:13 -08001382# format line of commit message.
Jakub Narebski717b8312006-07-31 21:22:15 +02001383sub format_log_line_html {
1384 my $line = shift;
1385
Junio C Hamano225932e2006-11-09 00:57:13 -08001386 $line = esc_html($line, -nbsp=>1);
Jakub Narebskiccb04f92009-02-06 10:12:41 +01001387 if ($line =~ m/\b([0-9a-fA-F]{8,40})\b/) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001388 my $hash_text = $1;
Jakub Narebskibfe21912006-12-10 13:25:49 +01001389 my $link =
1390 $cgi->a({-href => href(action=>"object", hash=>$hash_text),
1391 -class => "text"}, $hash_text);
1392 $line =~ s/$hash_text/$link/;
Jakub Narebski717b8312006-07-31 21:22:15 +02001393 }
1394 return $line;
1395}
1396
1397# format marker of refs pointing to given object
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02001398
1399# the destination action is chosen based on object type and current context:
1400# - for annotated tags, we choose the tag view unless it's the current view
1401# already, in which case we go to shortlog view
1402# - for other refs, we keep the current view if we're in history, shortlog or
1403# log view, and select shortlog otherwise
Jakub Narebski847e01f2006-08-14 02:05:47 +02001404sub format_ref_marker {
Jakub Narebski717b8312006-07-31 21:22:15 +02001405 my ($refs, $id) = @_;
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001406 my $markers = '';
Jakub Narebski717b8312006-07-31 21:22:15 +02001407
1408 if (defined $refs->{$id}) {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001409 foreach my $ref (@{$refs->{$id}}) {
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02001410 # this code exploits the fact that non-lightweight tags are the
1411 # only indirect objects, and that they are the only objects for which
1412 # we want to use tag instead of shortlog as action
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001413 my ($type, $name) = qw();
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02001414 my $indirect = ($ref =~ s/\^\{\}$//);
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001415 # e.g. tags/v2.6.11 or heads/next
1416 if ($ref =~ m!^(.*?)s?/(.*)$!) {
1417 $type = $1;
1418 $name = $2;
1419 } else {
1420 $type = "ref";
1421 $name = $ref;
1422 }
1423
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02001424 my $class = $type;
1425 $class .= " indirect" if $indirect;
1426
1427 my $dest_action = "shortlog";
1428
1429 if ($indirect) {
1430 $dest_action = "tag" unless $action eq "tag";
1431 } elsif ($action =~ /^(history|(short)?log)$/) {
1432 $dest_action = $action;
1433 }
1434
1435 my $dest = "";
1436 $dest .= "refs/" unless $ref =~ m!^refs/!;
1437 $dest .= $ref;
1438
1439 my $link = $cgi->a({
1440 -href => href(
1441 action=>$dest_action,
1442 hash=>$dest
1443 )}, $name);
1444
1445 $markers .= " <span class=\"$class\" title=\"$ref\">" .
1446 $link . "</span>";
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001447 }
1448 }
1449
1450 if ($markers) {
1451 return ' <span class="refs">'. $markers . '</span>';
Jakub Narebski717b8312006-07-31 21:22:15 +02001452 } else {
1453 return "";
1454 }
1455}
1456
Jakub Narebski17d07442006-08-14 02:08:27 +02001457# format, perhaps shortened and with markers, title line
1458sub format_subject_html {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02001459 my ($long, $short, $href, $extra) = @_;
Jakub Narebski17d07442006-08-14 02:08:27 +02001460 $extra = '' unless defined($extra);
1461
1462 if (length($short) < length($long)) {
Jakub Narebski7c278012006-08-22 12:02:48 +02001463 return $cgi->a({-href => $href, -class => "list subject",
Martin Koegler00f429a2007-06-03 17:42:44 +02001464 -title => to_utf8($long)},
Jakub Narebski17d07442006-08-14 02:08:27 +02001465 esc_html($short) . $extra);
1466 } else {
Jakub Narebski7c278012006-08-22 12:02:48 +02001467 return $cgi->a({-href => $href, -class => "list subject"},
Jakub Narebski17d07442006-08-14 02:08:27 +02001468 esc_html($long) . $extra);
1469 }
1470}
1471
Jakub Narebski90921742007-06-08 13:27:42 +02001472# format git diff header line, i.e. "diff --(git|combined|cc) ..."
1473sub format_git_diff_header_line {
1474 my $line = shift;
1475 my $diffinfo = shift;
1476 my ($from, $to) = @_;
1477
1478 if ($diffinfo->{'nparents'}) {
1479 # combined diff
1480 $line =~ s!^(diff (.*?) )"?.*$!$1!;
1481 if ($to->{'href'}) {
1482 $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
1483 esc_path($to->{'file'}));
1484 } else { # file was deleted (no href)
1485 $line .= esc_path($to->{'file'});
1486 }
1487 } else {
1488 # "ordinary" diff
1489 $line =~ s!^(diff (.*?) )"?a/.*$!$1!;
1490 if ($from->{'href'}) {
1491 $line .= $cgi->a({-href => $from->{'href'}, -class => "path"},
1492 'a/' . esc_path($from->{'file'}));
1493 } else { # file was added (no href)
1494 $line .= 'a/' . esc_path($from->{'file'});
1495 }
1496 $line .= ' ';
1497 if ($to->{'href'}) {
1498 $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
1499 'b/' . esc_path($to->{'file'}));
1500 } else { # file was deleted
1501 $line .= 'b/' . esc_path($to->{'file'});
1502 }
1503 }
1504
1505 return "<div class=\"diff header\">$line</div>\n";
1506}
1507
1508# format extended diff header line, before patch itself
1509sub format_extended_diff_header_line {
1510 my $line = shift;
1511 my $diffinfo = shift;
1512 my ($from, $to) = @_;
1513
1514 # match <path>
1515 if ($line =~ s!^((copy|rename) from ).*$!$1! && $from->{'href'}) {
1516 $line .= $cgi->a({-href=>$from->{'href'}, -class=>"path"},
1517 esc_path($from->{'file'}));
1518 }
1519 if ($line =~ s!^((copy|rename) to ).*$!$1! && $to->{'href'}) {
1520 $line .= $cgi->a({-href=>$to->{'href'}, -class=>"path"},
1521 esc_path($to->{'file'}));
1522 }
1523 # match single <mode>
1524 if ($line =~ m/\s(\d{6})$/) {
1525 $line .= '<span class="info"> (' .
1526 file_type_long($1) .
1527 ')</span>';
1528 }
1529 # match <hash>
1530 if ($line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
1531 # can match only for combined diff
1532 $line = 'index ';
1533 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
1534 if ($from->{'href'}[$i]) {
1535 $line .= $cgi->a({-href=>$from->{'href'}[$i],
1536 -class=>"hash"},
1537 substr($diffinfo->{'from_id'}[$i],0,7));
1538 } else {
1539 $line .= '0' x 7;
1540 }
1541 # separator
1542 $line .= ',' if ($i < $diffinfo->{'nparents'} - 1);
1543 }
1544 $line .= '..';
1545 if ($to->{'href'}) {
1546 $line .= $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
1547 substr($diffinfo->{'to_id'},0,7));
1548 } else {
1549 $line .= '0' x 7;
1550 }
1551
1552 } elsif ($line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
1553 # can match only for ordinary diff
1554 my ($from_link, $to_link);
1555 if ($from->{'href'}) {
1556 $from_link = $cgi->a({-href=>$from->{'href'}, -class=>"hash"},
1557 substr($diffinfo->{'from_id'},0,7));
1558 } else {
1559 $from_link = '0' x 7;
1560 }
1561 if ($to->{'href'}) {
1562 $to_link = $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
1563 substr($diffinfo->{'to_id'},0,7));
1564 } else {
1565 $to_link = '0' x 7;
1566 }
1567 my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
1568 $line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
1569 }
1570
1571 return $line . "<br/>\n";
1572}
1573
1574# format from-file/to-file diff header
1575sub format_diff_from_to_header {
Jakub Narebski91af4ce2007-06-08 13:32:44 +02001576 my ($from_line, $to_line, $diffinfo, $from, $to, @parents) = @_;
Jakub Narebski90921742007-06-08 13:27:42 +02001577 my $line;
1578 my $result = '';
1579
1580 $line = $from_line;
1581 #assert($line =~ m/^---/) if DEBUG;
Jakub Narebskideaa01a2007-06-08 13:29:49 +02001582 # no extra formatting for "^--- /dev/null"
1583 if (! $diffinfo->{'nparents'}) {
1584 # ordinary (single parent) diff
1585 if ($line =~ m!^--- "?a/!) {
1586 if ($from->{'href'}) {
1587 $line = '--- a/' .
1588 $cgi->a({-href=>$from->{'href'}, -class=>"path"},
1589 esc_path($from->{'file'}));
1590 } else {
1591 $line = '--- a/' .
1592 esc_path($from->{'file'});
1593 }
1594 }
1595 $result .= qq!<div class="diff from_file">$line</div>\n!;
1596
1597 } else {
1598 # combined diff (merge commit)
1599 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
1600 if ($from->{'href'}[$i]) {
1601 $line = '--- ' .
Jakub Narebski91af4ce2007-06-08 13:32:44 +02001602 $cgi->a({-href=>href(action=>"blobdiff",
1603 hash_parent=>$diffinfo->{'from_id'}[$i],
1604 hash_parent_base=>$parents[$i],
1605 file_parent=>$from->{'file'}[$i],
1606 hash=>$diffinfo->{'to_id'},
1607 hash_base=>$hash,
1608 file_name=>$to->{'file'}),
1609 -class=>"path",
1610 -title=>"diff" . ($i+1)},
1611 $i+1) .
1612 '/' .
Jakub Narebskideaa01a2007-06-08 13:29:49 +02001613 $cgi->a({-href=>$from->{'href'}[$i], -class=>"path"},
1614 esc_path($from->{'file'}[$i]));
1615 } else {
1616 $line = '--- /dev/null';
1617 }
1618 $result .= qq!<div class="diff from_file">$line</div>\n!;
Jakub Narebski90921742007-06-08 13:27:42 +02001619 }
1620 }
Jakub Narebski90921742007-06-08 13:27:42 +02001621
1622 $line = $to_line;
1623 #assert($line =~ m/^\+\+\+/) if DEBUG;
1624 # no extra formatting for "^+++ /dev/null"
1625 if ($line =~ m!^\+\+\+ "?b/!) {
1626 if ($to->{'href'}) {
1627 $line = '+++ b/' .
1628 $cgi->a({-href=>$to->{'href'}, -class=>"path"},
1629 esc_path($to->{'file'}));
1630 } else {
1631 $line = '+++ b/' .
1632 esc_path($to->{'file'});
1633 }
1634 }
1635 $result .= qq!<div class="diff to_file">$line</div>\n!;
1636
1637 return $result;
1638}
1639
Jakub Narebskicd030c32007-06-08 13:33:28 +02001640# create note for patch simplified by combined diff
1641sub format_diff_cc_simplified {
1642 my ($diffinfo, @parents) = @_;
1643 my $result = '';
1644
1645 $result .= "<div class=\"diff header\">" .
1646 "diff --cc ";
1647 if (!is_deleted($diffinfo)) {
1648 $result .= $cgi->a({-href => href(action=>"blob",
1649 hash_base=>$hash,
1650 hash=>$diffinfo->{'to_id'},
1651 file_name=>$diffinfo->{'to_file'}),
1652 -class => "path"},
1653 esc_path($diffinfo->{'to_file'}));
1654 } else {
1655 $result .= esc_path($diffinfo->{'to_file'});
1656 }
1657 $result .= "</div>\n" . # class="diff header"
1658 "<div class=\"diff nodifferences\">" .
1659 "Simple merge" .
1660 "</div>\n"; # class="diff nodifferences"
1661
1662 return $result;
1663}
1664
Jakub Narebski90921742007-06-08 13:27:42 +02001665# format patch (diff) line (not to be used for diff headers)
Jakub Narebskieee08902006-08-24 00:15:14 +02001666sub format_diff_line {
1667 my $line = shift;
Jakub Narebski59e3b142006-11-18 23:35:40 +01001668 my ($from, $to) = @_;
Jakub Narebskieee08902006-08-24 00:15:14 +02001669 my $diff_class = "";
1670
1671 chomp $line;
1672
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02001673 if ($from && $to && ref($from->{'href'}) eq "ARRAY") {
1674 # combined diff
1675 my $prefix = substr($line, 0, scalar @{$from->{'href'}});
1676 if ($line =~ m/^\@{3}/) {
1677 $diff_class = " chunk_header";
1678 } elsif ($line =~ m/^\\/) {
1679 $diff_class = " incomplete";
1680 } elsif ($prefix =~ tr/+/+/) {
1681 $diff_class = " add";
1682 } elsif ($prefix =~ tr/-/-/) {
1683 $diff_class = " rem";
1684 }
1685 } else {
1686 # assume ordinary diff
1687 my $char = substr($line, 0, 1);
1688 if ($char eq '+') {
1689 $diff_class = " add";
1690 } elsif ($char eq '-') {
1691 $diff_class = " rem";
1692 } elsif ($char eq '@') {
1693 $diff_class = " chunk_header";
1694 } elsif ($char eq "\\") {
1695 $diff_class = " incomplete";
1696 }
Jakub Narebskieee08902006-08-24 00:15:14 +02001697 }
1698 $line = untabify($line);
Jakub Narebski59e3b142006-11-18 23:35:40 +01001699 if ($from && $to && $line =~ m/^\@{2} /) {
1700 my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
1701 $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
1702
1703 $from_lines = 0 unless defined $from_lines;
1704 $to_lines = 0 unless defined $to_lines;
1705
1706 if ($from->{'href'}) {
1707 $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
1708 -class=>"list"}, $from_text);
1709 }
1710 if ($to->{'href'}) {
1711 $to_text = $cgi->a({-href=>"$to->{'href'}#l$to_start",
1712 -class=>"list"}, $to_text);
1713 }
1714 $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
1715 "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
1716 return "<div class=\"diff$diff_class\">$line</div>\n";
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02001717 } elsif ($from && $to && $line =~ m/^\@{3}/) {
1718 my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
1719 my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
1720
1721 @from_text = split(' ', $ranges);
1722 for (my $i = 0; $i < @from_text; ++$i) {
1723 ($from_start[$i], $from_nlines[$i]) =
1724 (split(',', substr($from_text[$i], 1)), 0);
1725 }
1726
1727 $to_text = pop @from_text;
1728 $to_start = pop @from_start;
1729 $to_nlines = pop @from_nlines;
1730
1731 $line = "<span class=\"chunk_info\">$prefix ";
1732 for (my $i = 0; $i < @from_text; ++$i) {
1733 if ($from->{'href'}[$i]) {
1734 $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
1735 -class=>"list"}, $from_text[$i]);
1736 } else {
1737 $line .= $from_text[$i];
1738 }
1739 $line .= " ";
1740 }
1741 if ($to->{'href'}) {
1742 $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
1743 -class=>"list"}, $to_text);
1744 } else {
1745 $line .= $to_text;
1746 }
1747 $line .= " $prefix</span>" .
1748 "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
1749 return "<div class=\"diff$diff_class\">$line</div>\n";
Jakub Narebski59e3b142006-11-18 23:35:40 +01001750 }
Jakub Narebski6255ef02006-11-01 14:33:21 +01001751 return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02001752}
1753
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001754# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
1755# linked. Pass the hash of the tree/commit to snapshot.
1756sub format_snapshot_links {
1757 my ($hash) = @_;
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001758 my $num_fmts = @snapshot_fmts;
1759 if ($num_fmts > 1) {
1760 # A parenthesized list of links bearing format names.
Jakub Narebskia7817852007-07-22 23:41:20 +02001761 # e.g. "snapshot (_tar.gz_ _zip_)"
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001762 return "snapshot (" . join(' ', map
1763 $cgi->a({
1764 -href => href(
1765 action=>"snapshot",
1766 hash=>$hash,
1767 snapshot_format=>$_
1768 )
1769 }, $known_snapshot_formats{$_}{'display'})
1770 , @snapshot_fmts) . ")";
1771 } elsif ($num_fmts == 1) {
1772 # A single "snapshot" link whose tooltip bears the format name.
Jakub Narebskia7817852007-07-22 23:41:20 +02001773 # i.e. "_snapshot_"
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001774 my ($fmt) = @snapshot_fmts;
Jakub Narebskia7817852007-07-22 23:41:20 +02001775 return
1776 $cgi->a({
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001777 -href => href(
1778 action=>"snapshot",
1779 hash=>$hash,
1780 snapshot_format=>$fmt
1781 ),
1782 -title => "in format: $known_snapshot_formats{$fmt}{'display'}"
1783 }, "snapshot");
1784 } else { # $num_fmts == 0
1785 return undef;
1786 }
1787}
1788
Jakub Narebski35621982008-04-20 22:09:48 +02001789## ......................................................................
1790## functions returning values to be passed, perhaps after some
1791## transformation, to other functions; e.g. returning arguments to href()
1792
1793# returns hash to be passed to href to generate gitweb URL
1794# in -title key it returns description of link
1795sub get_feed_info {
1796 my $format = shift || 'Atom';
1797 my %res = (action => lc($format));
1798
1799 # feed links are possible only for project views
1800 return unless (defined $project);
1801 # some views should link to OPML, or to generic project feed,
1802 # or don't have specific feed yet (so they should use generic)
1803 return if ($action =~ /^(?:tags|heads|forks|tag|search)$/x);
1804
1805 my $branch;
1806 # branches refs uses 'refs/heads/' prefix (fullname) to differentiate
1807 # from tag links; this also makes possible to detect branch links
1808 if ((defined $hash_base && $hash_base =~ m!^refs/heads/(.*)$!) ||
1809 (defined $hash && $hash =~ m!^refs/heads/(.*)$!)) {
1810 $branch = $1;
1811 }
1812 # find log type for feed description (title)
1813 my $type = 'log';
1814 if (defined $file_name) {
1815 $type = "history of $file_name";
1816 $type .= "/" if ($action eq 'tree');
1817 $type .= " on '$branch'" if (defined $branch);
1818 } else {
1819 $type = "log of $branch" if (defined $branch);
1820 }
1821
1822 $res{-title} = $type;
1823 $res{'hash'} = (defined $branch ? "refs/heads/$branch" : undef);
1824 $res{'file_name'} = $file_name;
1825
1826 return %res;
1827}
1828
Jakub Narebski717b8312006-07-31 21:22:15 +02001829## ----------------------------------------------------------------------
1830## git utility subroutines, invoking git commands
1831
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001832# returns path to the core git executable and the --git-dir parameter as list
1833sub git_cmd {
1834 return $GIT, '--git-dir='.$git_dir;
1835}
1836
Lea Wiemann516381d2008-06-17 23:46:35 +02001837# quote the given arguments for passing them to the shell
1838# quote_command("command", "arg 1", "arg with ' and ! characters")
1839# => "'command' 'arg 1' 'arg with '\'' and '\!' characters'"
1840# Try to avoid using this function wherever possible.
1841sub quote_command {
1842 return join(' ',
1843 map( { my $a = $_; $a =~ s/(['!])/'\\$1'/g; "'$a'" } @_ ));
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001844}
1845
Jakub Narebski717b8312006-07-31 21:22:15 +02001846# get HEAD ref of given project as hash
Jakub Narebski847e01f2006-08-14 02:05:47 +02001847sub git_get_head_hash {
Jakub Narebski717b8312006-07-31 21:22:15 +02001848 my $project = shift;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001849 my $o_git_dir = $git_dir;
Jakub Narebski717b8312006-07-31 21:22:15 +02001850 my $retval = undef;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001851 $git_dir = "$projectroot/$project";
1852 if (open my $fd, "-|", git_cmd(), "rev-parse", "--verify", "HEAD") {
Jakub Narebski717b8312006-07-31 21:22:15 +02001853 my $head = <$fd>;
1854 close $fd;
1855 if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) {
1856 $retval = $1;
1857 }
1858 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001859 if (defined $o_git_dir) {
1860 $git_dir = $o_git_dir;
Jakub Narebski717b8312006-07-31 21:22:15 +02001861 }
1862 return $retval;
1863}
1864
1865# get type of given object
1866sub git_get_type {
1867 my $hash = shift;
1868
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001869 open my $fd, "-|", git_cmd(), "cat-file", '-t', $hash or return;
Jakub Narebski717b8312006-07-31 21:22:15 +02001870 my $type = <$fd>;
1871 close $fd or return;
1872 chomp $type;
1873 return $type;
1874}
1875
Jakub Narebskib2019272007-11-03 00:41:19 +01001876# repository configuration
1877our $config_file = '';
1878our %config;
1879
1880# store multiple values for single key as anonymous array reference
1881# single values stored directly in the hash, not as [ <value> ]
1882sub hash_set_multi {
1883 my ($hash, $key, $value) = @_;
1884
1885 if (!exists $hash->{$key}) {
1886 $hash->{$key} = $value;
1887 } elsif (!ref $hash->{$key}) {
1888 $hash->{$key} = [ $hash->{$key}, $value ];
1889 } else {
1890 push @{$hash->{$key}}, $value;
1891 }
1892}
1893
1894# return hash of git project configuration
1895# optionally limited to some section, e.g. 'gitweb'
1896sub git_parse_project_config {
1897 my $section_regexp = shift;
1898 my %config;
1899
1900 local $/ = "\0";
1901
1902 open my $fh, "-|", git_cmd(), "config", '-z', '-l',
1903 or return;
1904
1905 while (my $keyval = <$fh>) {
1906 chomp $keyval;
1907 my ($key, $value) = split(/\n/, $keyval, 2);
1908
1909 hash_set_multi(\%config, $key, $value)
1910 if (!defined $section_regexp || $key =~ /^(?:$section_regexp)\./o);
1911 }
1912 close $fh;
1913
1914 return %config;
1915}
1916
1917# convert config value to boolean, 'true' or 'false'
1918# no value, number > 0, 'true' and 'yes' values are true
1919# rest of values are treated as false (never as error)
1920sub config_to_bool {
1921 my $val = shift;
1922
1923 # strip leading and trailing whitespace
1924 $val =~ s/^\s+//;
1925 $val =~ s/\s+$//;
1926
1927 return (!defined $val || # section.key
1928 ($val =~ /^\d+$/ && $val) || # section.key = 1
1929 ($val =~ /^(?:true|yes)$/i)); # section.key = true
1930}
1931
1932# convert config value to simple decimal number
1933# an optional value suffix of 'k', 'm', or 'g' will cause the value
1934# to be multiplied by 1024, 1048576, or 1073741824
1935sub config_to_int {
1936 my $val = shift;
1937
1938 # strip leading and trailing whitespace
1939 $val =~ s/^\s+//;
1940 $val =~ s/\s+$//;
1941
1942 if (my ($num, $unit) = ($val =~ /^([0-9]*)([kmg])$/i)) {
1943 $unit = lc($unit);
1944 # unknown unit is treated as 1
1945 return $num * ($unit eq 'g' ? 1073741824 :
1946 $unit eq 'm' ? 1048576 :
1947 $unit eq 'k' ? 1024 : 1);
1948 }
1949 return $val;
1950}
1951
1952# convert config value to array reference, if needed
1953sub config_to_multi {
1954 my $val = shift;
1955
Jakub Narebskid76a5852007-12-20 10:48:09 +01001956 return ref($val) ? $val : (defined($val) ? [ $val ] : []);
Jakub Narebskib2019272007-11-03 00:41:19 +01001957}
1958
Jakub Narebski717b8312006-07-31 21:22:15 +02001959sub git_get_project_config {
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05301960 my ($key, $type) = @_;
Jakub Narebski717b8312006-07-31 21:22:15 +02001961
Jakub Narebskib2019272007-11-03 00:41:19 +01001962 # key sanity check
Jakub Narebski717b8312006-07-31 21:22:15 +02001963 return unless ($key);
1964 $key =~ s/^gitweb\.//;
1965 return if ($key =~ m/\W/);
1966
Jakub Narebskib2019272007-11-03 00:41:19 +01001967 # type sanity check
1968 if (defined $type) {
1969 $type =~ s/^--//;
1970 $type = undef
1971 unless ($type eq 'bool' || $type eq 'int');
1972 }
1973
1974 # get config
1975 if (!defined $config_file ||
1976 $config_file ne "$git_dir/config") {
1977 %config = git_parse_project_config('gitweb');
1978 $config_file = "$git_dir/config";
1979 }
1980
1981 # ensure given type
1982 if (!defined $type) {
1983 return $config{"gitweb.$key"};
1984 } elsif ($type eq 'bool') {
1985 # backward compatibility: 'git config --bool' returns true/false
1986 return config_to_bool($config{"gitweb.$key"}) ? 'true' : 'false';
1987 } elsif ($type eq 'int') {
1988 return config_to_int($config{"gitweb.$key"});
1989 }
1990 return $config{"gitweb.$key"};
Jakub Narebski717b8312006-07-31 21:22:15 +02001991}
1992
Jakub Narebski717b8312006-07-31 21:22:15 +02001993# get hash of given path at given ref
1994sub git_get_hash_by_path {
1995 my $base = shift;
1996 my $path = shift || return undef;
Jakub Narebski1d782b02006-09-21 18:09:12 +02001997 my $type = shift;
Jakub Narebski717b8312006-07-31 21:22:15 +02001998
Jakub Narebski4b02f482006-09-26 01:54:24 +02001999 $path =~ s,/+$,,;
Jakub Narebski717b8312006-07-31 21:22:15 +02002000
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002001 open my $fd, "-|", git_cmd(), "ls-tree", $base, "--", $path
Lea Wiemann074afaa2008-06-19 22:03:21 +02002002 or die_error(500, "Open git-ls-tree failed");
Jakub Narebski717b8312006-07-31 21:22:15 +02002003 my $line = <$fd>;
2004 close $fd or return undef;
2005
Jakub Narebski198a2a82007-05-12 21:16:34 +02002006 if (!defined $line) {
2007 # there is no tree or hash given by $path at $base
2008 return undef;
2009 }
2010
Jakub Narebski717b8312006-07-31 21:22:15 +02002011 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
Jakub Narebski8b4b94c2006-10-30 22:25:11 +01002012 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/;
Jakub Narebski1d782b02006-09-21 18:09:12 +02002013 if (defined $type && $type ne $2) {
2014 # type doesn't match
2015 return undef;
2016 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002017 return $3;
2018}
2019
Jakub Narebskied224de2007-05-07 01:10:04 +02002020# get path of entry with given hash at given tree-ish (ref)
2021# used to get 'from' filename for combined diff (merge commit) for renames
2022sub git_get_path_by_hash {
2023 my $base = shift || return;
2024 my $hash = shift || return;
2025
2026 local $/ = "\0";
2027
2028 open my $fd, "-|", git_cmd(), "ls-tree", '-r', '-t', '-z', $base
2029 or return undef;
2030 while (my $line = <$fd>) {
2031 chomp $line;
2032
2033 #'040000 tree 595596a6a9117ddba9fe379b6b012b558bac8423 gitweb'
2034 #'100644 blob e02e90f0429be0d2a69b76571101f20b8f75530f gitweb/README'
2035 if ($line =~ m/(?:[0-9]+) (?:.+) $hash\t(.+)$/) {
2036 close $fd;
2037 return $1;
2038 }
2039 }
2040 close $fd;
2041 return undef;
2042}
2043
Jakub Narebski717b8312006-07-31 21:22:15 +02002044## ......................................................................
2045## git utility functions, directly accessing git repository
2046
Jakub Narebski847e01f2006-08-14 02:05:47 +02002047sub git_get_project_description {
Jakub Narebski717b8312006-07-31 21:22:15 +02002048 my $path = shift;
2049
Jakub Narebski0e121a22007-11-03 00:41:20 +01002050 $git_dir = "$projectroot/$path";
Bruno Ribasc1dcf7e2008-01-30 03:37:56 -02002051 open my $fd, "$git_dir/description"
Jakub Narebski0e121a22007-11-03 00:41:20 +01002052 or return git_get_project_config('description');
Jakub Narebski717b8312006-07-31 21:22:15 +02002053 my $descr = <$fd>;
2054 close $fd;
Junio C Hamano2eb54ef2007-05-16 21:04:16 -07002055 if (defined $descr) {
2056 chomp $descr;
2057 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002058 return $descr;
2059}
2060
Petr Baudisaed93de2008-10-02 17:13:02 +02002061sub git_get_project_ctags {
2062 my $path = shift;
2063 my $ctags = {};
2064
2065 $git_dir = "$projectroot/$path";
Junio C Hamanoeee01842008-10-14 21:27:12 -07002066 unless (opendir D, "$git_dir/ctags") {
2067 return $ctags;
2068 }
2069 foreach (grep { -f $_ } map { "$git_dir/ctags/$_" } readdir(D)) {
Petr Baudisaed93de2008-10-02 17:13:02 +02002070 open CT, $_ or next;
2071 my $val = <CT>;
2072 chomp $val;
2073 close CT;
2074 my $ctag = $_; $ctag =~ s#.*/##;
2075 $ctags->{$ctag} = $val;
2076 }
Junio C Hamanoeee01842008-10-14 21:27:12 -07002077 closedir D;
Petr Baudisaed93de2008-10-02 17:13:02 +02002078 $ctags;
2079}
2080
2081sub git_populate_project_tagcloud {
2082 my $ctags = shift;
2083
2084 # First, merge different-cased tags; tags vote on casing
2085 my %ctags_lc;
2086 foreach (keys %$ctags) {
2087 $ctags_lc{lc $_}->{count} += $ctags->{$_};
2088 if (not $ctags_lc{lc $_}->{topcount}
2089 or $ctags_lc{lc $_}->{topcount} < $ctags->{$_}) {
2090 $ctags_lc{lc $_}->{topcount} = $ctags->{$_};
2091 $ctags_lc{lc $_}->{topname} = $_;
2092 }
2093 }
2094
2095 my $cloud;
2096 if (eval { require HTML::TagCloud; 1; }) {
2097 $cloud = HTML::TagCloud->new;
2098 foreach (sort keys %ctags_lc) {
2099 # Pad the title with spaces so that the cloud looks
2100 # less crammed.
2101 my $title = $ctags_lc{$_}->{topname};
2102 $title =~ s/ /&nbsp;/g;
2103 $title =~ s/^/&nbsp;/g;
2104 $title =~ s/$/&nbsp;/g;
2105 $cloud->add($title, $home_link."?by_tag=".$_, $ctags_lc{$_}->{count});
2106 }
2107 } else {
2108 $cloud = \%ctags_lc;
2109 }
2110 $cloud;
2111}
2112
2113sub git_show_project_tagcloud {
2114 my ($cloud, $count) = @_;
2115 print STDERR ref($cloud)."..\n";
2116 if (ref $cloud eq 'HTML::TagCloud') {
2117 return $cloud->html_and_css($count);
2118 } else {
2119 my @tags = sort { $cloud->{$a}->{count} <=> $cloud->{$b}->{count} } keys %$cloud;
2120 return '<p align="center">' . join (', ', map {
2121 "<a href=\"$home_link?by_tag=$_\">$cloud->{$_}->{topname}</a>"
2122 } splice(@tags, 0, $count)) . '</p>';
2123 }
2124}
2125
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02002126sub git_get_project_url_list {
2127 my $path = shift;
2128
Jakub Narebski0e121a22007-11-03 00:41:20 +01002129 $git_dir = "$projectroot/$path";
Bruno Ribas201945e2008-02-06 15:15:12 -02002130 open my $fd, "$git_dir/cloneurl"
Jakub Narebski0e121a22007-11-03 00:41:20 +01002131 or return wantarray ?
2132 @{ config_to_multi(git_get_project_config('url')) } :
2133 config_to_multi(git_get_project_config('url'));
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02002134 my @git_project_url_list = map { chomp; $_ } <$fd>;
2135 close $fd;
2136
2137 return wantarray ? @git_project_url_list : \@git_project_url_list;
2138}
2139
Jakub Narebski847e01f2006-08-14 02:05:47 +02002140sub git_get_projects_list {
Petr Baudise30496d2006-10-24 05:33:17 +02002141 my ($filter) = @_;
Jakub Narebski717b8312006-07-31 21:22:15 +02002142 my @list;
2143
Petr Baudise30496d2006-10-24 05:33:17 +02002144 $filter ||= '';
2145 $filter =~ s/\.git$//;
2146
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08002147 my $check_forks = gitweb_check_feature('forks');
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002148
Jakub Narebski717b8312006-07-31 21:22:15 +02002149 if (-d $projects_list) {
2150 # search in directory
Petr Baudise30496d2006-10-24 05:33:17 +02002151 my $dir = $projects_list . ($filter ? "/$filter" : '');
Aneesh Kumar K.V6768d6b2006-11-03 10:41:45 +05302152 # remove the trailing "/"
2153 $dir =~ s!/+$!!;
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002154 my $pfxlen = length("$dir");
Luke Luca5e9492007-10-16 20:45:25 -07002155 my $pfxdepth = ($dir =~ tr!/!!);
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002156
2157 File::Find::find({
2158 follow_fast => 1, # follow symbolic links
Junio C Hamanod20602e2007-07-27 01:23:03 -07002159 follow_skip => 2, # ignore duplicates
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002160 dangling_symlinks => 0, # ignore dangling symlinks, silently
2161 wanted => sub {
2162 # skip project-list toplevel, if we get it.
2163 return if (m!^[/.]$!);
2164 # only directories can be git repositories
2165 return unless (-d $_);
Luke Luca5e9492007-10-16 20:45:25 -07002166 # don't traverse too deep (Find is super slow on os x)
2167 if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) {
2168 $File::Find::prune = 1;
2169 return;
2170 }
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002171
2172 my $subdir = substr($File::Find::name, $pfxlen + 1);
2173 # we check related file in $projectroot
Devin Doucettefb3bb3d2008-12-27 02:39:31 -07002174 my $path = ($filter ? "$filter/" : '') . $subdir;
2175 if (check_export_ok("$projectroot/$path")) {
2176 push @list, { path => $path };
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002177 $File::Find::prune = 1;
2178 }
2179 },
2180 }, "$dir");
2181
Jakub Narebski717b8312006-07-31 21:22:15 +02002182 } elsif (-f $projects_list) {
2183 # read from file(url-encoded):
2184 # 'git%2Fgit.git Linus+Torvalds'
2185 # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
2186 # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002187 my %paths;
Jakub Narebskidd1ad5f2006-09-26 01:56:17 +02002188 open my ($fd), $projects_list or return;
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002189 PROJECT:
Jakub Narebski717b8312006-07-31 21:22:15 +02002190 while (my $line = <$fd>) {
2191 chomp $line;
2192 my ($path, $owner) = split ' ', $line;
2193 $path = unescape($path);
2194 $owner = unescape($owner);
2195 if (!defined $path) {
2196 next;
2197 }
Junio C Hamano83ee94c2006-11-07 22:37:17 -08002198 if ($filter ne '') {
2199 # looking for forks;
2200 my $pfx = substr($path, 0, length($filter));
2201 if ($pfx ne $filter) {
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002202 next PROJECT;
Junio C Hamano83ee94c2006-11-07 22:37:17 -08002203 }
2204 my $sfx = substr($path, length($filter));
2205 if ($sfx !~ /^\/.*\.git$/) {
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002206 next PROJECT;
2207 }
2208 } elsif ($check_forks) {
2209 PATH:
2210 foreach my $filter (keys %paths) {
2211 # looking for forks;
2212 my $pfx = substr($path, 0, length($filter));
2213 if ($pfx ne $filter) {
2214 next PATH;
2215 }
2216 my $sfx = substr($path, length($filter));
2217 if ($sfx !~ /^\/.*\.git$/) {
2218 next PATH;
2219 }
2220 # is a fork, don't include it in
2221 # the list
2222 next PROJECT;
Junio C Hamano83ee94c2006-11-07 22:37:17 -08002223 }
2224 }
Junio C Hamano2172ce42006-10-03 02:30:47 -07002225 if (check_export_ok("$projectroot/$path")) {
Jakub Narebski717b8312006-07-31 21:22:15 +02002226 my $pr = {
2227 path => $path,
Martin Koegler00f429a2007-06-03 17:42:44 +02002228 owner => to_utf8($owner),
Jakub Narebski717b8312006-07-31 21:22:15 +02002229 };
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002230 push @list, $pr;
2231 (my $forks_path = $path) =~ s/\.git$//;
2232 $paths{$forks_path}++;
Jakub Narebski717b8312006-07-31 21:22:15 +02002233 }
2234 }
2235 close $fd;
2236 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002237 return @list;
2238}
2239
Junio C Hamano47852452007-07-03 22:10:42 -07002240our $gitweb_project_owner = undef;
2241sub git_get_project_list_from_file {
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002242
Junio C Hamano47852452007-07-03 22:10:42 -07002243 return if (defined $gitweb_project_owner);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002244
Junio C Hamano47852452007-07-03 22:10:42 -07002245 $gitweb_project_owner = {};
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002246 # read from file (url-encoded):
2247 # 'git%2Fgit.git Linus+Torvalds'
2248 # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
2249 # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
2250 if (-f $projects_list) {
2251 open (my $fd , $projects_list);
2252 while (my $line = <$fd>) {
2253 chomp $line;
2254 my ($pr, $ow) = split ' ', $line;
2255 $pr = unescape($pr);
2256 $ow = unescape($ow);
Junio C Hamano47852452007-07-03 22:10:42 -07002257 $gitweb_project_owner->{$pr} = to_utf8($ow);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002258 }
2259 close $fd;
2260 }
Junio C Hamano47852452007-07-03 22:10:42 -07002261}
2262
2263sub git_get_project_owner {
2264 my $project = shift;
2265 my $owner;
2266
2267 return undef unless $project;
Bruno Ribasb59012e2008-02-08 14:38:04 -02002268 $git_dir = "$projectroot/$project";
Junio C Hamano47852452007-07-03 22:10:42 -07002269
2270 if (!defined $gitweb_project_owner) {
2271 git_get_project_list_from_file();
2272 }
2273
2274 if (exists $gitweb_project_owner->{$project}) {
2275 $owner = $gitweb_project_owner->{$project};
2276 }
Bruno Ribasb59012e2008-02-08 14:38:04 -02002277 if (!defined $owner){
2278 $owner = git_get_project_config('owner');
2279 }
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002280 if (!defined $owner) {
Bruno Ribasb59012e2008-02-08 14:38:04 -02002281 $owner = get_file_owner("$git_dir");
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002282 }
2283
2284 return $owner;
2285}
2286
Jakub Narebskic60c56c2006-10-28 19:43:40 +02002287sub git_get_last_activity {
2288 my ($path) = @_;
2289 my $fd;
2290
2291 $git_dir = "$projectroot/$path";
2292 open($fd, "-|", git_cmd(), 'for-each-ref',
Robert Fitzsimons0ff5ec72006-12-22 19:38:13 +00002293 '--format=%(committer)',
Jakub Narebskic60c56c2006-10-28 19:43:40 +02002294 '--sort=-committerdate',
Robert Fitzsimons0ff5ec72006-12-22 19:38:13 +00002295 '--count=1',
Jakub Narebskic60c56c2006-10-28 19:43:40 +02002296 'refs/heads') or return;
2297 my $most_recent = <$fd>;
2298 close $fd or return;
Jakub Narebski785cdea2007-05-13 12:39:22 +02002299 if (defined $most_recent &&
2300 $most_recent =~ / (\d+) [-+][01]\d\d\d$/) {
Jakub Narebskic60c56c2006-10-28 19:43:40 +02002301 my $timestamp = $1;
2302 my $age = time - $timestamp;
2303 return ($age, age_string($age));
2304 }
Matt McCutchenc9563952007-06-28 18:15:22 -04002305 return (undef, undef);
Jakub Narebskic60c56c2006-10-28 19:43:40 +02002306}
2307
Jakub Narebski847e01f2006-08-14 02:05:47 +02002308sub git_get_references {
Jakub Narebski717b8312006-07-31 21:22:15 +02002309 my $type = shift || "";
2310 my %refs;
Jakub Narebski28b9d9f2006-11-25 11:32:08 +01002311 # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
2312 # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
2313 open my $fd, "-|", git_cmd(), "show-ref", "--dereference",
2314 ($type ? ("--", "refs/$type") : ()) # use -- <pattern> if $type
Jakub Narebski9704d752006-09-19 14:31:49 +02002315 or return;
Jakub Narebskid294e1c2006-08-14 02:14:20 +02002316
Jakub Narebski717b8312006-07-31 21:22:15 +02002317 while (my $line = <$fd>) {
2318 chomp $line;
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02002319 if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type.*)$!) {
Jakub Narebski717b8312006-07-31 21:22:15 +02002320 if (defined $refs{$1}) {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02002321 push @{$refs{$1}}, $2;
Jakub Narebski717b8312006-07-31 21:22:15 +02002322 } else {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02002323 $refs{$1} = [ $2 ];
Jakub Narebski717b8312006-07-31 21:22:15 +02002324 }
2325 }
2326 }
2327 close $fd or return;
2328 return \%refs;
2329}
2330
Jakub Narebski56a322f2006-08-24 19:41:23 +02002331sub git_get_rev_name_tags {
2332 my $hash = shift || return undef;
2333
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002334 open my $fd, "-|", git_cmd(), "name-rev", "--tags", $hash
Jakub Narebski56a322f2006-08-24 19:41:23 +02002335 or return;
2336 my $name_rev = <$fd>;
2337 close $fd;
2338
2339 if ($name_rev =~ m|^$hash tags/(.*)$|) {
2340 return $1;
2341 } else {
2342 # catches also '$hash undefined' output
2343 return undef;
2344 }
2345}
2346
Jakub Narebski717b8312006-07-31 21:22:15 +02002347## ----------------------------------------------------------------------
2348## parse to hash functions
2349
Jakub Narebski847e01f2006-08-14 02:05:47 +02002350sub parse_date {
Jakub Narebski717b8312006-07-31 21:22:15 +02002351 my $epoch = shift;
2352 my $tz = shift || "-0000";
2353
2354 my %date;
2355 my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
2356 my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
2357 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
2358 $date{'hour'} = $hour;
2359 $date{'minute'} = $min;
2360 $date{'mday'} = $mday;
2361 $date{'day'} = $days[$wday];
2362 $date{'month'} = $months[$mon];
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01002363 $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
2364 $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
Jakub Narebski952c65f2006-08-22 16:52:50 +02002365 $date{'mday-time'} = sprintf "%d %s %02d:%02d",
2366 $mday, $months[$mon], $hour ,$min;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01002367 $date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
Vincent Zanottia62d6d82007-11-10 19:55:27 +01002368 1900+$year, 1+$mon, $mday, $hour ,$min, $sec;
Jakub Narebski717b8312006-07-31 21:22:15 +02002369
2370 $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
2371 my $local = $epoch + ((int $1 + ($2/60)) * 3600);
2372 ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
2373 $date{'hour_local'} = $hour;
2374 $date{'minute_local'} = $min;
2375 $date{'tz_local'} = $tz;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01002376 $date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
2377 1900+$year, $mon+1, $mday,
2378 $hour, $min, $sec, $tz);
Jakub Narebski717b8312006-07-31 21:22:15 +02002379 return %date;
2380}
2381
Jakub Narebski847e01f2006-08-14 02:05:47 +02002382sub parse_tag {
Jakub Narebski717b8312006-07-31 21:22:15 +02002383 my $tag_id = shift;
2384 my %tag;
2385 my @comment;
2386
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002387 open my $fd, "-|", git_cmd(), "cat-file", "tag", $tag_id or return;
Jakub Narebski717b8312006-07-31 21:22:15 +02002388 $tag{'id'} = $tag_id;
2389 while (my $line = <$fd>) {
2390 chomp $line;
2391 if ($line =~ m/^object ([0-9a-fA-F]{40})$/) {
2392 $tag{'object'} = $1;
2393 } elsif ($line =~ m/^type (.+)$/) {
2394 $tag{'type'} = $1;
2395 } elsif ($line =~ m/^tag (.+)$/) {
2396 $tag{'name'} = $1;
2397 } elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
2398 $tag{'author'} = $1;
2399 $tag{'epoch'} = $2;
2400 $tag{'tz'} = $3;
2401 } elsif ($line =~ m/--BEGIN/) {
2402 push @comment, $line;
2403 last;
2404 } elsif ($line eq "") {
2405 last;
2406 }
2407 }
2408 push @comment, <$fd>;
2409 $tag{'comment'} = \@comment;
2410 close $fd or return;
2411 if (!defined $tag{'name'}) {
2412 return
2413 };
2414 return %tag
2415}
2416
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002417sub parse_commit_text {
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002418 my ($commit_text, $withparents) = @_;
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002419 my @commit_lines = split '\n', $commit_text;
Jakub Narebski717b8312006-07-31 21:22:15 +02002420 my %co;
2421
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002422 pop @commit_lines; # Remove '\0'
2423
Jakub Narebski198a2a82007-05-12 21:16:34 +02002424 if (! @commit_lines) {
2425 return;
2426 }
2427
Jakub Narebski717b8312006-07-31 21:22:15 +02002428 my $header = shift @commit_lines;
Jakub Narebski198a2a82007-05-12 21:16:34 +02002429 if ($header !~ m/^[0-9a-fA-F]{40}/) {
Jakub Narebski717b8312006-07-31 21:22:15 +02002430 return;
2431 }
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002432 ($co{'id'}, my @parents) = split ' ', $header;
Jakub Narebski717b8312006-07-31 21:22:15 +02002433 while (my $line = shift @commit_lines) {
2434 last if $line eq "\n";
2435 if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
2436 $co{'tree'} = $1;
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002437 } elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) {
Robert Fitzsimons208b2df2006-12-24 14:31:43 +00002438 push @parents, $1;
Jakub Narebski717b8312006-07-31 21:22:15 +02002439 } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
2440 $co{'author'} = $1;
2441 $co{'author_epoch'} = $2;
2442 $co{'author_tz'} = $3;
Jakub Narebskiba00b8c2006-11-25 15:54:32 +01002443 if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
2444 $co{'author_name'} = $1;
2445 $co{'author_email'} = $2;
Jakub Narebski717b8312006-07-31 21:22:15 +02002446 } else {
2447 $co{'author_name'} = $co{'author'};
2448 }
2449 } elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
2450 $co{'committer'} = $1;
2451 $co{'committer_epoch'} = $2;
2452 $co{'committer_tz'} = $3;
2453 $co{'committer_name'} = $co{'committer'};
Jakub Narebskiba00b8c2006-11-25 15:54:32 +01002454 if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
2455 $co{'committer_name'} = $1;
2456 $co{'committer_email'} = $2;
2457 } else {
2458 $co{'committer_name'} = $co{'committer'};
2459 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002460 }
2461 }
2462 if (!defined $co{'tree'}) {
2463 return;
2464 };
Robert Fitzsimons208b2df2006-12-24 14:31:43 +00002465 $co{'parents'} = \@parents;
2466 $co{'parent'} = $parents[0];
Jakub Narebski717b8312006-07-31 21:22:15 +02002467
2468 foreach my $title (@commit_lines) {
2469 $title =~ s/^ //;
2470 if ($title ne "") {
2471 $co{'title'} = chop_str($title, 80, 5);
2472 # remove leading stuff of merges to make the interesting part visible
2473 if (length($title) > 50) {
2474 $title =~ s/^Automatic //;
2475 $title =~ s/^merge (of|with) /Merge ... /i;
2476 if (length($title) > 50) {
2477 $title =~ s/(http|rsync):\/\///;
2478 }
2479 if (length($title) > 50) {
2480 $title =~ s/(master|www|rsync)\.//;
2481 }
2482 if (length($title) > 50) {
2483 $title =~ s/kernel.org:?//;
2484 }
2485 if (length($title) > 50) {
2486 $title =~ s/\/pub\/scm//;
2487 }
2488 }
2489 $co{'title_short'} = chop_str($title, 50, 5);
2490 last;
2491 }
2492 }
Joey Hess53c39672008-09-05 14:26:29 -04002493 if (! defined $co{'title'} || $co{'title'} eq "") {
Petr Baudis7e0fe5c2006-10-06 18:55:04 +02002494 $co{'title'} = $co{'title_short'} = '(no commit message)';
2495 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002496 # remove added spaces
2497 foreach my $line (@commit_lines) {
2498 $line =~ s/^ //;
2499 }
2500 $co{'comment'} = \@commit_lines;
2501
2502 my $age = time - $co{'committer_epoch'};
2503 $co{'age'} = $age;
2504 $co{'age_string'} = age_string($age);
2505 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'});
2506 if ($age > 60*60*24*7*2) {
2507 $co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
2508 $co{'age_string_age'} = $co{'age_string'};
2509 } else {
2510 $co{'age_string_date'} = $co{'age_string'};
2511 $co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
2512 }
2513 return %co;
2514}
2515
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002516sub parse_commit {
2517 my ($commit_id) = @_;
2518 my %co;
2519
2520 local $/ = "\0";
2521
2522 open my $fd, "-|", git_cmd(), "rev-list",
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002523 "--parents",
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002524 "--header",
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002525 "--max-count=1",
2526 $commit_id,
2527 "--",
Lea Wiemann074afaa2008-06-19 22:03:21 +02002528 or die_error(500, "Open git-rev-list failed");
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002529 %co = parse_commit_text(<$fd>, 1);
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002530 close $fd;
2531
2532 return %co;
2533}
2534
2535sub parse_commits {
Jakub Narebski311e5522008-02-26 13:22:06 +01002536 my ($commit_id, $maxcount, $skip, $filename, @args) = @_;
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002537 my @cos;
2538
2539 $maxcount ||= 1;
2540 $skip ||= 0;
2541
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002542 local $/ = "\0";
2543
2544 open my $fd, "-|", git_cmd(), "rev-list",
2545 "--header",
Jakub Narebski311e5522008-02-26 13:22:06 +01002546 @args,
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002547 ("--max-count=" . $maxcount),
Robert Fitzsimonsf47efbb2006-12-24 14:31:49 +00002548 ("--skip=" . $skip),
Miklos Vajna868bc062007-07-12 20:39:27 +02002549 @extra_options,
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002550 $commit_id,
2551 "--",
2552 ($filename ? ($filename) : ())
Lea Wiemann074afaa2008-06-19 22:03:21 +02002553 or die_error(500, "Open git-rev-list failed");
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002554 while (my $line = <$fd>) {
2555 my %co = parse_commit_text($line);
2556 push @cos, \%co;
2557 }
2558 close $fd;
2559
2560 return wantarray ? @cos : \@cos;
2561}
2562
Jakub Narebskie8e41a92006-08-21 23:08:52 +02002563# parse line of git-diff-tree "raw" output
Jakub Narebski740e67f2006-08-21 23:07:00 +02002564sub parse_difftree_raw_line {
2565 my $line = shift;
2566 my %res;
2567
2568 # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c'
2569 # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c'
2570 if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) {
2571 $res{'from_mode'} = $1;
2572 $res{'to_mode'} = $2;
2573 $res{'from_id'} = $3;
2574 $res{'to_id'} = $4;
Jakub Narebski4ed4a342008-04-05 21:13:24 +01002575 $res{'status'} = $5;
Jakub Narebski740e67f2006-08-21 23:07:00 +02002576 $res{'similarity'} = $6;
2577 if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied
Jakub Narebskie8e41a92006-08-21 23:08:52 +02002578 ($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7);
Jakub Narebski740e67f2006-08-21 23:07:00 +02002579 } else {
Jakub Narebski9d301452007-11-01 12:38:08 +01002580 $res{'from_file'} = $res{'to_file'} = $res{'file'} = unquote($7);
Jakub Narebski740e67f2006-08-21 23:07:00 +02002581 }
2582 }
Jakub Narebski78bc4032007-05-07 01:10:03 +02002583 # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh'
2584 # combined diff (for merge commit)
2585 elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) {
2586 $res{'nparents'} = length($1);
2587 $res{'from_mode'} = [ split(' ', $2) ];
2588 $res{'to_mode'} = pop @{$res{'from_mode'}};
2589 $res{'from_id'} = [ split(' ', $3) ];
2590 $res{'to_id'} = pop @{$res{'from_id'}};
2591 $res{'status'} = [ split('', $4) ];
2592 $res{'to_file'} = unquote($5);
2593 }
Jakub Narebski740e67f2006-08-21 23:07:00 +02002594 # 'c512b523472485aef4fff9e57b229d9d243c967f'
Jakub Narebski0edcb372006-08-31 00:36:04 +02002595 elsif ($line =~ m/^([0-9a-fA-F]{40})$/) {
2596 $res{'commit'} = $1;
2597 }
Jakub Narebski740e67f2006-08-21 23:07:00 +02002598
2599 return wantarray ? %res : \%res;
2600}
2601
Jakub Narebski0cec6db2007-10-30 01:35:05 +01002602# wrapper: return parsed line of git-diff-tree "raw" output
2603# (the argument might be raw line, or parsed info)
2604sub parsed_difftree_line {
2605 my $line_or_ref = shift;
2606
2607 if (ref($line_or_ref) eq "HASH") {
2608 # pre-parsed (or generated by hand)
2609 return $line_or_ref;
2610 } else {
2611 return parse_difftree_raw_line($line_or_ref);
2612 }
2613}
2614
Jakub Narebskicb849b42006-08-31 00:32:15 +02002615# parse line of git-ls-tree output
2616sub parse_ls_tree_line ($;%) {
2617 my $line = shift;
2618 my %opts = @_;
2619 my %res;
2620
2621 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
Jakub Narebski8b4b94c2006-10-30 22:25:11 +01002622 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
Jakub Narebskicb849b42006-08-31 00:32:15 +02002623
2624 $res{'mode'} = $1;
2625 $res{'type'} = $2;
2626 $res{'hash'} = $3;
2627 if ($opts{'-z'}) {
2628 $res{'name'} = $4;
2629 } else {
2630 $res{'name'} = unquote($4);
2631 }
2632
2633 return wantarray ? %res : \%res;
2634}
2635
Jakub Narebski90921742007-06-08 13:27:42 +02002636# generates _two_ hashes, references to which are passed as 2 and 3 argument
2637sub parse_from_to_diffinfo {
2638 my ($diffinfo, $from, $to, @parents) = @_;
2639
2640 if ($diffinfo->{'nparents'}) {
2641 # combined diff
2642 $from->{'file'} = [];
2643 $from->{'href'} = [];
2644 fill_from_file_info($diffinfo, @parents)
2645 unless exists $diffinfo->{'from_file'};
2646 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
Jakub Narebski9d301452007-11-01 12:38:08 +01002647 $from->{'file'}[$i] =
2648 defined $diffinfo->{'from_file'}[$i] ?
2649 $diffinfo->{'from_file'}[$i] :
2650 $diffinfo->{'to_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02002651 if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
2652 $from->{'href'}[$i] = href(action=>"blob",
2653 hash_base=>$parents[$i],
2654 hash=>$diffinfo->{'from_id'}[$i],
2655 file_name=>$from->{'file'}[$i]);
2656 } else {
2657 $from->{'href'}[$i] = undef;
2658 }
2659 }
2660 } else {
Jakub Narebski0cec6db2007-10-30 01:35:05 +01002661 # ordinary (not combined) diff
Jakub Narebski9d301452007-11-01 12:38:08 +01002662 $from->{'file'} = $diffinfo->{'from_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02002663 if ($diffinfo->{'status'} ne "A") { # not new (added) file
2664 $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent,
2665 hash=>$diffinfo->{'from_id'},
2666 file_name=>$from->{'file'});
2667 } else {
2668 delete $from->{'href'};
2669 }
2670 }
2671
Jakub Narebski9d301452007-11-01 12:38:08 +01002672 $to->{'file'} = $diffinfo->{'to_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02002673 if (!is_deleted($diffinfo)) { # file exists in result
2674 $to->{'href'} = href(action=>"blob", hash_base=>$hash,
2675 hash=>$diffinfo->{'to_id'},
2676 file_name=>$to->{'file'});
2677 } else {
2678 delete $to->{'href'};
2679 }
2680}
2681
Jakub Narebski717b8312006-07-31 21:22:15 +02002682## ......................................................................
2683## parse to array of hashes functions
2684
Jakub Narebskicd146402006-11-02 20:23:11 +01002685sub git_get_heads_list {
2686 my $limit = shift;
2687 my @headslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02002688
Jakub Narebskicd146402006-11-02 20:23:11 +01002689 open my $fd, '-|', git_cmd(), 'for-each-ref',
2690 ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
2691 '--format=%(objectname) %(refname) %(subject)%00%(committer)',
2692 'refs/heads'
Jakub Narebskic83a77e2006-09-15 03:43:28 +02002693 or return;
2694 while (my $line = <$fd>) {
Jakub Narebskicd146402006-11-02 20:23:11 +01002695 my %ref_item;
Jakub Narebski120ddde2006-09-19 14:33:22 +02002696
Jakub Narebskicd146402006-11-02 20:23:11 +01002697 chomp $line;
2698 my ($refinfo, $committerinfo) = split(/\0/, $line);
2699 my ($hash, $name, $title) = split(' ', $refinfo, 3);
2700 my ($committer, $epoch, $tz) =
2701 ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
Jakub Narebskibf901f82007-12-15 15:40:28 +01002702 $ref_item{'fullname'} = $name;
Jakub Narebskicd146402006-11-02 20:23:11 +01002703 $name =~ s!^refs/heads/!!;
2704
2705 $ref_item{'name'} = $name;
2706 $ref_item{'id'} = $hash;
2707 $ref_item{'title'} = $title || '(no commit message)';
2708 $ref_item{'epoch'} = $epoch;
2709 if ($epoch) {
2710 $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
2711 } else {
2712 $ref_item{'age'} = "unknown";
Jakub Narebski717b8312006-07-31 21:22:15 +02002713 }
Jakub Narebskicd146402006-11-02 20:23:11 +01002714
2715 push @headslist, \%ref_item;
Jakub Narebskic83a77e2006-09-15 03:43:28 +02002716 }
2717 close $fd;
Junio C Hamano7a13b992006-07-31 19:18:34 -07002718
Jakub Narebskicd146402006-11-02 20:23:11 +01002719 return wantarray ? @headslist : \@headslist;
2720}
Jakub Narebskic83a77e2006-09-15 03:43:28 +02002721
Jakub Narebskicd146402006-11-02 20:23:11 +01002722sub git_get_tags_list {
2723 my $limit = shift;
2724 my @tagslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02002725
Jakub Narebskicd146402006-11-02 20:23:11 +01002726 open my $fd, '-|', git_cmd(), 'for-each-ref',
2727 ($limit ? '--count='.($limit+1) : ()), '--sort=-creatordate',
2728 '--format=%(objectname) %(objecttype) %(refname) '.
2729 '%(*objectname) %(*objecttype) %(subject)%00%(creator)',
2730 'refs/tags'
2731 or return;
2732 while (my $line = <$fd>) {
2733 my %ref_item;
2734
2735 chomp $line;
2736 my ($refinfo, $creatorinfo) = split(/\0/, $line);
2737 my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6);
2738 my ($creator, $epoch, $tz) =
2739 ($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/);
Jakub Narebskibf901f82007-12-15 15:40:28 +01002740 $ref_item{'fullname'} = $name;
Jakub Narebskicd146402006-11-02 20:23:11 +01002741 $name =~ s!^refs/tags/!!;
2742
2743 $ref_item{'type'} = $type;
2744 $ref_item{'id'} = $id;
2745 $ref_item{'name'} = $name;
2746 if ($type eq "tag") {
2747 $ref_item{'subject'} = $title;
2748 $ref_item{'reftype'} = $reftype;
2749 $ref_item{'refid'} = $refid;
2750 } else {
2751 $ref_item{'reftype'} = $type;
2752 $ref_item{'refid'} = $id;
2753 }
2754
2755 if ($type eq "tag" || $type eq "commit") {
2756 $ref_item{'epoch'} = $epoch;
2757 if ($epoch) {
2758 $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
2759 } else {
2760 $ref_item{'age'} = "unknown";
2761 }
2762 }
2763
2764 push @tagslist, \%ref_item;
Jakub Narebski717b8312006-07-31 21:22:15 +02002765 }
Jakub Narebskicd146402006-11-02 20:23:11 +01002766 close $fd;
2767
2768 return wantarray ? @tagslist : \@tagslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02002769}
2770
2771## ----------------------------------------------------------------------
2772## filesystem-related functions
2773
2774sub get_file_owner {
2775 my $path = shift;
2776
2777 my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path);
2778 my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid);
2779 if (!defined $gcos) {
2780 return undef;
2781 }
2782 my $owner = $gcos;
2783 $owner =~ s/[,;].*$//;
Martin Koegler00f429a2007-06-03 17:42:44 +02002784 return to_utf8($owner);
Jakub Narebski717b8312006-07-31 21:22:15 +02002785}
2786
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01002787# assume that file exists
2788sub insert_file {
2789 my $filename = shift;
2790
2791 open my $fd, '<', $filename;
Jakub Narebski45868642008-12-08 14:13:21 +01002792 print map { to_utf8($_) } <$fd>;
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01002793 close $fd;
2794}
2795
Jakub Narebski717b8312006-07-31 21:22:15 +02002796## ......................................................................
2797## mimetype related functions
2798
2799sub mimetype_guess_file {
2800 my $filename = shift;
2801 my $mimemap = shift;
2802 -r $mimemap or return undef;
2803
2804 my %mimemap;
2805 open(MIME, $mimemap) or return undef;
2806 while (<MIME>) {
Jakub Narebski618918e2006-08-14 02:15:22 +02002807 next if m/^#/; # skip comments
Jakub Narebski717b8312006-07-31 21:22:15 +02002808 my ($mime, $exts) = split(/\t+/);
Junio C Hamano46b059d2006-07-31 19:24:37 -07002809 if (defined $exts) {
2810 my @exts = split(/\s+/, $exts);
2811 foreach my $ext (@exts) {
2812 $mimemap{$ext} = $mime;
2813 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002814 }
2815 }
2816 close(MIME);
2817
Jakub Narebski80593192006-09-19 13:57:03 +02002818 $filename =~ /\.([^.]*)$/;
Jakub Narebski717b8312006-07-31 21:22:15 +02002819 return $mimemap{$1};
2820}
2821
2822sub mimetype_guess {
2823 my $filename = shift;
2824 my $mime;
2825 $filename =~ /\./ or return undef;
2826
2827 if ($mimetypes_file) {
2828 my $file = $mimetypes_file;
Jakub Narebskid5aa50d2006-08-14 02:16:33 +02002829 if ($file !~ m!^/!) { # if it is relative path
2830 # it is relative to project
2831 $file = "$projectroot/$project/$file";
2832 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002833 $mime = mimetype_guess_file($filename, $file);
2834 }
2835 $mime ||= mimetype_guess_file($filename, '/etc/mime.types');
2836 return $mime;
2837}
2838
Jakub Narebski847e01f2006-08-14 02:05:47 +02002839sub blob_mimetype {
Jakub Narebski717b8312006-07-31 21:22:15 +02002840 my $fd = shift;
2841 my $filename = shift;
2842
2843 if ($filename) {
2844 my $mime = mimetype_guess($filename);
2845 $mime and return $mime;
2846 }
2847
2848 # just in case
2849 return $default_blob_plain_mimetype unless $fd;
2850
2851 if (-T $fd) {
Jakub Narebski7f718e82008-06-03 16:47:10 +02002852 return 'text/plain';
Jakub Narebski717b8312006-07-31 21:22:15 +02002853 } elsif (! $filename) {
2854 return 'application/octet-stream';
2855 } elsif ($filename =~ m/\.png$/i) {
2856 return 'image/png';
2857 } elsif ($filename =~ m/\.gif$/i) {
2858 return 'image/gif';
2859 } elsif ($filename =~ m/\.jpe?g$/i) {
2860 return 'image/jpeg';
2861 } else {
2862 return 'application/octet-stream';
2863 }
2864}
2865
Jakub Narebski7f718e82008-06-03 16:47:10 +02002866sub blob_contenttype {
2867 my ($fd, $file_name, $type) = @_;
2868
2869 $type ||= blob_mimetype($fd, $file_name);
2870 if ($type eq 'text/plain' && defined $default_text_plain_charset) {
2871 $type .= "; charset=$default_text_plain_charset";
2872 }
2873
2874 return $type;
2875}
2876
Jakub Narebski717b8312006-07-31 21:22:15 +02002877## ======================================================================
2878## functions printing HTML: header, footer, error page
2879
Kay Sievers12a88f22005-08-07 20:02:47 +02002880sub git_header_html {
Kay Sieversa59d4af2005-08-07 20:15:44 +02002881 my $status = shift || "200 OK";
Kay Sievers11044292005-10-19 03:18:45 +02002882 my $expires = shift;
Kay Sieversa59d4af2005-08-07 20:15:44 +02002883
Petr Baudis8be28902006-10-24 05:18:39 +02002884 my $title = "$site_name";
Kay Sieversb87d78d2005-08-07 20:21:04 +02002885 if (defined $project) {
Martin Koegler00f429a2007-06-03 17:42:44 +02002886 $title .= " - " . to_utf8($project);
Kay Sieversb87d78d2005-08-07 20:21:04 +02002887 if (defined $action) {
2888 $title .= "/$action";
Jakub Narebski7bedd9f2006-06-20 06:17:03 +00002889 if (defined $file_name) {
Jakub Narebski403d0902006-11-08 11:48:56 +01002890 $title .= " - " . esc_path($file_name);
Jakub Narebski7bedd9f2006-06-20 06:17:03 +00002891 if ($action eq "tree" && $file_name !~ m|/$|) {
2892 $title .= "/";
2893 }
2894 }
Kay Sieversb87d78d2005-08-07 20:21:04 +02002895 }
2896 }
Alp Tokerf6801d62006-07-11 11:19:34 +01002897 my $content_type;
2898 # require explicit support from the UA if we are to send the page as
2899 # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
2900 # we have to do this because MSIE sometimes globs '*/*', pretending to
2901 # support xhtml+xml but choking when it gets what it asked for.
Jakub Narebski952c65f2006-08-22 16:52:50 +02002902 if (defined $cgi->http('HTTP_ACCEPT') &&
2903 $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
2904 $cgi->Accept('application/xhtml+xml') != 0) {
Alp Tokerf6801d62006-07-11 11:19:34 +01002905 $content_type = 'application/xhtml+xml';
2906 } else {
2907 $content_type = 'text/html';
2908 }
Jakub Narebski952c65f2006-08-22 16:52:50 +02002909 print $cgi->header(-type=>$content_type, -charset => 'utf-8',
2910 -status=> $status, -expires => $expires);
Jakub Narebski45c9a752006-12-27 23:59:51 +01002911 my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
Kay Sieversa59d4af2005-08-07 20:15:44 +02002912 print <<EOF;
Kay Sievers6191f8e2005-08-07 20:19:56 +02002913<?xml version="1.0" encoding="utf-8"?>
Kay Sievers161332a2005-08-07 19:49:46 +02002914<!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 +02002915<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
Jakub Narebskid4baf9e2006-08-17 11:21:28 +02002916<!-- git web interface version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
Jakub Narebskiae20de52006-06-21 09:48:03 +02002917<!-- git core binaries version $git_version -->
Kay Sievers161332a2005-08-07 19:49:46 +02002918<head>
Alp Tokerf6801d62006-07-11 11:19:34 +01002919<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
Jakub Narebski45c9a752006-12-27 23:59:51 +01002920<meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/>
Kay Sieversc994d622005-08-07 20:27:18 +02002921<meta name="robots" content="index, nofollow"/>
Kay Sieversb87d78d2005-08-07 20:21:04 +02002922<title>$title</title>
Kay Sievers161332a2005-08-07 19:49:46 +02002923EOF
Giuseppe Bilotta41a4d162009-01-31 02:31:52 +01002924 # the stylesheet, favicon etc urls won't work correctly with path_info
2925 # unless we set the appropriate base URL
Giuseppe Bilottac3254ae2009-01-31 02:31:50 +01002926 if ($ENV{'PATH_INFO'}) {
Giuseppe Bilotta81d3fe92009-02-15 10:18:36 +01002927 print "<base href=\"".esc_url($base_url)."\" />\n";
Giuseppe Bilottac3254ae2009-01-31 02:31:50 +01002928 }
Giuseppe Bilotta41a4d162009-01-31 02:31:52 +01002929 # print out each stylesheet that exist, providing backwards capability
2930 # for those people who defined $stylesheet in a config file
Alan Chandlerb2d34762006-10-03 13:49:03 +01002931 if (defined $stylesheet) {
Alan Chandlerb2d34762006-10-03 13:49:03 +01002932 print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
2933 } else {
2934 foreach my $stylesheet (@stylesheets) {
2935 next unless $stylesheet;
2936 print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
2937 }
2938 }
Matthias Lederhoferdd04c422006-08-06 13:25:41 +02002939 if (defined $project) {
Jakub Narebski35621982008-04-20 22:09:48 +02002940 my %href_params = get_feed_info();
2941 if (!exists $href_params{'-title'}) {
2942 $href_params{'-title'} = 'log';
2943 }
2944
2945 foreach my $format qw(RSS Atom) {
2946 my $type = lc($format);
2947 my %link_attr = (
2948 '-rel' => 'alternate',
2949 '-title' => "$project - $href_params{'-title'} - $format feed",
2950 '-type' => "application/$type+xml"
2951 );
2952
2953 $href_params{'action'} = $type;
2954 $link_attr{'-href'} = href(%href_params);
2955 print "<link ".
2956 "rel=\"$link_attr{'-rel'}\" ".
2957 "title=\"$link_attr{'-title'}\" ".
2958 "href=\"$link_attr{'-href'}\" ".
2959 "type=\"$link_attr{'-type'}\" ".
2960 "/>\n";
2961
2962 $href_params{'extra_options'} = '--no-merges';
2963 $link_attr{'-href'} = href(%href_params);
2964 $link_attr{'-title'} .= ' (no merges)';
2965 print "<link ".
2966 "rel=\"$link_attr{'-rel'}\" ".
2967 "title=\"$link_attr{'-title'}\" ".
2968 "href=\"$link_attr{'-href'}\" ".
2969 "type=\"$link_attr{'-type'}\" ".
2970 "/>\n";
2971 }
2972
Jakub Narebski9d0734a2006-09-15 11:11:33 +02002973 } else {
2974 printf('<link rel="alternate" title="%s projects list" '.
Jakub Narebski35621982008-04-20 22:09:48 +02002975 'href="%s" type="text/plain; charset=utf-8" />'."\n",
Jakub Narebski9d0734a2006-09-15 11:11:33 +02002976 $site_name, href(project=>undef, action=>"project_index"));
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01002977 printf('<link rel="alternate" title="%s projects feeds" '.
Jakub Narebski35621982008-04-20 22:09:48 +02002978 'href="%s" type="text/x-opml" />'."\n",
Jakub Narebski9d0734a2006-09-15 11:11:33 +02002979 $site_name, href(project=>undef, action=>"opml"));
Matthias Lederhoferdd04c422006-08-06 13:25:41 +02002980 }
Jakub Narebski0b5deba2006-09-04 20:32:13 +02002981 if (defined $favicon) {
Jakub Narebski35621982008-04-20 22:09:48 +02002982 print qq(<link rel="shortcut icon" href="$favicon" type="image/png" />\n);
Jakub Narebski0b5deba2006-09-04 20:32:13 +02002983 }
Jakub Narebski10161352006-08-05 13:18:58 +02002984
Matthias Lederhoferdd04c422006-08-06 13:25:41 +02002985 print "</head>\n" .
Alan Chandlerb2d34762006-10-03 13:49:03 +01002986 "<body>\n";
2987
2988 if (-f $site_header) {
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01002989 insert_file($site_header);
Alan Chandlerb2d34762006-10-03 13:49:03 +01002990 }
2991
2992 print "<div class=\"page_header\">\n" .
Jakub Narebski9a7a62f2006-10-06 12:31:05 +02002993 $cgi->a({-href => esc_url($logo_url),
2994 -title => $logo_label},
2995 qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
Jakub Narebskif93bff82006-09-26 01:58:41 +02002996 print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
Kay Sieversb87d78d2005-08-07 20:21:04 +02002997 if (defined $project) {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02002998 print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
Kay Sieversb87d78d2005-08-07 20:21:04 +02002999 if (defined $action) {
3000 print " / $action";
3001 }
Kay Sievers19806692005-08-07 20:26:27 +02003002 print "\n";
Robert Fitzsimons6be93512006-12-23 03:35:16 +00003003 }
Petr Baudisd77b5672007-05-17 04:24:19 +02003004 print "</div>\n";
3005
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08003006 my $have_search = gitweb_check_feature('search');
Jakub Narebskif70dda22008-06-02 11:54:41 +02003007 if (defined $project && $have_search) {
Kay Sievers19806692005-08-07 20:26:27 +02003008 if (!defined $searchtext) {
3009 $searchtext = "";
3010 }
Kay Sieversc39e47d2005-09-17 03:00:21 +02003011 my $search_hash;
Timo Hirvonen4c5c2022006-06-20 16:41:05 +03003012 if (defined $hash_base) {
3013 $search_hash = $hash_base;
3014 } elsif (defined $hash) {
Kay Sieversc39e47d2005-09-17 03:00:21 +02003015 $search_hash = $hash;
3016 } else {
Jakub Narebski8adc4bd2006-06-22 08:52:57 +02003017 $search_hash = "HEAD";
Kay Sieversc39e47d2005-09-17 03:00:21 +02003018 }
Matt McCutchen40375a82007-06-28 14:57:07 -04003019 my $action = $my_uri;
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08003020 my $use_pathinfo = gitweb_check_feature('pathinfo');
Matt McCutchen40375a82007-06-28 14:57:07 -04003021 if ($use_pathinfo) {
martin f. krafft85d17a12008-04-20 23:23:38 +02003022 $action .= "/".esc_url($project);
Matt McCutchen40375a82007-06-28 14:57:07 -04003023 }
Matt McCutchen40375a82007-06-28 14:57:07 -04003024 print $cgi->startform(-method => "get", -action => $action) .
Kay Sieversc994d622005-08-07 20:27:18 +02003025 "<div class=\"search\">\n" .
Jakub Narebskif70dda22008-06-02 11:54:41 +02003026 (!$use_pathinfo &&
3027 $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
3028 $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
3029 $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
Petr Baudis88ad7292006-10-24 05:15:46 +02003030 $cgi->popup_menu(-name => 'st', -default => 'commit',
Petr Baudise7738552007-05-17 04:31:12 +02003031 -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
Petr Baudis88ad7292006-10-24 05:15:46 +02003032 $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
3033 " search:\n",
Kay Sieversc994d622005-08-07 20:27:18 +02003034 $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
Petr Baudis0e559912008-02-26 13:22:08 +01003035 "<span title=\"Extended regular expression\">" .
3036 $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
3037 -checked => $search_use_regexp) .
3038 "</span>" .
Kay Sieversc994d622005-08-07 20:27:18 +02003039 "</div>" .
3040 $cgi->end_form() . "\n";
Kay Sievers4c02e3c2005-08-07 19:52:52 +02003041 }
Kay Sievers161332a2005-08-07 19:49:46 +02003042}
3043
Kay Sievers12a88f22005-08-07 20:02:47 +02003044sub git_footer_html {
Jakub Narebski35621982008-04-20 22:09:48 +02003045 my $feed_class = 'rss_logo';
3046
Kay Sievers6191f8e2005-08-07 20:19:56 +02003047 print "<div class=\"page_footer\">\n";
Kay Sieversb87d78d2005-08-07 20:21:04 +02003048 if (defined $project) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02003049 my $descr = git_get_project_description($project);
Kay Sieversb87d78d2005-08-07 20:21:04 +02003050 if (defined $descr) {
Kay Sievers40c13812005-11-19 17:41:29 +01003051 print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
Kay Sievers6191f8e2005-08-07 20:19:56 +02003052 }
Jakub Narebski35621982008-04-20 22:09:48 +02003053
3054 my %href_params = get_feed_info();
3055 if (!%href_params) {
3056 $feed_class .= ' generic';
3057 }
3058 $href_params{'-title'} ||= 'log';
3059
3060 foreach my $format qw(RSS Atom) {
3061 $href_params{'action'} = lc($format);
3062 print $cgi->a({-href => href(%href_params),
3063 -title => "$href_params{'-title'} $format feed",
3064 -class => $feed_class}, $format)."\n";
3065 }
3066
Kay Sieversc994d622005-08-07 20:27:18 +02003067 } else {
Jakub Narebskia1565c42006-09-15 19:30:34 +02003068 print $cgi->a({-href => href(project=>undef, action=>"opml"),
Jakub Narebski35621982008-04-20 22:09:48 +02003069 -class => $feed_class}, "OPML") . " ";
Jakub Narebski9d0734a2006-09-15 11:11:33 +02003070 print $cgi->a({-href => href(project=>undef, action=>"project_index"),
Jakub Narebski35621982008-04-20 22:09:48 +02003071 -class => $feed_class}, "TXT") . "\n";
Kay Sieversff7669a2005-08-07 20:13:02 +02003072 }
Jakub Narebski35621982008-04-20 22:09:48 +02003073 print "</div>\n"; # class="page_footer"
Alan Chandlerb2d34762006-10-03 13:49:03 +01003074
3075 if (-f $site_footer) {
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01003076 insert_file($site_footer);
Alan Chandlerb2d34762006-10-03 13:49:03 +01003077 }
3078
3079 print "</body>\n" .
Kay Sievers9cd3d982005-08-07 20:17:42 +02003080 "</html>";
Kay Sievers161332a2005-08-07 19:49:46 +02003081}
3082
Lea Wiemann074afaa2008-06-19 22:03:21 +02003083# die_error(<http_status_code>, <error_message>)
3084# Example: die_error(404, 'Hash not found')
3085# By convention, use the following status codes (as defined in RFC 2616):
3086# 400: Invalid or missing CGI parameters, or
3087# requested object exists but has wrong type.
3088# 403: Requested feature (like "pickaxe" or "snapshot") not enabled on
3089# this server or project.
3090# 404: Requested object/revision/project doesn't exist.
3091# 500: The server isn't configured properly, or
3092# an internal error occurred (e.g. failed assertions caused by bugs), or
3093# an unknown error occurred (e.g. the git binary died unexpectedly).
Kay Sievers061cc7c2005-08-07 20:15:57 +02003094sub die_error {
Lea Wiemann074afaa2008-06-19 22:03:21 +02003095 my $status = shift || 500;
3096 my $error = shift || "Internal server error";
Kay Sievers664f4cc2005-08-07 20:17:19 +02003097
Lea Wiemann074afaa2008-06-19 22:03:21 +02003098 my %http_responses = (400 => '400 Bad Request',
3099 403 => '403 Forbidden',
3100 404 => '404 Not Found',
3101 500 => '500 Internal Server Error');
3102 git_header_html($http_responses{$status});
Jakub Narebski59b9f612006-08-22 23:42:53 +02003103 print <<EOF;
3104<div class="page_body">
3105<br /><br />
3106$status - $error
3107<br />
3108</div>
3109EOF
Kay Sieversa59d4af2005-08-07 20:15:44 +02003110 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02003111 exit;
Kay Sieversa59d4af2005-08-07 20:15:44 +02003112}
3113
Jakub Narebski717b8312006-07-31 21:22:15 +02003114## ----------------------------------------------------------------------
3115## functions printing or outputting HTML: navigation
3116
Jakub Narebski847e01f2006-08-14 02:05:47 +02003117sub git_print_page_nav {
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003118 my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_;
3119 $extra = '' if !defined $extra; # pager or formats
3120
3121 my @navs = qw(summary shortlog log commit commitdiff tree);
3122 if ($suppress) {
3123 @navs = grep { $_ ne $suppress } @navs;
3124 }
3125
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003126 my %arg = map { $_ => {action=>$_} } @navs;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003127 if (defined $head) {
3128 for (qw(commit commitdiff)) {
Jakub Narebski3be8e722007-04-01 22:22:21 +02003129 $arg{$_}{'hash'} = $head;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003130 }
3131 if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
3132 for (qw(shortlog log)) {
Jakub Narebski3be8e722007-04-01 22:22:21 +02003133 $arg{$_}{'hash'} = $head;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003134 }
3135 }
3136 }
Petr Baudisd627f682008-10-02 16:36:52 +02003137
Jakub Narebski3be8e722007-04-01 22:22:21 +02003138 $arg{'tree'}{'hash'} = $treehead if defined $treehead;
3139 $arg{'tree'}{'hash_base'} = $treebase if defined $treebase;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003140
Junio C Hamanoa7c5a282008-11-29 13:02:08 -08003141 my @actions = gitweb_get_feature('actions');
Jakub Narebski2b11e052008-10-12 00:02:32 +02003142 my %repl = (
3143 '%' => '%',
3144 'n' => $project, # project name
3145 'f' => $git_dir, # project path within filesystem
3146 'h' => $treehead || '', # current hash ('h' parameter)
3147 'b' => $treebase || '', # hash base ('hb' parameter)
3148 );
Petr Baudisd627f682008-10-02 16:36:52 +02003149 while (@actions) {
Jakub Narebski2b11e052008-10-12 00:02:32 +02003150 my ($label, $link, $pos) = splice(@actions,0,3);
3151 # insert
Petr Baudisd627f682008-10-02 16:36:52 +02003152 @navs = map { $_ eq $pos ? ($_, $label) : $_ } @navs;
3153 # munch munch
Jakub Narebski2b11e052008-10-12 00:02:32 +02003154 $link =~ s/%([%nfhb])/$repl{$1}/g;
Petr Baudisd627f682008-10-02 16:36:52 +02003155 $arg{$label}{'_href'} = $link;
3156 }
3157
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003158 print "<div class=\"page_nav\">\n" .
3159 (join " | ",
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003160 map { $_ eq $current ?
Petr Baudisd627f682008-10-02 16:36:52 +02003161 $_ : $cgi->a({-href => ($arg{$_}{_href} ? $arg{$_}{_href} : href(%{$arg{$_}}))}, "$_")
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003162 } @navs);
Jakub Narebski898a8932006-07-30 16:14:43 +02003163 print "<br/>\n$extra<br/>\n" .
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003164 "</div>\n";
3165}
3166
Jakub Narebski847e01f2006-08-14 02:05:47 +02003167sub format_paging_nav {
Lea Wiemann1f684dc2008-05-28 01:25:42 +02003168 my ($action, $hash, $head, $page, $has_next_link) = @_;
Jakub Narebski43ffc062006-07-30 17:49:00 +02003169 my $paging_nav;
3170
3171
3172 if ($hash ne $head || $page) {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003173 $paging_nav .= $cgi->a({-href => href(action=>$action)}, "HEAD");
Jakub Narebski43ffc062006-07-30 17:49:00 +02003174 } else {
3175 $paging_nav .= "HEAD";
3176 }
3177
3178 if ($page > 0) {
3179 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01003180 $cgi->a({-href => href(-replay=>1, page=>$page-1),
Jakub Narebski26298b52006-08-10 12:38:47 +02003181 -accesskey => "p", -title => "Alt-p"}, "prev");
Jakub Narebski43ffc062006-07-30 17:49:00 +02003182 } else {
3183 $paging_nav .= " &sdot; prev";
3184 }
3185
Lea Wiemann1f684dc2008-05-28 01:25:42 +02003186 if ($has_next_link) {
Jakub Narebski43ffc062006-07-30 17:49:00 +02003187 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01003188 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Jakub Narebski26298b52006-08-10 12:38:47 +02003189 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski43ffc062006-07-30 17:49:00 +02003190 } else {
3191 $paging_nav .= " &sdot; next";
3192 }
3193
3194 return $paging_nav;
3195}
3196
Jakub Narebski717b8312006-07-31 21:22:15 +02003197## ......................................................................
3198## functions printing or outputting HTML: div
Kay Sievers42f7eb92005-08-07 20:21:46 +02003199
Jakub Narebski847e01f2006-08-14 02:05:47 +02003200sub git_print_header_div {
Jakub Narebski717b8312006-07-31 21:22:15 +02003201 my ($action, $title, $hash, $hash_base) = @_;
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003202 my %args = ();
Jakub Narebski717b8312006-07-31 21:22:15 +02003203
Jakub Narebski3be8e722007-04-01 22:22:21 +02003204 $args{'action'} = $action;
3205 $args{'hash'} = $hash if $hash;
3206 $args{'hash_base'} = $hash_base if $hash_base;
Jakub Narebski717b8312006-07-31 21:22:15 +02003207
3208 print "<div class=\"header\">\n" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003209 $cgi->a({-href => href(%args), -class => "title"},
3210 $title ? $title : $action) .
3211 "\n</div>\n";
Kay Sievers42f7eb92005-08-07 20:21:46 +02003212}
3213
Jakub Narebski6fd92a22006-08-28 14:48:12 +02003214#sub git_print_authorship (\%) {
3215sub git_print_authorship {
3216 my $co = shift;
3217
Jakub Narebskia44465c2006-08-28 23:17:31 +02003218 my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
Jakub Narebski6fd92a22006-08-28 14:48:12 +02003219 print "<div class=\"author_date\">" .
3220 esc_html($co->{'author_name'}) .
Jakub Narebskia44465c2006-08-28 23:17:31 +02003221 " [$ad{'rfc2822'}";
3222 if ($ad{'hour_local'} < 6) {
3223 printf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
3224 $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
3225 } else {
3226 printf(" (%02d:%02d %s)",
3227 $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
3228 }
3229 print "]</div>\n";
Jakub Narebski6fd92a22006-08-28 14:48:12 +02003230}
3231
Jakub Narebski717b8312006-07-31 21:22:15 +02003232sub git_print_page_path {
3233 my $name = shift;
3234 my $type = shift;
Luben Tuikov59fb1c92006-08-17 10:39:29 -07003235 my $hb = shift;
Junio C Hamanodf2c37a2006-01-09 13:13:39 +01003236
Jakub Narebski4df118e2006-10-21 17:53:55 +02003237
3238 print "<div class=\"page_path\">";
3239 print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
Martin Koegler00f429a2007-06-03 17:42:44 +02003240 -title => 'tree root'}, to_utf8("[$project]"));
Jakub Narebski4df118e2006-10-21 17:53:55 +02003241 print " / ";
3242 if (defined $name) {
Jakub Narebski762c7202006-09-04 18:17:58 +02003243 my @dirname = split '/', $name;
3244 my $basename = pop @dirname;
3245 my $fullname = '';
3246
Jakub Narebski762c7202006-09-04 18:17:58 +02003247 foreach my $dir (@dirname) {
Petr Baudis16fdb482006-09-21 02:05:50 +02003248 $fullname .= ($fullname ? '/' : '') . $dir;
Jakub Narebski762c7202006-09-04 18:17:58 +02003249 print $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
3250 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01003251 -title => $fullname}, esc_path($dir));
Petr Baudis26d0a972006-09-23 01:00:12 +02003252 print " / ";
Jakub Narebski762c7202006-09-04 18:17:58 +02003253 }
3254 if (defined $type && $type eq 'blob') {
Jakub Narebski952c65f2006-08-22 16:52:50 +02003255 print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name,
Jakub Narebski762c7202006-09-04 18:17:58 +02003256 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01003257 -title => $name}, esc_path($basename));
Jakub Narebski762c7202006-09-04 18:17:58 +02003258 } elsif (defined $type && $type eq 'tree') {
3259 print $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
3260 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01003261 -title => $name}, esc_path($basename));
Jakub Narebski4df118e2006-10-21 17:53:55 +02003262 print " / ";
Luben Tuikov59fb1c92006-08-17 10:39:29 -07003263 } else {
Jakub Narebski403d0902006-11-08 11:48:56 +01003264 print esc_path($basename);
Luben Tuikov59fb1c92006-08-17 10:39:29 -07003265 }
Kay Sievers8ed23e12005-08-07 20:05:44 +02003266 }
Jakub Narebski4df118e2006-10-21 17:53:55 +02003267 print "<br/></div>\n";
Kay Sievers4c02e3c2005-08-07 19:52:52 +02003268}
3269
Jakub Narebskib7f92532006-08-28 14:48:10 +02003270# sub git_print_log (\@;%) {
3271sub git_print_log ($;%) {
Jakub Narebskid16d0932006-08-17 11:21:23 +02003272 my $log = shift;
Jakub Narebskib7f92532006-08-28 14:48:10 +02003273 my %opts = @_;
Jakub Narebskid16d0932006-08-17 11:21:23 +02003274
Jakub Narebskib7f92532006-08-28 14:48:10 +02003275 if ($opts{'-remove_title'}) {
3276 # remove title, i.e. first line of log
3277 shift @$log;
3278 }
Jakub Narebskid16d0932006-08-17 11:21:23 +02003279 # remove leading empty lines
3280 while (defined $log->[0] && $log->[0] eq "") {
3281 shift @$log;
3282 }
3283
3284 # print log
3285 my $signoff = 0;
3286 my $empty = 0;
3287 foreach my $line (@$log) {
Jakub Narebskib7f92532006-08-28 14:48:10 +02003288 if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
3289 $signoff = 1;
Jakub Narebskifba20b42006-08-28 14:48:13 +02003290 $empty = 0;
Jakub Narebskib7f92532006-08-28 14:48:10 +02003291 if (! $opts{'-remove_signoff'}) {
3292 print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n";
3293 next;
3294 } else {
3295 # remove signoff lines
3296 next;
3297 }
3298 } else {
3299 $signoff = 0;
3300 }
3301
Jakub Narebskid16d0932006-08-17 11:21:23 +02003302 # print only one empty line
3303 # do not print empty line after signoff
3304 if ($line eq "") {
3305 next if ($empty || $signoff);
3306 $empty = 1;
3307 } else {
3308 $empty = 0;
3309 }
Jakub Narebskib7f92532006-08-28 14:48:10 +02003310
3311 print format_log_line_html($line) . "<br/>\n";
3312 }
3313
3314 if ($opts{'-final_empty_line'}) {
3315 # end with single empty line
3316 print "<br/>\n" unless $empty;
Jakub Narebskid16d0932006-08-17 11:21:23 +02003317 }
3318}
3319
Jakub Narebskie33fba42006-12-10 13:25:46 +01003320# return link target (what link points to)
3321sub git_get_link_target {
3322 my $hash = shift;
3323 my $link_target;
3324
3325 # read link
3326 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
3327 or return;
3328 {
3329 local $/;
3330 $link_target = <$fd>;
3331 }
3332 close $fd
3333 or return;
3334
3335 return $link_target;
3336}
3337
Jakub Narebski3bf9d572006-12-10 13:25:48 +01003338# given link target, and the directory (basedir) the link is in,
3339# return target of link relative to top directory (top tree);
3340# return undef if it is not possible (including absolute links).
3341sub normalize_link_target {
3342 my ($link_target, $basedir, $hash_base) = @_;
3343
3344 # we can normalize symlink target only if $hash_base is provided
3345 return unless $hash_base;
3346
3347 # absolute symlinks (beginning with '/') cannot be normalized
3348 return if (substr($link_target, 0, 1) eq '/');
3349
3350 # normalize link target to path from top (root) tree (dir)
3351 my $path;
3352 if ($basedir) {
3353 $path = $basedir . '/' . $link_target;
3354 } else {
3355 # we are in top (root) tree (dir)
3356 $path = $link_target;
3357 }
3358
3359 # remove //, /./, and /../
3360 my @path_parts;
3361 foreach my $part (split('/', $path)) {
3362 # discard '.' and ''
3363 next if (!$part || $part eq '.');
3364 # handle '..'
3365 if ($part eq '..') {
3366 if (@path_parts) {
3367 pop @path_parts;
3368 } else {
3369 # link leads outside repository (outside top dir)
3370 return;
3371 }
3372 } else {
3373 push @path_parts, $part;
3374 }
3375 }
3376 $path = join('/', @path_parts);
3377
3378 return $path;
3379}
Jakub Narebskie33fba42006-12-10 13:25:46 +01003380
Jakub Narebskifa702002006-08-31 00:35:07 +02003381# print tree entry (row of git_tree), but without encompassing <tr> element
3382sub git_print_tree_entry {
3383 my ($t, $basedir, $hash_base, $have_blame) = @_;
3384
3385 my %base_key = ();
Jakub Narebskie33fba42006-12-10 13:25:46 +01003386 $base_key{'hash_base'} = $hash_base if defined $hash_base;
Jakub Narebskifa702002006-08-31 00:35:07 +02003387
Luben Tuikov4de741b2006-09-25 22:38:16 -07003388 # The format of a table row is: mode list link. Where mode is
3389 # the mode of the entry, list is the name of the entry, an href,
3390 # and link is the action links of the entry.
3391
Jakub Narebskifa702002006-08-31 00:35:07 +02003392 print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n";
3393 if ($t->{'type'} eq "blob") {
3394 print "<td class=\"list\">" .
Luben Tuikov4de741b2006-09-25 22:38:16 -07003395 $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02003396 file_name=>"$basedir$t->{'name'}", %base_key),
Jakub Narebskie33fba42006-12-10 13:25:46 +01003397 -class => "list"}, esc_path($t->{'name'}));
3398 if (S_ISLNK(oct $t->{'mode'})) {
3399 my $link_target = git_get_link_target($t->{'hash'});
3400 if ($link_target) {
Jakub Narebski3bf9d572006-12-10 13:25:48 +01003401 my $norm_target = normalize_link_target($link_target, $basedir, $hash_base);
3402 if (defined $norm_target) {
3403 print " -> " .
3404 $cgi->a({-href => href(action=>"object", hash_base=>$hash_base,
3405 file_name=>$norm_target),
3406 -title => $norm_target}, esc_path($link_target));
3407 } else {
3408 print " -> " . esc_path($link_target);
3409 }
Jakub Narebskie33fba42006-12-10 13:25:46 +01003410 }
3411 }
3412 print "</td>\n";
Luben Tuikov4de741b2006-09-25 22:38:16 -07003413 print "<td class=\"link\">";
Petr Baudis4777b012006-10-24 05:36:10 +02003414 print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
Jakub Narebskie33fba42006-12-10 13:25:46 +01003415 file_name=>"$basedir$t->{'name'}", %base_key)},
3416 "blob");
Jakub Narebskifa702002006-08-31 00:35:07 +02003417 if ($have_blame) {
Petr Baudis4777b012006-10-24 05:36:10 +02003418 print " | " .
3419 $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
Jakub Narebskie33fba42006-12-10 13:25:46 +01003420 file_name=>"$basedir$t->{'name'}", %base_key)},
3421 "blame");
Jakub Narebskifa702002006-08-31 00:35:07 +02003422 }
3423 if (defined $hash_base) {
Petr Baudis4777b012006-10-24 05:36:10 +02003424 print " | " .
3425 $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
Jakub Narebskifa702002006-08-31 00:35:07 +02003426 hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
3427 "history");
3428 }
3429 print " | " .
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07003430 $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base,
Jakub Narebskie7fb0222006-10-21 17:52:19 +02003431 file_name=>"$basedir$t->{'name'}")},
3432 "raw");
Luben Tuikov4de741b2006-09-25 22:38:16 -07003433 print "</td>\n";
Jakub Narebskifa702002006-08-31 00:35:07 +02003434
3435 } elsif ($t->{'type'} eq "tree") {
Luben Tuikov0fa105e2006-09-26 12:45:37 -07003436 print "<td class=\"list\">";
3437 print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
Jakub Narebskifa702002006-08-31 00:35:07 +02003438 file_name=>"$basedir$t->{'name'}", %base_key)},
Jakub Narebski403d0902006-11-08 11:48:56 +01003439 esc_path($t->{'name'}));
Luben Tuikov0fa105e2006-09-26 12:45:37 -07003440 print "</td>\n";
3441 print "<td class=\"link\">";
Petr Baudis4777b012006-10-24 05:36:10 +02003442 print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
Jakub Narebskie33fba42006-12-10 13:25:46 +01003443 file_name=>"$basedir$t->{'name'}", %base_key)},
3444 "tree");
Jakub Narebskifa702002006-08-31 00:35:07 +02003445 if (defined $hash_base) {
Petr Baudis4777b012006-10-24 05:36:10 +02003446 print " | " .
3447 $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
Jakub Narebskifa702002006-08-31 00:35:07 +02003448 file_name=>"$basedir$t->{'name'}")},
3449 "history");
3450 }
3451 print "</td>\n";
Jakub Narebski01ac1e32007-07-28 16:27:31 +02003452 } else {
3453 # unknown object: we can only present history for it
3454 # (this includes 'commit' object, i.e. submodule support)
3455 print "<td class=\"list\">" .
3456 esc_path($t->{'name'}) .
3457 "</td>\n";
3458 print "<td class=\"link\">";
3459 if (defined $hash_base) {
3460 print $cgi->a({-href => href(action=>"history",
3461 hash_base=>$hash_base,
3462 file_name=>"$basedir$t->{'name'}")},
3463 "history");
3464 }
3465 print "</td>\n";
Jakub Narebskifa702002006-08-31 00:35:07 +02003466 }
3467}
3468
Jakub Narebski717b8312006-07-31 21:22:15 +02003469## ......................................................................
3470## functions printing large fragments of HTML
Kay Sieversede5e102005-08-07 20:23:12 +02003471
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003472# get pre-image filenames for merge (combined) diff
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02003473sub fill_from_file_info {
3474 my ($diff, @parents) = @_;
3475
3476 $diff->{'from_file'} = [ ];
3477 $diff->{'from_file'}[$diff->{'nparents'} - 1] = undef;
3478 for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
3479 if ($diff->{'status'}[$i] eq 'R' ||
3480 $diff->{'status'}[$i] eq 'C') {
3481 $diff->{'from_file'}[$i] =
3482 git_get_path_by_hash($parents[$i], $diff->{'from_id'}[$i]);
3483 }
3484 }
3485
3486 return $diff;
3487}
3488
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003489# is current raw difftree line of file deletion
Jakub Narebski90921742007-06-08 13:27:42 +02003490sub is_deleted {
3491 my $diffinfo = shift;
3492
Jakub Narebski4ed4a342008-04-05 21:13:24 +01003493 return $diffinfo->{'to_id'} eq ('0' x 40);
Jakub Narebski90921742007-06-08 13:27:42 +02003494}
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02003495
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003496# does patch correspond to [previous] difftree raw line
3497# $diffinfo - hashref of parsed raw diff format
3498# $patchinfo - hashref of parsed patch diff format
3499# (the same keys as in $diffinfo)
3500sub is_patch_split {
3501 my ($diffinfo, $patchinfo) = @_;
3502
3503 return defined $diffinfo && defined $patchinfo
Jakub Narebski9d301452007-11-01 12:38:08 +01003504 && $diffinfo->{'to_file'} eq $patchinfo->{'to_file'};
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003505}
3506
3507
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003508sub git_difftree_body {
Jakub Narebskied224de2007-05-07 01:10:04 +02003509 my ($difftree, $hash, @parents) = @_;
3510 my ($parent) = $parents[0];
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08003511 my $have_blame = gitweb_check_feature('blame');
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003512 print "<div class=\"list_head\">\n";
3513 if ($#{$difftree} > 10) {
3514 print(($#{$difftree} + 1) . " files changed:\n");
3515 }
3516 print "</div>\n";
3517
Jakub Narebskied224de2007-05-07 01:10:04 +02003518 print "<table class=\"" .
3519 (@parents > 1 ? "combined " : "") .
3520 "diff_tree\">\n";
Jakub Narebski47598d72007-06-08 13:24:56 +02003521
3522 # header only for combined diff in 'commitdiff' view
Jakub Narebski3ef408a2007-09-08 21:54:28 +02003523 my $has_header = @$difftree && @parents > 1 && $action eq 'commitdiff';
Jakub Narebski47598d72007-06-08 13:24:56 +02003524 if ($has_header) {
3525 # table header
3526 print "<thead><tr>\n" .
3527 "<th></th><th></th>\n"; # filename, patchN link
3528 for (my $i = 0; $i < @parents; $i++) {
3529 my $par = $parents[$i];
3530 print "<th>" .
3531 $cgi->a({-href => href(action=>"commitdiff",
3532 hash=>$hash, hash_parent=>$par),
3533 -title => 'commitdiff to parent number ' .
3534 ($i+1) . ': ' . substr($par,0,7)},
3535 $i+1) .
3536 "&nbsp;</th>\n";
3537 }
3538 print "</tr></thead>\n<tbody>\n";
3539 }
3540
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07003541 my $alternate = 1;
Jakub Narebskib4657e72006-08-28 14:48:14 +02003542 my $patchno = 0;
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003543 foreach my $line (@{$difftree}) {
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003544 my $diff = parsed_difftree_line($line);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003545
3546 if ($alternate) {
3547 print "<tr class=\"dark\">\n";
3548 } else {
3549 print "<tr class=\"light\">\n";
3550 }
3551 $alternate ^= 1;
3552
Jakub Narebski493e01d2007-05-07 01:10:06 +02003553 if (exists $diff->{'nparents'}) { # combined diff
Jakub Narebskied224de2007-05-07 01:10:04 +02003554
Jakub Narebski493e01d2007-05-07 01:10:06 +02003555 fill_from_file_info($diff, @parents)
3556 unless exists $diff->{'from_file'};
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02003557
Jakub Narebski90921742007-06-08 13:27:42 +02003558 if (!is_deleted($diff)) {
Jakub Narebskied224de2007-05-07 01:10:04 +02003559 # file exists in the result (child) commit
3560 print "<td>" .
Jakub Narebski493e01d2007-05-07 01:10:06 +02003561 $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3562 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003563 hash_base=>$hash),
Jakub Narebski493e01d2007-05-07 01:10:06 +02003564 -class => "list"}, esc_path($diff->{'to_file'})) .
Jakub Narebskied224de2007-05-07 01:10:04 +02003565 "</td>\n";
3566 } else {
3567 print "<td>" .
Jakub Narebski493e01d2007-05-07 01:10:06 +02003568 esc_path($diff->{'to_file'}) .
Jakub Narebskied224de2007-05-07 01:10:04 +02003569 "</td>\n";
3570 }
3571
3572 if ($action eq 'commitdiff') {
3573 # link to patch
3574 $patchno++;
3575 print "<td class=\"link\">" .
3576 $cgi->a({-href => "#patch$patchno"}, "patch") .
3577 " | " .
3578 "</td>\n";
3579 }
3580
3581 my $has_history = 0;
3582 my $not_deleted = 0;
Jakub Narebski493e01d2007-05-07 01:10:06 +02003583 for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
Jakub Narebskied224de2007-05-07 01:10:04 +02003584 my $hash_parent = $parents[$i];
Jakub Narebski493e01d2007-05-07 01:10:06 +02003585 my $from_hash = $diff->{'from_id'}[$i];
3586 my $from_path = $diff->{'from_file'}[$i];
3587 my $status = $diff->{'status'}[$i];
Jakub Narebskied224de2007-05-07 01:10:04 +02003588
3589 $has_history ||= ($status ne 'A');
3590 $not_deleted ||= ($status ne 'D');
3591
Jakub Narebskied224de2007-05-07 01:10:04 +02003592 if ($status eq 'A') {
3593 print "<td class=\"link\" align=\"right\"> | </td>\n";
3594 } elsif ($status eq 'D') {
3595 print "<td class=\"link\">" .
3596 $cgi->a({-href => href(action=>"blob",
3597 hash_base=>$hash,
3598 hash=>$from_hash,
3599 file_name=>$from_path)},
3600 "blob" . ($i+1)) .
3601 " | </td>\n";
3602 } else {
Jakub Narebski493e01d2007-05-07 01:10:06 +02003603 if ($diff->{'to_id'} eq $from_hash) {
Jakub Narebskied224de2007-05-07 01:10:04 +02003604 print "<td class=\"link nochange\">";
3605 } else {
3606 print "<td class=\"link\">";
3607 }
3608 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003609 hash=>$diff->{'to_id'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003610 hash_parent=>$from_hash,
3611 hash_base=>$hash,
3612 hash_parent_base=>$hash_parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003613 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003614 file_parent=>$from_path)},
3615 "diff" . ($i+1)) .
3616 " | </td>\n";
3617 }
3618 }
3619
3620 print "<td class=\"link\">";
3621 if ($not_deleted) {
3622 print $cgi->a({-href => href(action=>"blob",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003623 hash=>$diff->{'to_id'},
3624 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003625 hash_base=>$hash)},
3626 "blob");
3627 print " | " if ($has_history);
3628 }
3629 if ($has_history) {
3630 print $cgi->a({-href => href(action=>"history",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003631 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003632 hash_base=>$hash)},
3633 "history");
3634 }
3635 print "</td>\n";
3636
3637 print "</tr>\n";
3638 next; # instead of 'else' clause, to avoid extra indent
3639 }
3640 # else ordinary diff
3641
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003642 my ($to_mode_oct, $to_mode_str, $to_file_type);
3643 my ($from_mode_oct, $from_mode_str, $from_file_type);
Jakub Narebski493e01d2007-05-07 01:10:06 +02003644 if ($diff->{'to_mode'} ne ('0' x 6)) {
3645 $to_mode_oct = oct $diff->{'to_mode'};
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003646 if (S_ISREG($to_mode_oct)) { # only for regular file
3647 $to_mode_str = sprintf("%04o", $to_mode_oct & 0777); # permission bits
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003648 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003649 $to_file_type = file_type($diff->{'to_mode'});
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003650 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003651 if ($diff->{'from_mode'} ne ('0' x 6)) {
3652 $from_mode_oct = oct $diff->{'from_mode'};
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003653 if (S_ISREG($to_mode_oct)) { # only for regular file
3654 $from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits
3655 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003656 $from_file_type = file_type($diff->{'from_mode'});
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003657 }
3658
Jakub Narebski493e01d2007-05-07 01:10:06 +02003659 if ($diff->{'status'} eq "A") { # created
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003660 my $mode_chng = "<span class=\"file_status new\">[new $to_file_type";
3661 $mode_chng .= " with mode: $to_mode_str" if $to_mode_str;
3662 $mode_chng .= "]</span>";
Luben Tuikov499faed2006-09-27 17:23:25 -07003663 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003664 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3665 hash_base=>$hash, file_name=>$diff->{'file'}),
3666 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07003667 print "</td>\n";
3668 print "<td>$mode_chng</td>\n";
3669 print "<td class=\"link\">";
Jakub Narebski72dbafa2006-09-04 18:19:58 +02003670 if ($action eq 'commitdiff') {
Jakub Narebskib4657e72006-08-28 14:48:14 +02003671 # link to patch
3672 $patchno++;
Luben Tuikov499faed2006-09-27 17:23:25 -07003673 print $cgi->a({-href => "#patch$patchno"}, "patch");
Jakub Narebski897d1d22006-11-19 22:51:39 +01003674 print " | ";
Jakub Narebskib4657e72006-08-28 14:48:14 +02003675 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003676 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3677 hash_base=>$hash, file_name=>$diff->{'file'})},
Jakub Narebski3faa5412007-01-08 02:10:42 +01003678 "blob");
Jakub Narebskib4657e72006-08-28 14:48:14 +02003679 print "</td>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003680
Jakub Narebski493e01d2007-05-07 01:10:06 +02003681 } elsif ($diff->{'status'} eq "D") { # deleted
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003682 my $mode_chng = "<span class=\"file_status deleted\">[deleted $from_file_type]</span>";
Luben Tuikov499faed2006-09-27 17:23:25 -07003683 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003684 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
3685 hash_base=>$parent, file_name=>$diff->{'file'}),
3686 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07003687 print "</td>\n";
3688 print "<td>$mode_chng</td>\n";
3689 print "<td class=\"link\">";
Jakub Narebski72dbafa2006-09-04 18:19:58 +02003690 if ($action eq 'commitdiff') {
Jakub Narebskib4657e72006-08-28 14:48:14 +02003691 # link to patch
3692 $patchno++;
Luben Tuikov499faed2006-09-27 17:23:25 -07003693 print $cgi->a({-href => "#patch$patchno"}, "patch");
3694 print " | ";
Jakub Narebskib4657e72006-08-28 14:48:14 +02003695 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003696 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
3697 hash_base=>$parent, file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003698 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003699 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01003700 print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003701 file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003702 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003703 }
Jakub Narebskib4657e72006-08-28 14:48:14 +02003704 print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003705 file_name=>$diff->{'file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02003706 "history");
Luben Tuikov499faed2006-09-27 17:23:25 -07003707 print "</td>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003708
Jakub Narebski493e01d2007-05-07 01:10:06 +02003709 } elsif ($diff->{'status'} eq "M" || $diff->{'status'} eq "T") { # modified, or type changed
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003710 my $mode_chnge = "";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003711 if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003712 $mode_chnge = "<span class=\"file_status mode_chnge\">[changed";
Jakub Narebski6e72cf42007-01-03 20:47:24 +01003713 if ($from_file_type ne $to_file_type) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003714 $mode_chnge .= " from $from_file_type to $to_file_type";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003715 }
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003716 if (($from_mode_oct & 0777) != ($to_mode_oct & 0777)) {
3717 if ($from_mode_str && $to_mode_str) {
3718 $mode_chnge .= " mode: $from_mode_str->$to_mode_str";
3719 } elsif ($to_mode_str) {
3720 $mode_chnge .= " mode: $to_mode_str";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003721 }
3722 }
3723 $mode_chnge .= "]</span>\n";
3724 }
3725 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003726 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3727 hash_base=>$hash, file_name=>$diff->{'file'}),
3728 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07003729 print "</td>\n";
3730 print "<td>$mode_chnge</td>\n";
3731 print "<td class=\"link\">";
Jakub Narebski241cc592006-10-31 17:36:27 +01003732 if ($action eq 'commitdiff') {
3733 # link to patch
3734 $patchno++;
3735 print $cgi->a({-href => "#patch$patchno"}, "patch") .
3736 " | ";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003737 } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
Jakub Narebski241cc592006-10-31 17:36:27 +01003738 # "commit" view and modified file (not onlu mode changed)
3739 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003740 hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
Jakub Narebski241cc592006-10-31 17:36:27 +01003741 hash_base=>$hash, hash_parent_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003742 file_name=>$diff->{'file'})},
Jakub Narebski241cc592006-10-31 17:36:27 +01003743 "diff") .
3744 " | ";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003745 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003746 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3747 hash_base=>$hash, file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003748 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003749 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01003750 print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003751 file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003752 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003753 }
Luben Tuikoveb51ec92006-09-27 17:24:49 -07003754 print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003755 file_name=>$diff->{'file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02003756 "history");
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003757 print "</td>\n";
3758
Jakub Narebski493e01d2007-05-07 01:10:06 +02003759 } elsif ($diff->{'status'} eq "R" || $diff->{'status'} eq "C") { # renamed or copied
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003760 my %status_name = ('R' => 'moved', 'C' => 'copied');
Jakub Narebski493e01d2007-05-07 01:10:06 +02003761 my $nstatus = $status_name{$diff->{'status'}};
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003762 my $mode_chng = "";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003763 if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003764 # mode also for directories, so we cannot use $to_mode_str
3765 $mode_chng = sprintf(", mode: %04o", $to_mode_oct & 0777);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003766 }
3767 print "<td>" .
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003768 $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003769 hash=>$diff->{'to_id'}, file_name=>$diff->{'to_file'}),
3770 -class => "list"}, esc_path($diff->{'to_file'})) . "</td>\n" .
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003771 "<td><span class=\"file_status $nstatus\">[$nstatus from " .
3772 $cgi->a({-href => href(action=>"blob", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003773 hash=>$diff->{'from_id'}, file_name=>$diff->{'from_file'}),
3774 -class => "list"}, esc_path($diff->{'from_file'})) .
3775 " with " . (int $diff->{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
Luben Tuikov499faed2006-09-27 17:23:25 -07003776 "<td class=\"link\">";
Jakub Narebski241cc592006-10-31 17:36:27 +01003777 if ($action eq 'commitdiff') {
3778 # link to patch
3779 $patchno++;
3780 print $cgi->a({-href => "#patch$patchno"}, "patch") .
3781 " | ";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003782 } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
Jakub Narebski241cc592006-10-31 17:36:27 +01003783 # "commit" view and modified file (not only pure rename or copy)
3784 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003785 hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
Jakub Narebski241cc592006-10-31 17:36:27 +01003786 hash_base=>$hash, hash_parent_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003787 file_name=>$diff->{'to_file'}, file_parent=>$diff->{'from_file'})},
Jakub Narebski241cc592006-10-31 17:36:27 +01003788 "diff") .
3789 " | ";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003790 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003791 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3792 hash_base=>$parent, file_name=>$diff->{'to_file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003793 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003794 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01003795 print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003796 file_name=>$diff->{'to_file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003797 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003798 }
Jakub Narebski897d1d22006-11-19 22:51:39 +01003799 print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003800 file_name=>$diff->{'to_file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02003801 "history");
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003802 print "</td>\n";
3803
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003804 } # we should not encounter Unmerged (U) or Unknown (X) status
3805 print "</tr>\n";
3806 }
Jakub Narebski47598d72007-06-08 13:24:56 +02003807 print "</tbody>" if $has_header;
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003808 print "</table>\n";
3809}
3810
Jakub Narebskieee08902006-08-24 00:15:14 +02003811sub git_patchset_body {
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02003812 my ($fd, $difftree, $hash, @hash_parents) = @_;
3813 my ($hash_parent) = $hash_parents[0];
Jakub Narebskieee08902006-08-24 00:15:14 +02003814
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003815 my $is_combined = (@hash_parents > 1);
Jakub Narebskieee08902006-08-24 00:15:14 +02003816 my $patch_idx = 0;
Martin Koegler4280cde2007-04-22 22:49:25 -07003817 my $patch_number = 0;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003818 my $patch_line;
Jakub Narebskife875852006-08-25 20:59:39 +02003819 my $diffinfo;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003820 my $to_name;
Jakub Narebski744d0ac2006-11-08 17:59:41 +01003821 my (%from, %to);
Jakub Narebskieee08902006-08-24 00:15:14 +02003822
3823 print "<div class=\"patchset\">\n";
3824
Jakub Narebski6d55f052006-11-18 23:35:39 +01003825 # skip to first patch
3826 while ($patch_line = <$fd>) {
Jakub Narebski157e43b2006-08-24 19:34:36 +02003827 chomp $patch_line;
Jakub Narebskieee08902006-08-24 00:15:14 +02003828
Jakub Narebski6d55f052006-11-18 23:35:39 +01003829 last if ($patch_line =~ m/^diff /);
3830 }
3831
3832 PATCH:
3833 while ($patch_line) {
Jakub Narebski6d55f052006-11-18 23:35:39 +01003834
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003835 # parse "git diff" header line
3836 if ($patch_line =~ m/^diff --git (\"(?:[^\\\"]*(?:\\.[^\\\"]*)*)\"|[^ "]*) (.*)$/) {
3837 # $1 is from_name, which we do not use
3838 $to_name = unquote($2);
3839 $to_name =~ s!^b/!!;
3840 } elsif ($patch_line =~ m/^diff --(cc|combined) ("?.*"?)$/) {
3841 # $1 is 'cc' or 'combined', which we do not use
3842 $to_name = unquote($2);
3843 } else {
3844 $to_name = undef;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003845 }
Jakub Narebski6d55f052006-11-18 23:35:39 +01003846
3847 # check if current patch belong to current raw line
3848 # and parse raw git-diff line if needed
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003849 if (is_patch_split($diffinfo, { 'to_file' => $to_name })) {
Jakub Narebski22065372007-05-12 12:42:32 +02003850 # this is continuation of a split patch
Jakub Narebski6d55f052006-11-18 23:35:39 +01003851 print "<div class=\"patch cont\">\n";
3852 } else {
3853 # advance raw git-diff output if needed
3854 $patch_idx++ if defined $diffinfo;
Jakub Narebskieee08902006-08-24 00:15:14 +02003855
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003856 # read and prepare patch information
3857 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
3858
Jakub Narebskicd030c32007-06-08 13:33:28 +02003859 # compact combined diff output can have some patches skipped
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003860 # find which patch (using pathname of result) we are at now;
3861 if ($is_combined) {
3862 while ($to_name ne $diffinfo->{'to_file'}) {
Jakub Narebskicd030c32007-06-08 13:33:28 +02003863 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
3864 format_diff_cc_simplified($diffinfo, @hash_parents) .
3865 "</div>\n"; # class="patch"
3866
3867 $patch_idx++;
3868 $patch_number++;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003869
3870 last if $patch_idx > $#$difftree;
3871 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
Jakub Narebskicd030c32007-06-08 13:33:28 +02003872 }
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003873 }
Jakub Narebski711fa742007-09-08 21:49:11 +02003874
Jakub Narebski90921742007-06-08 13:27:42 +02003875 # modifies %from, %to hashes
3876 parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents);
Jakub Narebski5f855052007-05-17 22:54:28 +02003877
Jakub Narebski6d55f052006-11-18 23:35:39 +01003878 # this is first patch for raw difftree line with $patch_idx index
3879 # we index @$difftree array from 0, but number patches from 1
3880 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
Jakub Narebski744d0ac2006-11-08 17:59:41 +01003881 }
Jakub Narebskieee08902006-08-24 00:15:14 +02003882
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003883 # git diff header
3884 #assert($patch_line =~ m/^diff /) if DEBUG;
3885 #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
3886 $patch_number++;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003887 # print "git diff" header
Jakub Narebski90921742007-06-08 13:27:42 +02003888 print format_git_diff_header_line($patch_line, $diffinfo,
3889 \%from, \%to);
Jakub Narebskieee08902006-08-24 00:15:14 +02003890
Jakub Narebski6d55f052006-11-18 23:35:39 +01003891 # print extended diff header
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003892 print "<div class=\"diff extended_header\">\n";
Jakub Narebski6d55f052006-11-18 23:35:39 +01003893 EXTENDED_HEADER:
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003894 while ($patch_line = <$fd>) {
3895 chomp $patch_line;
3896
3897 last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /);
3898
Jakub Narebski90921742007-06-08 13:27:42 +02003899 print format_extended_diff_header_line($patch_line, $diffinfo,
3900 \%from, \%to);
Jakub Narebski6d55f052006-11-18 23:35:39 +01003901 }
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003902 print "</div>\n"; # class="diff extended_header"
Jakub Narebskie4e4f822006-08-25 21:04:13 +02003903
Jakub Narebski6d55f052006-11-18 23:35:39 +01003904 # from-file/to-file diff header
Jakub Narebski0bdb28c2007-01-10 00:07:43 +01003905 if (! $patch_line) {
3906 print "</div>\n"; # class="patch"
3907 last PATCH;
3908 }
Jakub Narebski66399ef2007-01-07 02:52:25 +01003909 next PATCH if ($patch_line =~ m/^diff /);
Jakub Narebski6d55f052006-11-18 23:35:39 +01003910 #assert($patch_line =~ m/^---/) if DEBUG;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003911
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003912 my $last_patch_line = $patch_line;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003913 $patch_line = <$fd>;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003914 chomp $patch_line;
Jakub Narebski90921742007-06-08 13:27:42 +02003915 #assert($patch_line =~ m/^\+\+\+/) if DEBUG;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003916
Jakub Narebski90921742007-06-08 13:27:42 +02003917 print format_diff_from_to_header($last_patch_line, $patch_line,
Jakub Narebski91af4ce2007-06-08 13:32:44 +02003918 $diffinfo, \%from, \%to,
3919 @hash_parents);
Jakub Narebski6d55f052006-11-18 23:35:39 +01003920
3921 # the patch itself
3922 LINE:
3923 while ($patch_line = <$fd>) {
3924 chomp $patch_line;
3925
3926 next PATCH if ($patch_line =~ m/^diff /);
3927
Jakub Narebski59e3b142006-11-18 23:35:40 +01003928 print format_diff_line($patch_line, \%from, \%to);
Jakub Narebskieee08902006-08-24 00:15:14 +02003929 }
Jakub Narebskieee08902006-08-24 00:15:14 +02003930
Jakub Narebski6d55f052006-11-18 23:35:39 +01003931 } continue {
3932 print "</div>\n"; # class="patch"
Jakub Narebskieee08902006-08-24 00:15:14 +02003933 }
Jakub Narebskid26c4262007-05-17 00:05:55 +02003934
Jakub Narebskicd030c32007-06-08 13:33:28 +02003935 # for compact combined (--cc) format, with chunk and patch simpliciaction
3936 # patchset might be empty, but there might be unprocessed raw lines
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003937 for (++$patch_idx if $patch_number > 0;
Jakub Narebskicd030c32007-06-08 13:33:28 +02003938 $patch_idx < @$difftree;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003939 ++$patch_idx) {
Jakub Narebskicd030c32007-06-08 13:33:28 +02003940 # read and prepare patch information
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003941 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
Jakub Narebskicd030c32007-06-08 13:33:28 +02003942
3943 # generate anchor for "patch" links in difftree / whatchanged part
3944 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
3945 format_diff_cc_simplified($diffinfo, @hash_parents) .
3946 "</div>\n"; # class="patch"
3947
3948 $patch_number++;
3949 }
3950
Jakub Narebskid26c4262007-05-17 00:05:55 +02003951 if ($patch_number == 0) {
3952 if (@hash_parents > 1) {
3953 print "<div class=\"diff nodifferences\">Trivial merge</div>\n";
3954 } else {
3955 print "<div class=\"diff nodifferences\">No differences found</div>\n";
3956 }
3957 }
Jakub Narebskieee08902006-08-24 00:15:14 +02003958
3959 print "</div>\n"; # class="patchset"
3960}
3961
3962# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3963
Jakub Narebski69913412008-06-10 19:21:01 +02003964# fills project list info (age, description, owner, forks) for each
3965# project in the list, removing invalid projects from returned list
3966# NOTE: modifies $projlist, but does not remove entries from it
3967sub fill_project_list_info {
3968 my ($projlist, $check_forks) = @_;
Petr Baudise30496d2006-10-24 05:33:17 +02003969 my @projects;
Jakub Narebski69913412008-06-10 19:21:01 +02003970
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08003971 my $show_ctags = gitweb_check_feature('ctags');
Jakub Narebski69913412008-06-10 19:21:01 +02003972 PROJECT:
Petr Baudise30496d2006-10-24 05:33:17 +02003973 foreach my $pr (@$projlist) {
Jakub Narebski69913412008-06-10 19:21:01 +02003974 my (@activity) = git_get_last_activity($pr->{'path'});
3975 unless (@activity) {
3976 next PROJECT;
Petr Baudise30496d2006-10-24 05:33:17 +02003977 }
Jakub Narebski69913412008-06-10 19:21:01 +02003978 ($pr->{'age'}, $pr->{'age_string'}) = @activity;
Petr Baudise30496d2006-10-24 05:33:17 +02003979 if (!defined $pr->{'descr'}) {
3980 my $descr = git_get_project_description($pr->{'path'}) || "";
Jakub Narebski69913412008-06-10 19:21:01 +02003981 $descr = to_utf8($descr);
3982 $pr->{'descr_long'} = $descr;
Michael Hendricks55feb122007-07-04 18:36:48 -06003983 $pr->{'descr'} = chop_str($descr, $projects_list_description_width, 5);
Petr Baudise30496d2006-10-24 05:33:17 +02003984 }
3985 if (!defined $pr->{'owner'}) {
Miklos Vajna76e4f5d2007-07-04 00:11:23 +02003986 $pr->{'owner'} = git_get_project_owner("$pr->{'path'}") || "";
Petr Baudise30496d2006-10-24 05:33:17 +02003987 }
3988 if ($check_forks) {
3989 my $pname = $pr->{'path'};
Junio C Hamano83ee94c2006-11-07 22:37:17 -08003990 if (($pname =~ s/\.git$//) &&
3991 ($pname !~ /\/$/) &&
3992 (-d "$projectroot/$pname")) {
3993 $pr->{'forks'} = "-d $projectroot/$pname";
Jakub Narebski69913412008-06-10 19:21:01 +02003994 } else {
Junio C Hamano83ee94c2006-11-07 22:37:17 -08003995 $pr->{'forks'} = 0;
3996 }
Petr Baudise30496d2006-10-24 05:33:17 +02003997 }
Petr Baudisaed93de2008-10-02 17:13:02 +02003998 $show_ctags and $pr->{'ctags'} = git_get_project_ctags($pr->{'path'});
Petr Baudise30496d2006-10-24 05:33:17 +02003999 push @projects, $pr;
4000 }
4001
Jakub Narebski69913412008-06-10 19:21:01 +02004002 return @projects;
4003}
4004
Petr Baudis6b28da62008-09-25 18:48:37 +02004005# print 'sort by' <th> element, generating 'sort by $name' replay link
4006# if that order is not selected
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02004007sub print_sort_th {
Petr Baudis6b28da62008-09-25 18:48:37 +02004008 my ($name, $order, $header) = @_;
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02004009 $header ||= ucfirst($name);
4010
4011 if ($order eq $name) {
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02004012 print "<th>$header</th>\n";
4013 } else {
4014 print "<th>" .
4015 $cgi->a({-href => href(-replay=>1, order=>$name),
4016 -class => "header"}, $header) .
4017 "</th>\n";
4018 }
4019}
4020
Jakub Narebski69913412008-06-10 19:21:01 +02004021sub git_project_list_body {
Petr Baudis42326112008-10-02 17:17:01 +02004022 # actually uses global variable $project
Jakub Narebski69913412008-06-10 19:21:01 +02004023 my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
4024
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004025 my $check_forks = gitweb_check_feature('forks');
Jakub Narebski69913412008-06-10 19:21:01 +02004026 my @projects = fill_project_list_info($projlist, $check_forks);
4027
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02004028 $order ||= $default_projects_order;
Petr Baudise30496d2006-10-24 05:33:17 +02004029 $from = 0 unless defined $from;
4030 $to = $#projects if (!defined $to || $#projects < $to);
4031
Petr Baudis6b28da62008-09-25 18:48:37 +02004032 my %order_info = (
4033 project => { key => 'path', type => 'str' },
4034 descr => { key => 'descr_long', type => 'str' },
4035 owner => { key => 'owner', type => 'str' },
4036 age => { key => 'age', type => 'num' }
4037 );
4038 my $oi = $order_info{$order};
4039 if ($oi->{'type'} eq 'str') {
4040 @projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @projects;
4041 } else {
4042 @projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects;
4043 }
4044
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004045 my $show_ctags = gitweb_check_feature('ctags');
Petr Baudisaed93de2008-10-02 17:13:02 +02004046 if ($show_ctags) {
4047 my %ctags;
4048 foreach my $p (@projects) {
4049 foreach my $ct (keys %{$p->{'ctags'}}) {
4050 $ctags{$ct} += $p->{'ctags'}->{$ct};
4051 }
4052 }
4053 my $cloud = git_populate_project_tagcloud(\%ctags);
4054 print git_show_project_tagcloud($cloud, 64);
4055 }
4056
Petr Baudise30496d2006-10-24 05:33:17 +02004057 print "<table class=\"project_list\">\n";
4058 unless ($no_header) {
4059 print "<tr>\n";
4060 if ($check_forks) {
4061 print "<th></th>\n";
4062 }
Petr Baudis6b28da62008-09-25 18:48:37 +02004063 print_sort_th('project', $order, 'Project');
4064 print_sort_th('descr', $order, 'Description');
4065 print_sort_th('owner', $order, 'Owner');
4066 print_sort_th('age', $order, 'Last Change');
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02004067 print "<th></th>\n" . # for links
Petr Baudise30496d2006-10-24 05:33:17 +02004068 "</tr>\n";
4069 }
4070 my $alternate = 1;
Petr Baudisaed93de2008-10-02 17:13:02 +02004071 my $tagfilter = $cgi->param('by_tag');
Petr Baudise30496d2006-10-24 05:33:17 +02004072 for (my $i = $from; $i <= $to; $i++) {
4073 my $pr = $projects[$i];
Petr Baudis42326112008-10-02 17:17:01 +02004074
Petr Baudisaed93de2008-10-02 17:13:02 +02004075 next if $tagfilter and $show_ctags and not grep { lc $_ eq lc $tagfilter } keys %{$pr->{'ctags'}};
Petr Baudis0d1d1542008-10-03 09:29:45 +02004076 next if $searchtext and not $pr->{'path'} =~ /$searchtext/
4077 and not $pr->{'descr_long'} =~ /$searchtext/;
4078 # Weed out forks or non-matching entries of search
Petr Baudis42326112008-10-02 17:17:01 +02004079 if ($check_forks) {
4080 my $forkbase = $project; $forkbase ||= ''; $forkbase =~ s#\.git$#/#;
4081 $forkbase="^$forkbase" if $forkbase;
Petr Baudis0d1d1542008-10-03 09:29:45 +02004082 next if not $searchtext and not $tagfilter and $show_ctags
4083 and $pr->{'path'} =~ m#$forkbase.*/.*#; # regexp-safe
Petr Baudis42326112008-10-02 17:17:01 +02004084 }
4085
Petr Baudise30496d2006-10-24 05:33:17 +02004086 if ($alternate) {
4087 print "<tr class=\"dark\">\n";
4088 } else {
4089 print "<tr class=\"light\">\n";
4090 }
4091 $alternate ^= 1;
4092 if ($check_forks) {
4093 print "<td>";
4094 if ($pr->{'forks'}) {
Junio C Hamano83ee94c2006-11-07 22:37:17 -08004095 print "<!-- $pr->{'forks'} -->\n";
Petr Baudise30496d2006-10-24 05:33:17 +02004096 print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+");
4097 }
4098 print "</td>\n";
4099 }
4100 print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
4101 -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
Jakub Narebskie88ce8a2006-11-26 02:18:26 +01004102 "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
4103 -class => "list", -title => $pr->{'descr_long'}},
4104 esc_html($pr->{'descr'})) . "</td>\n" .
David Symondsd3cd2492007-10-23 11:31:23 +10004105 "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
Petr Baudise30496d2006-10-24 05:33:17 +02004106 print "<td class=\"". age_class($pr->{'age'}) . "\">" .
Jakub Narebski785cdea2007-05-13 12:39:22 +02004107 (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" .
Petr Baudise30496d2006-10-24 05:33:17 +02004108 "<td class=\"link\">" .
4109 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " .
Alexandre Julliardfaa1bbf2006-11-15 21:37:50 +01004110 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
Petr Baudise30496d2006-10-24 05:33:17 +02004111 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
4112 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
4113 ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') .
4114 "</td>\n" .
4115 "</tr>\n";
4116 }
4117 if (defined $extra) {
4118 print "<tr>\n";
4119 if ($check_forks) {
4120 print "<td></td>\n";
4121 }
4122 print "<td colspan=\"5\">$extra</td>\n" .
4123 "</tr>\n";
4124 }
4125 print "</table>\n";
4126}
4127
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004128sub git_shortlog_body {
4129 # uses global variable $project
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00004130 my ($commitlist, $from, $to, $refs, $extra) = @_;
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05304131
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004132 $from = 0 unless defined $from;
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00004133 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004134
Jakub Narebski591ebf62007-11-19 14:16:11 +01004135 print "<table class=\"shortlog\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004136 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004137 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00004138 my %co = %{$commitlist->[$i]};
4139 my $commit = $co{'id'};
Jakub Narebski847e01f2006-08-14 02:05:47 +02004140 my $ref = format_ref_marker($refs, $commit);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004141 if ($alternate) {
4142 print "<tr class=\"dark\">\n";
4143 } else {
4144 print "<tr class=\"light\">\n";
4145 }
4146 $alternate ^= 1;
David Symondsce58ec92007-10-23 11:31:22 +10004147 my $author = chop_and_escape_str($co{'author_name'}, 10);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004148 # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
4149 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
David Symondse076a0e2007-10-22 10:28:03 +10004150 "<td><i>" . $author . "</i></td>\n" .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004151 "<td>";
Jakub Narebski952c65f2006-08-22 16:52:50 +02004152 print format_subject_html($co{'title'}, $co{'title_short'},
4153 href(action=>"commit", hash=>$commit), $ref);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004154 print "</td>\n" .
4155 "<td class=\"link\">" .
Petr Baudis4777b012006-10-24 05:36:10 +02004156 $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
Petr Baudis35749ae2006-09-22 03:19:44 +02004157 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
Petr Baudis55ff35c2006-10-06 15:57:52 +02004158 $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004159 my $snapshot_links = format_snapshot_links($commit);
4160 if (defined $snapshot_links) {
4161 print " | " . $snapshot_links;
Petr Baudis55ff35c2006-10-06 15:57:52 +02004162 }
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304163 print "</td>\n" .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004164 "</tr>\n";
4165 }
4166 if (defined $extra) {
4167 print "<tr>\n" .
4168 "<td colspan=\"4\">$extra</td>\n" .
4169 "</tr>\n";
4170 }
4171 print "</table>\n";
4172}
4173
Jakub Narebski581860e2006-08-14 02:09:08 +02004174sub git_history_body {
4175 # Warning: assumes constant type (blob or tree) during history
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00004176 my ($commitlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_;
Jakub Narebski8be68352006-09-11 00:36:04 +02004177
4178 $from = 0 unless defined $from;
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00004179 $to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist});
Jakub Narebski581860e2006-08-14 02:09:08 +02004180
Jakub Narebski591ebf62007-11-19 14:16:11 +01004181 print "<table class=\"history\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004182 my $alternate = 1;
Jakub Narebski8be68352006-09-11 00:36:04 +02004183 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00004184 my %co = %{$commitlist->[$i]};
Jakub Narebski581860e2006-08-14 02:09:08 +02004185 if (!%co) {
4186 next;
4187 }
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00004188 my $commit = $co{'id'};
Jakub Narebski581860e2006-08-14 02:09:08 +02004189
4190 my $ref = format_ref_marker($refs, $commit);
4191
4192 if ($alternate) {
4193 print "<tr class=\"dark\">\n";
4194 } else {
4195 print "<tr class=\"light\">\n";
4196 }
4197 $alternate ^= 1;
David Symondse076a0e2007-10-22 10:28:03 +10004198 # shortlog uses chop_str($co{'author_name'}, 10)
David Symondsce58ec92007-10-23 11:31:22 +10004199 my $author = chop_and_escape_str($co{'author_name'}, 15, 3);
Jakub Narebski581860e2006-08-14 02:09:08 +02004200 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
David Symondse076a0e2007-10-22 10:28:03 +10004201 "<td><i>" . $author . "</i></td>\n" .
Jakub Narebski581860e2006-08-14 02:09:08 +02004202 "<td>";
4203 # originally git_history used chop_str($co{'title'}, 50)
Jakub Narebski952c65f2006-08-22 16:52:50 +02004204 print format_subject_html($co{'title'}, $co{'title_short'},
4205 href(action=>"commit", hash=>$commit), $ref);
Jakub Narebski581860e2006-08-14 02:09:08 +02004206 print "</td>\n" .
4207 "<td class=\"link\">" .
Luben Tuikov6d81c5a2006-09-28 17:21:07 -07004208 $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype) . " | " .
4209 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
Jakub Narebski581860e2006-08-14 02:09:08 +02004210
4211 if ($ftype eq 'blob') {
4212 my $blob_current = git_get_hash_by_path($hash_base, $file_name);
4213 my $blob_parent = git_get_hash_by_path($commit, $file_name);
4214 if (defined $blob_current && defined $blob_parent &&
4215 $blob_current ne $blob_parent) {
4216 print " | " .
Jakub Narebski420e92f2006-08-24 23:53:54 +02004217 $cgi->a({-href => href(action=>"blobdiff",
4218 hash=>$blob_current, hash_parent=>$blob_parent,
4219 hash_base=>$hash_base, hash_parent_base=>$commit,
4220 file_name=>$file_name)},
Jakub Narebski581860e2006-08-14 02:09:08 +02004221 "diff to current");
4222 }
4223 }
4224 print "</td>\n" .
4225 "</tr>\n";
4226 }
4227 if (defined $extra) {
4228 print "<tr>\n" .
4229 "<td colspan=\"4\">$extra</td>\n" .
4230 "</tr>\n";
4231 }
4232 print "</table>\n";
4233}
4234
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004235sub git_tags_body {
4236 # uses global variable $project
4237 my ($taglist, $from, $to, $extra) = @_;
4238 $from = 0 unless defined $from;
4239 $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
4240
Jakub Narebski591ebf62007-11-19 14:16:11 +01004241 print "<table class=\"tags\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004242 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004243 for (my $i = $from; $i <= $to; $i++) {
4244 my $entry = $taglist->[$i];
4245 my %tag = %$entry;
Jakub Narebskicd146402006-11-02 20:23:11 +01004246 my $comment = $tag{'subject'};
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004247 my $comment_short;
4248 if (defined $comment) {
4249 $comment_short = chop_str($comment, 30, 5);
4250 }
4251 if ($alternate) {
4252 print "<tr class=\"dark\">\n";
4253 } else {
4254 print "<tr class=\"light\">\n";
4255 }
4256 $alternate ^= 1;
Jakub Narebski27dd1a82007-01-03 22:54:29 +01004257 if (defined $tag{'age'}) {
4258 print "<td><i>$tag{'age'}</i></td>\n";
4259 } else {
4260 print "<td></td>\n";
4261 }
4262 print "<td>" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004263 $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'}),
Jakub Narebski63e42202006-08-22 12:38:59 +02004264 -class => "list name"}, esc_html($tag{'name'})) .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004265 "</td>\n" .
4266 "<td>";
4267 if (defined $comment) {
Jakub Narebski952c65f2006-08-22 16:52:50 +02004268 print format_subject_html($comment, $comment_short,
4269 href(action=>"tag", hash=>$tag{'id'}));
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004270 }
4271 print "</td>\n" .
4272 "<td class=\"selflink\">";
4273 if ($tag{'type'} eq "tag") {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004274 print $cgi->a({-href => href(action=>"tag", hash=>$tag{'id'})}, "tag");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004275 } else {
4276 print "&nbsp;";
4277 }
4278 print "</td>\n" .
4279 "<td class=\"link\">" . " | " .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004280 $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'});
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004281 if ($tag{'reftype'} eq "commit") {
Jakub Narebskibf901f82007-12-15 15:40:28 +01004282 print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'fullname'})}, "shortlog") .
4283 " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'fullname'})}, "log");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004284 } elsif ($tag{'reftype'} eq "blob") {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004285 print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004286 }
4287 print "</td>\n" .
4288 "</tr>";
4289 }
4290 if (defined $extra) {
4291 print "<tr>\n" .
4292 "<td colspan=\"5\">$extra</td>\n" .
4293 "</tr>\n";
4294 }
4295 print "</table>\n";
4296}
4297
4298sub git_heads_body {
4299 # uses global variable $project
Jakub Narebski120ddde2006-09-19 14:33:22 +02004300 my ($headlist, $head, $from, $to, $extra) = @_;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004301 $from = 0 unless defined $from;
Jakub Narebski120ddde2006-09-19 14:33:22 +02004302 $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004303
Jakub Narebski591ebf62007-11-19 14:16:11 +01004304 print "<table class=\"heads\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004305 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004306 for (my $i = $from; $i <= $to; $i++) {
Jakub Narebski120ddde2006-09-19 14:33:22 +02004307 my $entry = $headlist->[$i];
Jakub Narebskicd146402006-11-02 20:23:11 +01004308 my %ref = %$entry;
4309 my $curr = $ref{'id'} eq $head;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004310 if ($alternate) {
4311 print "<tr class=\"dark\">\n";
4312 } else {
4313 print "<tr class=\"light\">\n";
4314 }
4315 $alternate ^= 1;
Jakub Narebskicd146402006-11-02 20:23:11 +01004316 print "<td><i>$ref{'age'}</i></td>\n" .
4317 ($curr ? "<td class=\"current_head\">" : "<td>") .
Jakub Narebskibf901f82007-12-15 15:40:28 +01004318 $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'}),
Jakub Narebskicd146402006-11-02 20:23:11 +01004319 -class => "list name"},esc_html($ref{'name'})) .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004320 "</td>\n" .
4321 "<td class=\"link\">" .
Jakub Narebskibf901f82007-12-15 15:40:28 +01004322 $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'})}, "shortlog") . " | " .
4323 $cgi->a({-href => href(action=>"log", hash=>$ref{'fullname'})}, "log") . " | " .
4324 $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'name'})}, "tree") .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004325 "</td>\n" .
4326 "</tr>";
4327 }
4328 if (defined $extra) {
4329 print "<tr>\n" .
4330 "<td colspan=\"3\">$extra</td>\n" .
4331 "</tr>\n";
4332 }
4333 print "</table>\n";
4334}
4335
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004336sub git_search_grep_body {
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00004337 my ($commitlist, $from, $to, $extra) = @_;
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004338 $from = 0 unless defined $from;
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00004339 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004340
Jakub Narebski591ebf62007-11-19 14:16:11 +01004341 print "<table class=\"commit_search\">\n";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004342 my $alternate = 1;
4343 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00004344 my %co = %{$commitlist->[$i]};
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004345 if (!%co) {
4346 next;
4347 }
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00004348 my $commit = $co{'id'};
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004349 if ($alternate) {
4350 print "<tr class=\"dark\">\n";
4351 } else {
4352 print "<tr class=\"light\">\n";
4353 }
4354 $alternate ^= 1;
David Symondsce58ec92007-10-23 11:31:22 +10004355 my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004356 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
David Symondse076a0e2007-10-22 10:28:03 +10004357 "<td><i>" . $author . "</i></td>\n" .
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004358 "<td>" .
Junio C Hamanobe8b9062008-02-22 17:33:47 +01004359 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
4360 -class => "list subject"},
4361 chop_and_escape_str($co{'title'}, 50) . "<br/>");
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004362 my $comment = $co{'comment'};
4363 foreach my $line (@$comment) {
Jakub Narebski6dfbb302008-03-02 16:57:14 +01004364 if ($line =~ m/^(.*?)($search_regexp)(.*)$/i) {
Junio C Hamanobe8b9062008-02-22 17:33:47 +01004365 my ($lead, $match, $trail) = ($1, $2, $3);
Jakub Narebskib8d97d02008-02-25 21:07:57 +01004366 $match = chop_str($match, 70, 5, 'center');
4367 my $contextlen = int((80 - length($match))/2);
4368 $contextlen = 30 if ($contextlen > 30);
4369 $lead = chop_str($lead, $contextlen, 10, 'left');
4370 $trail = chop_str($trail, $contextlen, 10, 'right');
Junio C Hamanobe8b9062008-02-22 17:33:47 +01004371
4372 $lead = esc_html($lead);
4373 $match = esc_html($match);
4374 $trail = esc_html($trail);
4375
4376 print "$lead<span class=\"match\">$match</span>$trail<br />";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004377 }
4378 }
4379 print "</td>\n" .
4380 "<td class=\"link\">" .
4381 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
4382 " | " .
Denis Chengf1fe8f52007-11-26 20:42:06 +08004383 $cgi->a({-href => href(action=>"commitdiff", hash=>$co{'id'})}, "commitdiff") .
4384 " | " .
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004385 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
4386 print "</td>\n" .
4387 "</tr>\n";
4388 }
4389 if (defined $extra) {
4390 print "<tr>\n" .
4391 "<td colspan=\"3\">$extra</td>\n" .
4392 "</tr>\n";
4393 }
4394 print "</table>\n";
4395}
4396
Jakub Narebski717b8312006-07-31 21:22:15 +02004397## ======================================================================
4398## ======================================================================
4399## actions
4400
Jakub Narebski717b8312006-07-31 21:22:15 +02004401sub git_project_list {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02004402 my $order = $input_params{'order'};
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02004403 if (defined $order && $order !~ m/none|project|descr|owner|age/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004404 die_error(400, "Unknown order parameter");
Jakub Narebski6326b602006-08-01 02:59:12 +02004405 }
4406
Jakub Narebski847e01f2006-08-14 02:05:47 +02004407 my @list = git_get_projects_list();
Jakub Narebski717b8312006-07-31 21:22:15 +02004408 if (!@list) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004409 die_error(404, "No projects found");
Jakub Narebski717b8312006-07-31 21:22:15 +02004410 }
Jakub Narebski6326b602006-08-01 02:59:12 +02004411
Jakub Narebski717b8312006-07-31 21:22:15 +02004412 git_header_html();
4413 if (-f $home_text) {
4414 print "<div class=\"index_include\">\n";
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01004415 insert_file($home_text);
Jakub Narebski717b8312006-07-31 21:22:15 +02004416 print "</div>\n";
4417 }
Petr Baudis0d1d1542008-10-03 09:29:45 +02004418 print $cgi->startform(-method => "get") .
4419 "<p class=\"projsearch\">Search:\n" .
4420 $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
4421 "</p>" .
4422 $cgi->end_form() . "\n";
Petr Baudise30496d2006-10-24 05:33:17 +02004423 git_project_list_body(\@list, $order);
4424 git_footer_html();
4425}
4426
4427sub git_forks {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02004428 my $order = $input_params{'order'};
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02004429 if (defined $order && $order !~ m/none|project|descr|owner|age/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004430 die_error(400, "Unknown order parameter");
Jakub Narebski717b8312006-07-31 21:22:15 +02004431 }
Petr Baudise30496d2006-10-24 05:33:17 +02004432
4433 my @list = git_get_projects_list($project);
4434 if (!@list) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004435 die_error(404, "No forks found");
Jakub Narebski717b8312006-07-31 21:22:15 +02004436 }
Petr Baudise30496d2006-10-24 05:33:17 +02004437
4438 git_header_html();
4439 git_print_page_nav('','');
4440 git_print_header_div('summary', "$project forks");
4441 git_project_list_body(\@list, $order);
Jakub Narebski717b8312006-07-31 21:22:15 +02004442 git_footer_html();
4443}
4444
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02004445sub git_project_index {
Petr Baudise30496d2006-10-24 05:33:17 +02004446 my @projects = git_get_projects_list($project);
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02004447
4448 print $cgi->header(
4449 -type => 'text/plain',
4450 -charset => 'utf-8',
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02004451 -content_disposition => 'inline; filename="index.aux"');
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02004452
4453 foreach my $pr (@projects) {
4454 if (!exists $pr->{'owner'}) {
Miklos Vajna76e4f5d2007-07-04 00:11:23 +02004455 $pr->{'owner'} = git_get_project_owner("$pr->{'path'}");
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02004456 }
4457
4458 my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'});
4459 # quote as in CGI::Util::encode, but keep the slash, and use '+' for ' '
4460 $path =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
4461 $owner =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
4462 $path =~ s/ /\+/g;
4463 $owner =~ s/ /\+/g;
4464
4465 print "$path $owner\n";
4466 }
4467}
4468
Kay Sieversede5e102005-08-07 20:23:12 +02004469sub git_summary {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004470 my $descr = git_get_project_description($project) || "none";
Robert Fitzsimonsa979d122006-12-22 19:38:15 +00004471 my %co = parse_commit("HEAD");
Jakub Narebski785cdea2007-05-13 12:39:22 +02004472 my %cd = %co ? parse_date($co{'committer_epoch'}, $co{'committer_tz'}) : ();
Robert Fitzsimonsa979d122006-12-22 19:38:15 +00004473 my $head = $co{'id'};
Kay Sieversede5e102005-08-07 20:23:12 +02004474
Jakub Narebski1e0cf032006-08-14 02:10:06 +02004475 my $owner = git_get_project_owner($project);
Kay Sieversede5e102005-08-07 20:23:12 +02004476
Jakub Narebskicd146402006-11-02 20:23:11 +01004477 my $refs = git_get_references();
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00004478 # These get_*_list functions return one more to allow us to see if
4479 # there are more ...
4480 my @taglist = git_get_tags_list(16);
4481 my @headlist = git_get_heads_list(16);
Petr Baudise30496d2006-10-24 05:33:17 +02004482 my @forklist;
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004483 my $check_forks = gitweb_check_feature('forks');
Junio C Hamano5dd5ed02006-11-07 22:00:45 -08004484
4485 if ($check_forks) {
Petr Baudise30496d2006-10-24 05:33:17 +02004486 @forklist = git_get_projects_list($project);
4487 }
Jakub Narebski120ddde2006-09-19 14:33:22 +02004488
Kay Sieversede5e102005-08-07 20:23:12 +02004489 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02004490 git_print_page_nav('summary','', $head);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004491
Kay Sievers19806692005-08-07 20:26:27 +02004492 print "<div class=\"title\">&nbsp;</div>\n";
Jakub Narebski591ebf62007-11-19 14:16:11 +01004493 print "<table class=\"projects_list\">\n" .
Petr Baudisa4761422008-10-02 16:25:05 +02004494 "<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
4495 "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
Jakub Narebski785cdea2007-05-13 12:39:22 +02004496 if (defined $cd{'rfc2822'}) {
Petr Baudisa4761422008-10-02 16:25:05 +02004497 print "<tr id=\"metadata_lchange\"><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n";
Jakub Narebski785cdea2007-05-13 12:39:22 +02004498 }
4499
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02004500 # use per project git URL list in $projectroot/$project/cloneurl
4501 # or make project git URL from git base URL and project name
Jakub Narebski19a87212006-08-15 23:03:17 +02004502 my $url_tag = "URL";
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02004503 my @url_list = git_get_project_url_list($project);
4504 @url_list = map { "$_/$project" } @git_base_url_list unless @url_list;
4505 foreach my $git_url (@url_list) {
4506 next unless $git_url;
Petr Baudisa4761422008-10-02 16:25:05 +02004507 print "<tr class=\"metadata_url\"><td>$url_tag</td><td>$git_url</td></tr>\n";
Jakub Narebski19a87212006-08-15 23:03:17 +02004508 $url_tag = "";
4509 }
Petr Baudisaed93de2008-10-02 17:13:02 +02004510
4511 # Tag cloud
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004512 my $show_ctags = gitweb_check_feature('ctags');
Petr Baudisaed93de2008-10-02 17:13:02 +02004513 if ($show_ctags) {
4514 my $ctags = git_get_project_ctags($project);
4515 my $cloud = git_populate_project_tagcloud($ctags);
4516 print "<tr id=\"metadata_ctags\"><td>Content tags:<br />";
4517 print "</td>\n<td>" unless %$ctags;
4518 print "<form action=\"$show_ctags\" method=\"post\"><input type=\"hidden\" name=\"p\" value=\"$project\" />Add: <input type=\"text\" name=\"t\" size=\"8\" /></form>";
4519 print "</td>\n<td>" if %$ctags;
4520 print git_show_project_tagcloud($cloud, 48);
4521 print "</td></tr>";
4522 }
4523
Jakub Narebski19a87212006-08-15 23:03:17 +02004524 print "</table>\n";
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004525
Matt McCutchen7e1100e2009-02-07 19:00:09 -05004526 # If XSS prevention is on, we don't include README.html.
4527 # TODO: Allow a readme in some safe format.
4528 if (!$prevent_xss && -s "$projectroot/$project/README.html") {
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01004529 print "<div class=\"title\">readme</div>\n" .
4530 "<div class=\"readme\">\n";
4531 insert_file("$projectroot/$project/README.html");
4532 print "\n</div>\n"; # class="readme"
Petr Baudis447ef092006-10-24 05:23:46 +02004533 }
4534
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00004535 # we need to request one more than 16 (0..15) to check if
4536 # those 16 are all
Jakub Narebski785cdea2007-05-13 12:39:22 +02004537 my @commitlist = $head ? parse_commits($head, 17) : ();
4538 if (@commitlist) {
4539 git_print_header_div('shortlog');
4540 git_shortlog_body(\@commitlist, 0, 15, $refs,
4541 $#commitlist <= 15 ? undef :
4542 $cgi->a({-href => href(action=>"shortlog")}, "..."));
4543 }
Kay Sieversede5e102005-08-07 20:23:12 +02004544
Jakub Narebski120ddde2006-09-19 14:33:22 +02004545 if (@taglist) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004546 git_print_header_div('tags');
Jakub Narebski120ddde2006-09-19 14:33:22 +02004547 git_tags_body(\@taglist, 0, 15,
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00004548 $#taglist <= 15 ? undef :
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004549 $cgi->a({-href => href(action=>"tags")}, "..."));
Kay Sieversede5e102005-08-07 20:23:12 +02004550 }
Kay Sievers0db37972005-08-07 20:24:35 +02004551
Jakub Narebski120ddde2006-09-19 14:33:22 +02004552 if (@headlist) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004553 git_print_header_div('heads');
Jakub Narebski120ddde2006-09-19 14:33:22 +02004554 git_heads_body(\@headlist, $head, 0, 15,
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00004555 $#headlist <= 15 ? undef :
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004556 $cgi->a({-href => href(action=>"heads")}, "..."));
Kay Sievers0db37972005-08-07 20:24:35 +02004557 }
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004558
Petr Baudise30496d2006-10-24 05:33:17 +02004559 if (@forklist) {
4560 git_print_header_div('forks');
Mike Ralphsonf04f27e2008-09-25 18:48:48 +02004561 git_project_list_body(\@forklist, 'age', 0, 15,
Robert Fitzsimonsaaca9672006-12-22 19:38:12 +00004562 $#forklist <= 15 ? undef :
Petr Baudise30496d2006-10-24 05:33:17 +02004563 $cgi->a({-href => href(action=>"forks")}, "..."),
Mike Ralphsonf04f27e2008-09-25 18:48:48 +02004564 'no_header');
Petr Baudise30496d2006-10-24 05:33:17 +02004565 }
4566
Kay Sieversede5e102005-08-07 20:23:12 +02004567 git_footer_html();
4568}
4569
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004570sub git_tag {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004571 my $head = git_get_head_hash($project);
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004572 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02004573 git_print_page_nav('','', $head,undef,$head);
4574 my %tag = parse_tag($hash);
Jakub Narebski198a2a82007-05-12 21:16:34 +02004575
4576 if (! %tag) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004577 die_error(404, "Unknown tag object");
Jakub Narebski198a2a82007-05-12 21:16:34 +02004578 }
4579
Jakub Narebski847e01f2006-08-14 02:05:47 +02004580 git_print_header_div('commit', esc_html($tag{'name'}), $hash);
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004581 print "<div class=\"title_text\">\n" .
Jakub Narebski591ebf62007-11-19 14:16:11 +01004582 "<table class=\"object_header\">\n" .
Kay Sieverse4669df2005-08-08 00:02:39 +02004583 "<tr>\n" .
4584 "<td>object</td>\n" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004585 "<td>" . $cgi->a({-class => "list", -href => href(action=>$tag{'type'}, hash=>$tag{'object'})},
4586 $tag{'object'}) . "</td>\n" .
4587 "<td class=\"link\">" . $cgi->a({-href => href(action=>$tag{'type'}, hash=>$tag{'object'})},
4588 $tag{'type'}) . "</td>\n" .
Kay Sieverse4669df2005-08-08 00:02:39 +02004589 "</tr>\n";
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004590 if (defined($tag{'author'})) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004591 my %ad = parse_date($tag{'epoch'}, $tag{'tz'});
Kay Sievers40c13812005-11-19 17:41:29 +01004592 print "<tr><td>author</td><td>" . esc_html($tag{'author'}) . "</td></tr>\n";
Jakub Narebski952c65f2006-08-22 16:52:50 +02004593 print "<tr><td></td><td>" . $ad{'rfc2822'} .
4594 sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) .
4595 "</td></tr>\n";
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004596 }
4597 print "</table>\n\n" .
4598 "</div>\n";
4599 print "<div class=\"page_body\">";
4600 my $comment = $tag{'comment'};
4601 foreach my $line (@$comment) {
Junio C Hamano70022432006-11-24 14:04:01 -08004602 chomp $line;
Jakub Narebski793c4002006-11-24 22:25:50 +01004603 print esc_html($line, -nbsp=>1) . "<br/>\n";
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004604 }
4605 print "</div>\n";
4606 git_footer_html();
4607}
4608
Rafael Garcia-Suarez3a5b9192008-06-06 09:53:32 +02004609sub git_blame {
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004610 # permissions
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004611 gitweb_check_feature('blame')
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004612 or die_error(403, "Blame view not allowed");
Lea Wiemann074afaa2008-06-19 22:03:21 +02004613
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004614 # error checking
Lea Wiemann074afaa2008-06-19 22:03:21 +02004615 die_error(400, "No file name given") unless $file_name;
Jakub Narebski847e01f2006-08-14 02:05:47 +02004616 $hash_base ||= git_get_head_hash($project);
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004617 die_error(404, "Couldn't find base commit") unless $hash_base;
Jakub Narebski847e01f2006-08-14 02:05:47 +02004618 my %co = parse_commit($hash_base)
Lea Wiemann074afaa2008-06-19 22:03:21 +02004619 or die_error(404, "Commit not found");
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004620 my $ftype = "blob";
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004621 if (!defined $hash) {
4622 $hash = git_get_hash_by_path($hash_base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02004623 or die_error(404, "Error looking up file");
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004624 } else {
4625 $ftype = git_get_type($hash);
4626 if ($ftype !~ "blob") {
4627 die_error(400, "Object is not a blob");
4628 }
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004629 }
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004630
4631 # run git-blame --porcelain
4632 open my $fd, "-|", git_cmd(), "blame", '-p',
4633 $hash_base, '--', $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02004634 or die_error(500, "Open git-blame failed");
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004635
4636 # page header
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004637 git_header_html();
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02004638 my $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01004639 $cgi->a({-href => href(action=>"blob", -replay=>1)},
Jakub Narebski952c65f2006-08-22 16:52:50 +02004640 "blob") .
4641 " | " .
Jakub Narebskia3823e52007-11-01 13:06:29 +01004642 $cgi->a({-href => href(action=>"history", -replay=>1)},
4643 "history") .
Petr Baudiscae18622006-09-22 03:19:41 +02004644 " | " .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004645 $cgi->a({-href => href(action=>"blame", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02004646 "HEAD");
Jakub Narebski847e01f2006-08-14 02:05:47 +02004647 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
4648 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
Luben Tuikov59fb1c92006-08-17 10:39:29 -07004649 git_print_page_path($file_name, $ftype, $hash_base);
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004650
4651 # page body
4652 my @rev_color = qw(light2 dark2);
Luben Tuikovcc1bf972006-07-23 13:37:53 -07004653 my $num_colors = scalar(@rev_color);
4654 my $current_color = 0;
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004655 my %metainfo = ();
4656
Jakub Narebski59b9f612006-08-22 23:42:53 +02004657 print <<HTML;
4658<div class="page_body">
4659<table class="blame">
4660<tr><th>Commit</th><th>Line</th><th>Data</th></tr>
4661HTML
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004662 LINE:
4663 while (my $line = <$fd>) {
4664 chomp $line;
4665 # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
4666 # no <lines in group> for subsequent lines in group of lines
Luben Tuikovd15c55a2006-10-11 00:30:05 -07004667 my ($full_rev, $orig_lineno, $lineno, $group_size) =
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004668 ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004669 if (!exists $metainfo{$full_rev}) {
4670 $metainfo{$full_rev} = {};
4671 }
4672 my $meta = $metainfo{$full_rev};
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004673 my $data;
4674 while ($data = <$fd>) {
4675 chomp $data;
4676 last if ($data =~ s/^\t//); # contents of line
4677 if ($data =~ /^(\S+) (.*)$/) {
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004678 $meta->{$1} = $2;
4679 }
4680 }
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004681 my $short_rev = substr($full_rev, 0, 8);
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004682 my $author = $meta->{'author'};
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004683 my %date =
4684 parse_date($meta->{'author-time'}, $meta->{'author-tz'});
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004685 my $date = $date{'iso-tz'};
4686 if ($group_size) {
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004687 $current_color = ($current_color + 1) % $num_colors;
Luben Tuikovcc1bf972006-07-23 13:37:53 -07004688 }
Jakub Narebski4a24bfc2008-12-09 23:46:16 +01004689 print "<tr id=\"l$lineno\" class=\"$rev_color[$current_color]\">\n";
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004690 if ($group_size) {
4691 print "<td class=\"sha1\"";
Luben Tuikov5ad08282006-10-30 12:37:54 -08004692 print " title=\"". esc_html($author) . ", $date\"";
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004693 print " rowspan=\"$group_size\"" if ($group_size > 1);
4694 print ">";
4695 print $cgi->a({-href => href(action=>"commit",
Jakub Narebskia23f0a72007-04-01 22:21:38 +02004696 hash=>$full_rev,
4697 file_name=>$file_name)},
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004698 esc_html($short_rev));
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004699 print "</td>\n";
Luben Tuikov9dc5f8c2006-10-04 00:12:17 -07004700 }
Jakub Narebski39c19ce2008-12-11 01:33:29 +01004701 my $parent_commit;
4702 if (!exists $meta->{'parent'}) {
4703 open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^")
4704 or die_error(500, "Open git-rev-parse failed");
4705 $parent_commit = <$dd>;
4706 close $dd;
4707 chomp($parent_commit);
4708 $meta->{'parent'} = $parent_commit;
4709 } else {
4710 $parent_commit = $meta->{'parent'};
4711 }
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004712 my $blamed = href(action => 'blame',
Jakub Narebskia23f0a72007-04-01 22:21:38 +02004713 file_name => $meta->{'filename'},
4714 hash_base => $parent_commit);
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004715 print "<td class=\"linenr\">";
4716 print $cgi->a({ -href => "$blamed#l$orig_lineno",
Jakub Narebskia23f0a72007-04-01 22:21:38 +02004717 -class => "linenr" },
4718 esc_html($lineno));
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004719 print "</td>";
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004720 print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
4721 print "</tr>\n";
4722 }
4723 print "</table>\n";
4724 print "</div>";
Jakub Narebski952c65f2006-08-22 16:52:50 +02004725 close $fd
4726 or print "Reading blob failed\n";
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004727
4728 # page footer
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004729 git_footer_html();
4730}
4731
Kay Sieversede5e102005-08-07 20:23:12 +02004732sub git_tags {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004733 my $head = git_get_head_hash($project);
Kay Sieversede5e102005-08-07 20:23:12 +02004734 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02004735 git_print_page_nav('','', $head,undef,$head);
4736 git_print_header_div('summary', $project);
Jakub Narebski27fb8c42006-07-30 20:32:01 +02004737
Jakub Narebskicd146402006-11-02 20:23:11 +01004738 my @tagslist = git_get_tags_list();
4739 if (@tagslist) {
4740 git_tags_body(\@tagslist);
Kay Sieversede5e102005-08-07 20:23:12 +02004741 }
Kay Sieversede5e102005-08-07 20:23:12 +02004742 git_footer_html();
4743}
4744
Kay Sieversd8f1c5c2005-10-04 01:12:47 +02004745sub git_heads {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004746 my $head = git_get_head_hash($project);
Kay Sievers0db37972005-08-07 20:24:35 +02004747 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02004748 git_print_page_nav('','', $head,undef,$head);
4749 git_print_header_div('summary', $project);
Jakub Narebski27fb8c42006-07-30 20:32:01 +02004750
Jakub Narebskicd146402006-11-02 20:23:11 +01004751 my @headslist = git_get_heads_list();
4752 if (@headslist) {
4753 git_heads_body(\@headslist, $head);
Kay Sievers0db37972005-08-07 20:24:35 +02004754 }
Kay Sievers0db37972005-08-07 20:24:35 +02004755 git_footer_html();
4756}
4757
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004758sub git_blob_plain {
Jakub Narebski7f718e82008-06-03 16:47:10 +02004759 my $type = shift;
Jakub Narebskif2e73302006-08-26 19:14:25 +02004760 my $expires;
Jakub Narebskif2e73302006-08-26 19:14:25 +02004761
Luben Tuikovcff07712006-07-23 13:28:55 -07004762 if (!defined $hash) {
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004763 if (defined $file_name) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004764 my $base = $hash_base || git_get_head_hash($project);
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004765 $hash = git_get_hash_by_path($base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02004766 or die_error(404, "Cannot find file");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004767 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004768 die_error(400, "No file name defined");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004769 }
Martin Waitz800764c2006-09-16 23:09:02 +02004770 } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
4771 # blobs defined by non-textual hash id's can be cached
4772 $expires = "+1d";
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004773 }
Martin Waitz800764c2006-09-16 23:09:02 +02004774
Dennis Stosberg25691fb2006-08-28 17:49:58 +02004775 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
Lea Wiemann074afaa2008-06-19 22:03:21 +02004776 or die_error(500, "Open git-cat-file blob '$hash' failed");
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004777
Jakub Narebski7f718e82008-06-03 16:47:10 +02004778 # content-type (can include charset)
4779 $type = blob_contenttype($fd, $file_name, $type);
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004780
Jakub Narebski7f718e82008-06-03 16:47:10 +02004781 # "save as" filename, even when no $file_name is given
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004782 my $save_as = "$hash";
4783 if (defined $file_name) {
4784 $save_as = $file_name;
4785 } elsif ($type =~ m/^text\//) {
4786 $save_as .= '.txt';
4787 }
4788
Matt McCutchen7e1100e2009-02-07 19:00:09 -05004789 # With XSS prevention on, blobs of all types except a few known safe
4790 # ones are served with "Content-Disposition: attachment" to make sure
4791 # they don't run in our security domain. For certain image types,
4792 # blob view writes an <img> tag referring to blob_plain view, and we
4793 # want to be sure not to break that by serving the image as an
4794 # attachment (though Firefox 3 doesn't seem to care).
4795 my $sandbox = $prevent_xss &&
4796 $type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))$!;
4797
Jakub Narebskif2e73302006-08-26 19:14:25 +02004798 print $cgi->header(
Jakub Narebski7f718e82008-06-03 16:47:10 +02004799 -type => $type,
4800 -expires => $expires,
Matt McCutchen7e1100e2009-02-07 19:00:09 -05004801 -content_disposition =>
4802 ($sandbox ? 'attachment' : 'inline')
4803 . '; filename="' . $save_as . '"');
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004804 undef $/;
4805 binmode STDOUT, ':raw';
4806 print <$fd>;
4807 binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
4808 $/ = "\n";
4809 close $fd;
4810}
4811
Kay Sievers09bd7892005-08-07 20:21:23 +02004812sub git_blob {
Jakub Narebskif2e73302006-08-26 19:14:25 +02004813 my $expires;
Jakub Narebskif2e73302006-08-26 19:14:25 +02004814
Luben Tuikovcff07712006-07-23 13:28:55 -07004815 if (!defined $hash) {
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004816 if (defined $file_name) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004817 my $base = $hash_base || git_get_head_hash($project);
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004818 $hash = git_get_hash_by_path($base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02004819 or die_error(404, "Cannot find file");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004820 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004821 die_error(400, "No file name defined");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004822 }
Martin Waitz800764c2006-09-16 23:09:02 +02004823 } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
4824 # blobs defined by non-textual hash id's can be cached
4825 $expires = "+1d";
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004826 }
Martin Waitz800764c2006-09-16 23:09:02 +02004827
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004828 my $have_blame = gitweb_check_feature('blame');
Dennis Stosberg25691fb2006-08-28 17:49:58 +02004829 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
Lea Wiemann074afaa2008-06-19 22:03:21 +02004830 or die_error(500, "Couldn't cat $file_name, $hash");
Jakub Narebski847e01f2006-08-14 02:05:47 +02004831 my $mimetype = blob_mimetype($fd, $file_name);
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01004832 if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)! && -B $fd) {
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004833 close $fd;
4834 return git_blob_plain($mimetype);
4835 }
Jakub Narebski5a4cf332006-12-04 23:47:22 +01004836 # we can have blame only for text/* mimetype
4837 $have_blame &&= ($mimetype =~ m!^text/!);
4838
Jakub Narebskif2e73302006-08-26 19:14:25 +02004839 git_header_html(undef, $expires);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02004840 my $formats_nav = '';
Jakub Narebski847e01f2006-08-14 02:05:47 +02004841 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
Kay Sievers93129442005-10-17 03:27:54 +02004842 if (defined $file_name) {
Florian Forster5996ca02006-06-12 10:31:57 +02004843 if ($have_blame) {
Jakub Narebski952c65f2006-08-22 16:52:50 +02004844 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01004845 $cgi->a({-href => href(action=>"blame", -replay=>1)},
Jakub Narebski952c65f2006-08-22 16:52:50 +02004846 "blame") .
4847 " | ";
Florian Forster5996ca02006-06-12 10:31:57 +02004848 }
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02004849 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01004850 $cgi->a({-href => href(action=>"history", -replay=>1)},
Petr Baudiscae18622006-09-22 03:19:41 +02004851 "history") .
4852 " | " .
Jakub Narebskia3823e52007-11-01 13:06:29 +01004853 $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
Petr Baudis35329cc2006-09-22 03:19:50 +02004854 "raw") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004855 " | " .
4856 $cgi->a({-href => href(action=>"blob",
4857 hash_base=>"HEAD", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02004858 "HEAD");
Kay Sievers93129442005-10-17 03:27:54 +02004859 } else {
Jakub Narebski952c65f2006-08-22 16:52:50 +02004860 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01004861 $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
4862 "raw");
Kay Sievers93129442005-10-17 03:27:54 +02004863 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02004864 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
4865 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
Kay Sievers09bd7892005-08-07 20:21:23 +02004866 } else {
4867 print "<div class=\"page_nav\">\n" .
4868 "<br/><br/></div>\n" .
4869 "<div class=\"title\">$hash</div>\n";
4870 }
Luben Tuikov59fb1c92006-08-17 10:39:29 -07004871 git_print_page_path($file_name, "blob", $hash_base);
Kay Sieversc07ad4b2005-08-07 20:22:44 +02004872 print "<div class=\"page_body\">\n";
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01004873 if ($mimetype =~ m!^image/!) {
Jakub Narebski5a4cf332006-12-04 23:47:22 +01004874 print qq!<img type="$mimetype"!;
4875 if ($file_name) {
4876 print qq! alt="$file_name" title="$file_name"!;
4877 }
4878 print qq! src="! .
4879 href(action=>"blob_plain", hash=>$hash,
4880 hash_base=>$hash_base, file_name=>$file_name) .
4881 qq!" />\n!;
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01004882 } else {
4883 my $nr;
4884 while (my $line = <$fd>) {
4885 chomp $line;
4886 $nr++;
4887 $line = untabify($line);
4888 printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
4889 $nr, $nr, $nr, esc_html($line, -nbsp=>1);
4890 }
Kay Sievers161332a2005-08-07 19:49:46 +02004891 }
Jakub Narebski952c65f2006-08-22 16:52:50 +02004892 close $fd
4893 or print "Reading blob failed.\n";
Kay Sieversfbb592a2005-08-07 20:12:11 +02004894 print "</div>";
Kay Sievers12a88f22005-08-07 20:02:47 +02004895 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02004896}
4897
4898sub git_tree {
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07004899 if (!defined $hash_base) {
4900 $hash_base = "HEAD";
4901 }
Kay Sieversb87d78d2005-08-07 20:21:04 +02004902 if (!defined $hash) {
Kay Sievers09bd7892005-08-07 20:21:23 +02004903 if (defined $file_name) {
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07004904 $hash = git_get_hash_by_path($hash_base, $file_name, "tree");
4905 } else {
4906 $hash = $hash_base;
Kay Sievers10dba282005-08-07 20:25:27 +02004907 }
Kay Sieverse925f382005-08-07 20:23:35 +02004908 }
Jakub Narebski2d7a3532008-10-02 16:50:04 +02004909 die_error(404, "No such tree") unless defined($hash);
Kay Sievers232ff552005-11-24 16:56:55 +01004910 $/ = "\0";
Dennis Stosberg25691fb2006-08-28 17:49:58 +02004911 open my $fd, "-|", git_cmd(), "ls-tree", '-z', $hash
Lea Wiemann074afaa2008-06-19 22:03:21 +02004912 or die_error(500, "Open git-ls-tree failed");
Jakub Narebski0881d2d2006-07-30 14:58:11 +02004913 my @entries = map { chomp; $_ } <$fd>;
Lea Wiemann074afaa2008-06-19 22:03:21 +02004914 close $fd or die_error(404, "Reading tree failed");
Kay Sievers232ff552005-11-24 16:56:55 +01004915 $/ = "\n";
Kay Sieversd63577d2005-08-07 20:18:13 +02004916
Jakub Narebski847e01f2006-08-14 02:05:47 +02004917 my $refs = git_get_references();
4918 my $ref = format_ref_marker($refs, $hash_base);
Kay Sievers12a88f22005-08-07 20:02:47 +02004919 git_header_html();
Jakub Narebski300454f2006-10-21 17:53:09 +02004920 my $basedir = '';
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004921 my $have_blame = gitweb_check_feature('blame');
Jakub Narebski847e01f2006-08-14 02:05:47 +02004922 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
Petr Baudiscae18622006-09-22 03:19:41 +02004923 my @views_nav = ();
4924 if (defined $file_name) {
4925 push @views_nav,
Jakub Narebskia3823e52007-11-01 13:06:29 +01004926 $cgi->a({-href => href(action=>"history", -replay=>1)},
Petr Baudiscae18622006-09-22 03:19:41 +02004927 "history"),
4928 $cgi->a({-href => href(action=>"tree",
4929 hash_base=>"HEAD", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02004930 "HEAD"),
Petr Baudiscae18622006-09-22 03:19:41 +02004931 }
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004932 my $snapshot_links = format_snapshot_links($hash);
4933 if (defined $snapshot_links) {
Petr Baudiscae18622006-09-22 03:19:41 +02004934 # FIXME: Should be available when we have no hash base as well.
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004935 push @views_nav, $snapshot_links;
Petr Baudiscae18622006-09-22 03:19:41 +02004936 }
4937 git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav));
Jakub Narebski847e01f2006-08-14 02:05:47 +02004938 git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
Kay Sieversd63577d2005-08-07 20:18:13 +02004939 } else {
Jakub Narebskifa702002006-08-31 00:35:07 +02004940 undef $hash_base;
Kay Sieversd63577d2005-08-07 20:18:13 +02004941 print "<div class=\"page_nav\">\n";
4942 print "<br/><br/></div>\n";
4943 print "<div class=\"title\">$hash</div>\n";
4944 }
Kay Sievers09bd7892005-08-07 20:21:23 +02004945 if (defined $file_name) {
Jakub Narebski300454f2006-10-21 17:53:09 +02004946 $basedir = $file_name;
4947 if ($basedir ne '' && substr($basedir, -1) ne '/') {
4948 $basedir .= '/';
4949 }
Jakub Narebski2d7a3532008-10-02 16:50:04 +02004950 git_print_page_path($file_name, 'tree', $hash_base);
Kay Sievers09bd7892005-08-07 20:21:23 +02004951 }
Kay Sieversfbb592a2005-08-07 20:12:11 +02004952 print "<div class=\"page_body\">\n";
Jakub Narebski591ebf62007-11-19 14:16:11 +01004953 print "<table class=\"tree\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004954 my $alternate = 1;
Jakub Narebskib6b7fc72006-10-21 17:54:44 +02004955 # '..' (top directory) link if possible
4956 if (defined $hash_base &&
4957 defined $file_name && $file_name =~ m![^/]+$!) {
4958 if ($alternate) {
4959 print "<tr class=\"dark\">\n";
4960 } else {
4961 print "<tr class=\"light\">\n";
4962 }
4963 $alternate ^= 1;
4964
4965 my $up = $file_name;
4966 $up =~ s!/?[^/]+$!!;
4967 undef $up unless $up;
4968 # based on git_print_tree_entry
4969 print '<td class="mode">' . mode_str('040000') . "</td>\n";
4970 print '<td class="list">';
4971 print $cgi->a({-href => href(action=>"tree", hash_base=>$hash_base,
4972 file_name=>$up)},
4973 "..");
4974 print "</td>\n";
4975 print "<td class=\"link\"></td>\n";
4976
4977 print "</tr>\n";
4978 }
Kay Sievers161332a2005-08-07 19:49:46 +02004979 foreach my $line (@entries) {
Jakub Narebskicb849b42006-08-31 00:32:15 +02004980 my %t = parse_ls_tree_line($line, -z => 1);
4981
Kay Sieversbddec012005-08-07 20:25:42 +02004982 if ($alternate) {
Kay Sieversc994d622005-08-07 20:27:18 +02004983 print "<tr class=\"dark\">\n";
Kay Sieversbddec012005-08-07 20:25:42 +02004984 } else {
Kay Sieversc994d622005-08-07 20:27:18 +02004985 print "<tr class=\"light\">\n";
Kay Sieversbddec012005-08-07 20:25:42 +02004986 }
4987 $alternate ^= 1;
Jakub Narebskicb849b42006-08-31 00:32:15 +02004988
Jakub Narebski300454f2006-10-21 17:53:09 +02004989 git_print_tree_entry(\%t, $basedir, $hash_base, $have_blame);
Jakub Narebskifa702002006-08-31 00:35:07 +02004990
Kay Sievers42f7eb92005-08-07 20:21:46 +02004991 print "</tr>\n";
Kay Sievers161332a2005-08-07 19:49:46 +02004992 }
Kay Sievers42f7eb92005-08-07 20:21:46 +02004993 print "</table>\n" .
4994 "</div>";
Kay Sievers12a88f22005-08-07 20:02:47 +02004995 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02004996}
4997
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304998sub git_snapshot {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02004999 my $format = $input_params{'snapshot_format'};
Giuseppe Bilotta5e166842008-11-02 10:21:37 +01005000 if (!@snapshot_fmts) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005001 die_error(403, "Snapshots not allowed");
Jakub Narebski3473e7d2007-07-25 01:19:58 +02005002 }
5003 # default to first supported snapshot format
Giuseppe Bilotta5e166842008-11-02 10:21:37 +01005004 $format ||= $snapshot_fmts[0];
Jakub Narebski3473e7d2007-07-25 01:19:58 +02005005 if ($format !~ m/^[a-z0-9]+$/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005006 die_error(400, "Invalid snapshot format parameter");
Jakub Narebski3473e7d2007-07-25 01:19:58 +02005007 } elsif (!exists($known_snapshot_formats{$format})) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005008 die_error(400, "Unknown snapshot format");
Giuseppe Bilotta5e166842008-11-02 10:21:37 +01005009 } elsif (!grep($_ eq $format, @snapshot_fmts)) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005010 die_error(403, "Unsupported snapshot format");
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05305011 }
5012
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305013 if (!defined $hash) {
5014 $hash = git_get_head_hash($project);
5015 }
5016
Mark Levedahl072570e2007-05-20 11:46:46 -04005017 my $name = $project;
Matthias Lederhofer9a7d9412007-06-07 11:27:08 +02005018 $name =~ s,([^/])/*\.git$,$1,;
5019 $name = basename($name);
5020 my $filename = to_utf8($name);
Mark Levedahl072570e2007-05-20 11:46:46 -04005021 $name =~ s/\047/\047\\\047\047/g;
Mark Levedahl072570e2007-05-20 11:46:46 -04005022 my $cmd;
Matt McCutchena3c8ab32007-07-22 01:30:27 +02005023 $filename .= "-$hash$known_snapshot_formats{$format}{'suffix'}";
Lea Wiemann516381d2008-06-17 23:46:35 +02005024 $cmd = quote_command(
5025 git_cmd(), 'archive',
5026 "--format=$known_snapshot_formats{$format}{'format'}",
5027 "--prefix=$name/", $hash);
Matt McCutchena3c8ab32007-07-22 01:30:27 +02005028 if (exists $known_snapshot_formats{$format}{'compressor'}) {
Lea Wiemann516381d2008-06-17 23:46:35 +02005029 $cmd .= ' | ' . quote_command(@{$known_snapshot_formats{$format}{'compressor'}});
Mark Levedahl072570e2007-05-20 11:46:46 -04005030 }
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305031
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02005032 print $cgi->header(
Matt McCutchena3c8ab32007-07-22 01:30:27 +02005033 -type => $known_snapshot_formats{$format}{'type'},
Luben Tuikova2a3bf72006-09-28 16:51:43 -07005034 -content_disposition => 'inline; filename="' . "$filename" . '"',
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02005035 -status => '200 OK');
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305036
Mark Levedahl072570e2007-05-20 11:46:46 -04005037 open my $fd, "-|", $cmd
Lea Wiemann074afaa2008-06-19 22:03:21 +02005038 or die_error(500, "Execute git-archive failed");
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305039 binmode STDOUT, ':raw';
5040 print <$fd>;
5041 binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
5042 close $fd;
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305043}
5044
Kay Sievers09bd7892005-08-07 20:21:23 +02005045sub git_log {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005046 my $head = git_get_head_hash($project);
Kay Sievers0db37972005-08-07 20:24:35 +02005047 if (!defined $hash) {
Kay Sievers19806692005-08-07 20:26:27 +02005048 $hash = $head;
Kay Sievers0db37972005-08-07 20:24:35 +02005049 }
Kay Sieversea4a6df2005-08-07 20:26:49 +02005050 if (!defined $page) {
5051 $page = 0;
Kay Sieversb87d78d2005-08-07 20:21:04 +02005052 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02005053 my $refs = git_get_references();
Kay Sieversea4a6df2005-08-07 20:26:49 +02005054
Robert Fitzsimons719dad22006-12-24 14:31:45 +00005055 my @commitlist = parse_commits($hash, 101, (100 * $page));
Kay Sieversea4a6df2005-08-07 20:26:49 +02005056
Lea Wiemann1f684dc2008-05-28 01:25:42 +02005057 my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#commitlist >= 100);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02005058
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01005059 my ($patch_max) = gitweb_get_feature('patches');
5060 if ($patch_max) {
5061 if ($patch_max < 0 || @commitlist <= $patch_max) {
5062 $paging_nav .= " &sdot; " .
5063 $cgi->a({-href => href(action=>"patches", -replay=>1)},
5064 "patches");
5065 }
5066 }
5067
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02005068 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02005069 git_print_page_nav('log','', $hash,undef,undef, $paging_nav);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02005070
Robert Fitzsimons719dad22006-12-24 14:31:45 +00005071 if (!@commitlist) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005072 my %co = parse_commit($hash);
Jakub Narebski27fb8c42006-07-30 20:32:01 +02005073
Jakub Narebski847e01f2006-08-14 02:05:47 +02005074 git_print_header_div('summary', $project);
Kay Sieverse925f382005-08-07 20:23:35 +02005075 print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n";
Kay Sievers034df392005-08-07 20:20:07 +02005076 }
Robert Fitzsimons719dad22006-12-24 14:31:45 +00005077 my $to = ($#commitlist >= 99) ? (99) : ($#commitlist);
5078 for (my $i = 0; $i <= $to; $i++) {
5079 my %co = %{$commitlist[$i]};
Kay Sieversb87d78d2005-08-07 20:21:04 +02005080 next if !%co;
Robert Fitzsimons719dad22006-12-24 14:31:45 +00005081 my $commit = $co{'id'};
5082 my $ref = format_ref_marker($refs, $commit);
Jakub Narebski847e01f2006-08-14 02:05:47 +02005083 my %ad = parse_date($co{'author_epoch'});
5084 git_print_header_div('commit',
Jakub Narebski26298b52006-08-10 12:38:47 +02005085 "<span class=\"age\">$co{'age_string'}</span>" .
5086 esc_html($co{'title'}) . $ref,
5087 $commit);
Kay Sievers034df392005-08-07 20:20:07 +02005088 print "<div class=\"title_text\">\n" .
5089 "<div class=\"log_link\">\n" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02005090 $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02005091 " | " .
5092 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") .
Petr Baudis6ef4cb22006-09-22 03:19:48 +02005093 " | " .
Petr Baudisd7267202006-09-22 16:56:43 -07005094 $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
Kay Sieverseb282402005-08-07 20:21:34 +02005095 "<br/>\n" .
Kay Sievers034df392005-08-07 20:20:07 +02005096 "</div>\n" .
Kay Sievers40c13812005-11-19 17:41:29 +01005097 "<i>" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]</i><br/>\n" .
Jakub Narebskid16d0932006-08-17 11:21:23 +02005098 "</div>\n";
5099
5100 print "<div class=\"log_body\">\n";
Jakub Narebskif2069412006-10-24 13:52:46 +02005101 git_print_log($co{'comment'}, -final_empty_line=> 1);
Kay Sievers09bd7892005-08-07 20:21:23 +02005102 print "</div>\n";
Kay Sievers034df392005-08-07 20:20:07 +02005103 }
Robert Fitzsimons719dad22006-12-24 14:31:45 +00005104 if ($#commitlist >= 100) {
5105 print "<div class=\"page_nav\">\n";
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005106 print $cgi->a({-href => href(-replay=>1, page=>$page+1),
Robert Fitzsimons719dad22006-12-24 14:31:45 +00005107 -accesskey => "n", -title => "Alt-n"}, "next");
5108 print "</div>\n";
5109 }
Kay Sievers034df392005-08-07 20:20:07 +02005110 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02005111}
5112
5113sub git_commit {
Jakub Narebski9954f772006-11-18 23:35:41 +01005114 $hash ||= $hash_base || "HEAD";
Lea Wiemann074afaa2008-06-19 22:03:21 +02005115 my %co = parse_commit($hash)
5116 or die_error(404, "Unknown commit object");
Jakub Narebski847e01f2006-08-14 02:05:47 +02005117 my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
5118 my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
Kay Sievers161332a2005-08-07 19:49:46 +02005119
Jakub Narebskic9d193d2006-12-15 21:57:16 +01005120 my $parent = $co{'parent'};
5121 my $parents = $co{'parents'}; # listref
5122
5123 # we need to prepare $formats_nav before any parameter munging
5124 my $formats_nav;
5125 if (!defined $parent) {
5126 # --root commitdiff
5127 $formats_nav .= '(initial)';
5128 } elsif (@$parents == 1) {
5129 # single parent commit
5130 $formats_nav .=
5131 '(parent: ' .
5132 $cgi->a({-href => href(action=>"commit",
5133 hash=>$parent)},
5134 esc_html(substr($parent, 0, 7))) .
5135 ')';
5136 } else {
5137 # merge commit
5138 $formats_nav .=
5139 '(merge: ' .
5140 join(' ', map {
Jakub Narebskif9308a12007-03-23 21:04:31 +01005141 $cgi->a({-href => href(action=>"commit",
Jakub Narebskic9d193d2006-12-15 21:57:16 +01005142 hash=>$_)},
5143 esc_html(substr($_, 0, 7)));
5144 } @$parents ) .
5145 ')';
5146 }
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01005147 if (gitweb_check_feature('patches')) {
5148 $formats_nav .= " | " .
5149 $cgi->a({-href => href(action=>"patch", -replay=>1)},
5150 "patch");
5151 }
Jakub Narebskic9d193d2006-12-15 21:57:16 +01005152
Kay Sieversd8a20ba2005-08-07 20:28:53 +02005153 if (!defined $parent) {
Jakub Narebskib9182982006-07-30 18:28:34 -07005154 $parent = "--root";
Kay Sievers6191f8e2005-08-07 20:19:56 +02005155 }
Jakub Narebski549ab4a2006-12-15 17:53:45 +01005156 my @difftree;
Jakub Narebski208ecb22007-05-07 01:10:08 +02005157 open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
5158 @diff_opts,
5159 (@$parents <= 1 ? $parent : '-c'),
5160 $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02005161 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski208ecb22007-05-07 01:10:08 +02005162 @difftree = map { chomp; $_ } <$fd>;
Lea Wiemann074afaa2008-06-19 22:03:21 +02005163 close $fd or die_error(404, "Reading git-diff-tree failed");
Kay Sievers11044292005-10-19 03:18:45 +02005164
5165 # non-textual hash id's can be cached
5166 my $expires;
5167 if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
5168 $expires = "+1d";
5169 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02005170 my $refs = git_get_references();
5171 my $ref = format_ref_marker($refs, $co{'id'});
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05305172
Jakub Narebski594e2122006-07-31 02:21:52 +02005173 git_header_html(undef, $expires);
Petr Baudisa1441542006-10-06 18:59:33 +02005174 git_print_page_nav('commit', '',
Jakub Narebski952c65f2006-08-22 16:52:50 +02005175 $hash, $co{'tree'}, $hash,
Jakub Narebskic9d193d2006-12-15 21:57:16 +01005176 $formats_nav);
Luben Tuikov4f7b34c2006-07-23 13:36:32 -07005177
Kay Sieversb87d78d2005-08-07 20:21:04 +02005178 if (defined $co{'parent'}) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005179 git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);
Kay Sieversb87d78d2005-08-07 20:21:04 +02005180 } else {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005181 git_print_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash);
Kay Sieversb87d78d2005-08-07 20:21:04 +02005182 }
Kay Sievers6191f8e2005-08-07 20:19:56 +02005183 print "<div class=\"title_text\">\n" .
Jakub Narebski591ebf62007-11-19 14:16:11 +01005184 "<table class=\"object_header\">\n";
Kay Sievers40c13812005-11-19 17:41:29 +01005185 print "<tr><td>author</td><td>" . esc_html($co{'author'}) . "</td></tr>\n".
Kay Sieversbddec012005-08-07 20:25:42 +02005186 "<tr>" .
5187 "<td></td><td> $ad{'rfc2822'}";
Kay Sievers927dcec2005-08-07 20:18:44 +02005188 if ($ad{'hour_local'} < 6) {
Jakub Narebski952c65f2006-08-22 16:52:50 +02005189 printf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
5190 $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
Kay Sieversb87d78d2005-08-07 20:21:04 +02005191 } else {
Jakub Narebski952c65f2006-08-22 16:52:50 +02005192 printf(" (%02d:%02d %s)",
5193 $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
Kay Sievers927dcec2005-08-07 20:18:44 +02005194 }
Kay Sieversbddec012005-08-07 20:25:42 +02005195 print "</td>" .
5196 "</tr>\n";
Kay Sievers40c13812005-11-19 17:41:29 +01005197 print "<tr><td>committer</td><td>" . esc_html($co{'committer'}) . "</td></tr>\n";
Jakub Narebski952c65f2006-08-22 16:52:50 +02005198 print "<tr><td></td><td> $cd{'rfc2822'}" .
5199 sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) .
5200 "</td></tr>\n";
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00005201 print "<tr><td>commit</td><td class=\"sha1\">$co{'id'}</td></tr>\n";
Kay Sieversbddec012005-08-07 20:25:42 +02005202 print "<tr>" .
5203 "<td>tree</td>" .
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00005204 "<td class=\"sha1\">" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02005205 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash),
5206 class => "list"}, $co{'tree'}) .
Kay Sievers19806692005-08-07 20:26:27 +02005207 "</td>" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02005208 "<td class=\"link\">" .
5209 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)},
5210 "tree");
Matt McCutchena3c8ab32007-07-22 01:30:27 +02005211 my $snapshot_links = format_snapshot_links($hash);
5212 if (defined $snapshot_links) {
5213 print " | " . $snapshot_links;
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305214 }
5215 print "</td>" .
Kay Sieversbddec012005-08-07 20:25:42 +02005216 "</tr>\n";
Jakub Narebski549ab4a2006-12-15 17:53:45 +01005217
Kay Sievers3e029292005-08-07 20:05:15 +02005218 foreach my $par (@$parents) {
Kay Sieversbddec012005-08-07 20:25:42 +02005219 print "<tr>" .
5220 "<td>parent</td>" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02005221 "<td class=\"sha1\">" .
5222 $cgi->a({-href => href(action=>"commit", hash=>$par),
5223 class => "list"}, $par) .
5224 "</td>" .
Kay Sieversbddec012005-08-07 20:25:42 +02005225 "<td class=\"link\">" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02005226 $cgi->a({-href => href(action=>"commit", hash=>$par)}, "commit") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02005227 " | " .
Jakub Narebskif2e60942006-09-03 23:43:03 +02005228 $cgi->a({-href => href(action=>"commitdiff", hash=>$hash, hash_parent=>$par)}, "diff") .
Kay Sieversbddec012005-08-07 20:25:42 +02005229 "</td>" .
5230 "</tr>\n";
Kay Sievers3e029292005-08-07 20:05:15 +02005231 }
Jakub Narebski7a9b4c52006-06-21 09:48:02 +02005232 print "</table>".
Kay Sieversb87d78d2005-08-07 20:21:04 +02005233 "</div>\n";
Jakub Narebskid16d0932006-08-17 11:21:23 +02005234
Kay Sieversfbb592a2005-08-07 20:12:11 +02005235 print "<div class=\"page_body\">\n";
Jakub Narebskid16d0932006-08-17 11:21:23 +02005236 git_print_log($co{'comment'});
Kay Sievers927dcec2005-08-07 20:18:44 +02005237 print "</div>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005238
Jakub Narebski208ecb22007-05-07 01:10:08 +02005239 git_difftree_body(\@difftree, $hash, @$parents);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005240
Kay Sievers12a88f22005-08-07 20:02:47 +02005241 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02005242}
5243
Jakub Narebskica946012006-12-10 13:25:47 +01005244sub git_object {
5245 # object is defined by:
5246 # - hash or hash_base alone
5247 # - hash_base and file_name
5248 my $type;
5249
5250 # - hash or hash_base alone
5251 if ($hash || ($hash_base && !defined $file_name)) {
5252 my $object_id = $hash || $hash_base;
5253
Lea Wiemann516381d2008-06-17 23:46:35 +02005254 open my $fd, "-|", quote_command(
5255 git_cmd(), 'cat-file', '-t', $object_id) . ' 2> /dev/null'
Lea Wiemann074afaa2008-06-19 22:03:21 +02005256 or die_error(404, "Object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01005257 $type = <$fd>;
5258 chomp $type;
5259 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02005260 or die_error(404, "Object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01005261
5262 # - hash_base and file_name
5263 } elsif ($hash_base && defined $file_name) {
5264 $file_name =~ s,/+$,,;
5265
5266 system(git_cmd(), "cat-file", '-e', $hash_base) == 0
Lea Wiemann074afaa2008-06-19 22:03:21 +02005267 or die_error(404, "Base object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01005268
5269 # here errors should not hapen
5270 open my $fd, "-|", git_cmd(), "ls-tree", $hash_base, "--", $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02005271 or die_error(500, "Open git-ls-tree failed");
Jakub Narebskica946012006-12-10 13:25:47 +01005272 my $line = <$fd>;
5273 close $fd;
5274
5275 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
5276 unless ($line && $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005277 die_error(404, "File or directory for given base does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01005278 }
5279 $type = $2;
5280 $hash = $3;
5281 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005282 die_error(400, "Not enough information to find object");
Jakub Narebskica946012006-12-10 13:25:47 +01005283 }
5284
5285 print $cgi->redirect(-uri => href(action=>$type, -full=>1,
5286 hash=>$hash, hash_base=>$hash_base,
5287 file_name=>$file_name),
5288 -status => '302 Found');
5289}
5290
Kay Sievers09bd7892005-08-07 20:21:23 +02005291sub git_blobdiff {
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005292 my $format = shift || 'html';
5293
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005294 my $fd;
5295 my @difftree;
5296 my %diffinfo;
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005297 my $expires;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005298
5299 # preparing $fd and %diffinfo for git_patchset_body
5300 # new style URI
5301 if (defined $hash_base && defined $hash_parent_base) {
5302 if (defined $file_name) {
5303 # read raw output
Jakub Narebski45bd0c82006-10-30 22:29:06 +01005304 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
5305 $hash_parent_base, $hash_base,
Jakub Narebski5ae917a2007-03-30 23:41:26 +02005306 "--", (defined $file_parent ? $file_parent : ()), $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02005307 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005308 @difftree = map { chomp; $_ } <$fd>;
5309 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02005310 or die_error(404, "Reading git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005311 @difftree
Lea Wiemann074afaa2008-06-19 22:03:21 +02005312 or die_error(404, "Blob diff not found");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005313
Jakub Narebski0aea3372006-08-27 23:45:26 +02005314 } elsif (defined $hash &&
5315 $hash =~ /[0-9a-fA-F]{40}/) {
5316 # try to find filename from $hash
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005317
5318 # read filtered raw output
Jakub Narebski45bd0c82006-10-30 22:29:06 +01005319 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
5320 $hash_parent_base, $hash_base, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02005321 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005322 @difftree =
5323 # ':100644 100644 03b21826... 3b93d5e7... M ls-files.c'
5324 # $hash == to_id
5325 grep { /^:[0-7]{6} [0-7]{6} [0-9a-fA-F]{40} $hash/ }
5326 map { chomp; $_ } <$fd>;
5327 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02005328 or die_error(404, "Reading git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005329 @difftree
Lea Wiemann074afaa2008-06-19 22:03:21 +02005330 or die_error(404, "Blob diff not found");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005331
5332 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005333 die_error(400, "Missing one of the blob diff parameters");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005334 }
5335
5336 if (@difftree > 1) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005337 die_error(400, "Ambiguous blob diff specification");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005338 }
5339
5340 %diffinfo = parse_difftree_raw_line($difftree[0]);
Jakub Narebski9d301452007-11-01 12:38:08 +01005341 $file_parent ||= $diffinfo{'from_file'} || $file_name;
5342 $file_name ||= $diffinfo{'to_file'};
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005343
5344 $hash_parent ||= $diffinfo{'from_id'};
5345 $hash ||= $diffinfo{'to_id'};
5346
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005347 # non-textual hash id's can be cached
5348 if ($hash_base =~ m/^[0-9a-fA-F]{40}$/ &&
5349 $hash_parent_base =~ m/^[0-9a-fA-F]{40}$/) {
5350 $expires = '+1d';
5351 }
5352
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005353 # open patch output
Dennis Stosberg25691fb2006-08-28 17:49:58 +02005354 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebski957d6ea2007-04-05 13:45:41 +02005355 '-p', ($format eq 'html' ? "--full-index" : ()),
5356 $hash_parent_base, $hash_base,
Jakub Narebski5ae917a2007-03-30 23:41:26 +02005357 "--", (defined $file_parent ? $file_parent : ()), $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02005358 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005359 }
5360
Junio C Hamanob54dc9f2008-12-16 19:42:02 -08005361 # old/legacy style URI -- not generated anymore since 1.4.3.
5362 if (!%diffinfo) {
5363 die_error('404 Not Found', "Missing one of the blob diff parameters")
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005364 }
5365
5366 # header
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005367 if ($format eq 'html') {
5368 my $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01005369 $cgi->a({-href => href(action=>"blobdiff_plain", -replay=>1)},
Petr Baudis35329cc2006-09-22 03:19:50 +02005370 "raw");
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005371 git_header_html(undef, $expires);
5372 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
5373 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
5374 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
5375 } else {
5376 print "<div class=\"page_nav\"><br/>$formats_nav<br/></div>\n";
5377 print "<div class=\"title\">$hash vs $hash_parent</div>\n";
5378 }
5379 if (defined $file_name) {
5380 git_print_page_path($file_name, "blob", $hash_base);
5381 } else {
5382 print "<div class=\"page_path\"></div>\n";
5383 }
5384
5385 } elsif ($format eq 'plain') {
5386 print $cgi->header(
5387 -type => 'text/plain',
5388 -charset => 'utf-8',
5389 -expires => $expires,
Luben Tuikova2a3bf72006-09-28 16:51:43 -07005390 -content_disposition => 'inline; filename="' . "$file_name" . '.patch"');
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005391
5392 print "X-Git-Url: " . $cgi->self_url() . "\n\n";
5393
Kay Sievers09bd7892005-08-07 20:21:23 +02005394 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005395 die_error(400, "Unknown blobdiff format");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005396 }
5397
5398 # patch
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005399 if ($format eq 'html') {
5400 print "<div class=\"page_body\">\n";
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005401
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005402 git_patchset_body($fd, [ \%diffinfo ], $hash_base, $hash_parent_base);
5403 close $fd;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005404
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005405 print "</div>\n"; # class="page_body"
5406 git_footer_html();
5407
5408 } else {
5409 while (my $line = <$fd>) {
Jakub Narebski403d0902006-11-08 11:48:56 +01005410 $line =~ s!a/($hash|$hash_parent)!'a/'.esc_path($diffinfo{'from_file'})!eg;
5411 $line =~ s!b/($hash|$hash_parent)!'b/'.esc_path($diffinfo{'to_file'})!eg;
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005412
5413 print $line;
5414
5415 last if $line =~ m!^\+\+\+!;
5416 }
5417 local $/ = undef;
5418 print <$fd>;
5419 close $fd;
5420 }
Kay Sievers09bd7892005-08-07 20:21:23 +02005421}
5422
Kay Sievers19806692005-08-07 20:26:27 +02005423sub git_blobdiff_plain {
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005424 git_blobdiff('plain');
Kay Sievers19806692005-08-07 20:26:27 +02005425}
5426
Kay Sievers09bd7892005-08-07 20:21:23 +02005427sub git_commitdiff {
Giuseppe Bilotta20209852008-12-18 08:13:17 +01005428 my %params = @_;
5429 my $format = $params{-format} || 'html';
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01005430
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01005431 my ($patch_max) = gitweb_get_feature('patches');
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01005432 if ($format eq 'patch') {
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01005433 die_error(403, "Patch view not allowed") unless $patch_max;
5434 }
5435
Jakub Narebski9954f772006-11-18 23:35:41 +01005436 $hash ||= $hash_base || "HEAD";
Lea Wiemann074afaa2008-06-19 22:03:21 +02005437 my %co = parse_commit($hash)
5438 or die_error(404, "Unknown commit object");
Jakub Narebski151602d2006-10-23 00:37:56 +02005439
Jakub Narebskicd030c32007-06-08 13:33:28 +02005440 # choose format for commitdiff for merge
5441 if (! defined $hash_parent && @{$co{'parents'}} > 1) {
5442 $hash_parent = '--cc';
5443 }
5444 # we need to prepare $formats_nav before almost any parameter munging
Jakub Narebski151602d2006-10-23 00:37:56 +02005445 my $formats_nav;
5446 if ($format eq 'html') {
5447 $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01005448 $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
Jakub Narebski151602d2006-10-23 00:37:56 +02005449 "raw");
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01005450 if ($patch_max) {
5451 $formats_nav .= " | " .
5452 $cgi->a({-href => href(action=>"patch", -replay=>1)},
5453 "patch");
5454 }
Jakub Narebski151602d2006-10-23 00:37:56 +02005455
Jakub Narebskicd030c32007-06-08 13:33:28 +02005456 if (defined $hash_parent &&
5457 $hash_parent ne '-c' && $hash_parent ne '--cc') {
Jakub Narebski151602d2006-10-23 00:37:56 +02005458 # commitdiff with two commits given
5459 my $hash_parent_short = $hash_parent;
5460 if ($hash_parent =~ m/^[0-9a-fA-F]{40}$/) {
5461 $hash_parent_short = substr($hash_parent, 0, 7);
5462 }
5463 $formats_nav .=
Jakub Narebskiada3e1f2007-06-08 13:26:31 +02005464 ' (from';
5465 for (my $i = 0; $i < @{$co{'parents'}}; $i++) {
5466 if ($co{'parents'}[$i] eq $hash_parent) {
5467 $formats_nav .= ' parent ' . ($i+1);
5468 last;
5469 }
5470 }
5471 $formats_nav .= ': ' .
Jakub Narebski151602d2006-10-23 00:37:56 +02005472 $cgi->a({-href => href(action=>"commitdiff",
5473 hash=>$hash_parent)},
5474 esc_html($hash_parent_short)) .
5475 ')';
5476 } elsif (!$co{'parent'}) {
5477 # --root commitdiff
5478 $formats_nav .= ' (initial)';
5479 } elsif (scalar @{$co{'parents'}} == 1) {
5480 # single parent commit
5481 $formats_nav .=
5482 ' (parent: ' .
5483 $cgi->a({-href => href(action=>"commitdiff",
5484 hash=>$co{'parent'})},
5485 esc_html(substr($co{'parent'}, 0, 7))) .
5486 ')';
5487 } else {
5488 # merge commit
Jakub Narebskicd030c32007-06-08 13:33:28 +02005489 if ($hash_parent eq '--cc') {
5490 $formats_nav .= ' | ' .
5491 $cgi->a({-href => href(action=>"commitdiff",
5492 hash=>$hash, hash_parent=>'-c')},
5493 'combined');
5494 } else { # $hash_parent eq '-c'
5495 $formats_nav .= ' | ' .
5496 $cgi->a({-href => href(action=>"commitdiff",
5497 hash=>$hash, hash_parent=>'--cc')},
5498 'compact');
5499 }
Jakub Narebski151602d2006-10-23 00:37:56 +02005500 $formats_nav .=
5501 ' (merge: ' .
5502 join(' ', map {
5503 $cgi->a({-href => href(action=>"commitdiff",
5504 hash=>$_)},
5505 esc_html(substr($_, 0, 7)));
5506 } @{$co{'parents'}} ) .
5507 ')';
5508 }
5509 }
5510
Jakub Narebskifb1dde42007-05-07 01:10:07 +02005511 my $hash_parent_param = $hash_parent;
Jakub Narebskicd030c32007-06-08 13:33:28 +02005512 if (!defined $hash_parent_param) {
5513 # --cc for multiple parents, --root for parentless
Jakub Narebskifb1dde42007-05-07 01:10:07 +02005514 $hash_parent_param =
Jakub Narebskicd030c32007-06-08 13:33:28 +02005515 @{$co{'parents'}} > 1 ? '--cc' : $co{'parent'} || '--root';
Kay Sieversbddec012005-08-07 20:25:42 +02005516 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005517
5518 # read commitdiff
5519 my $fd;
5520 my @difftree;
Jakub Narebskieee08902006-08-24 00:15:14 +02005521 if ($format eq 'html') {
Dennis Stosberg25691fb2006-08-28 17:49:58 +02005522 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebski45bd0c82006-10-30 22:29:06 +01005523 "--no-commit-id", "--patch-with-raw", "--full-index",
Jakub Narebskifb1dde42007-05-07 01:10:07 +02005524 $hash_parent_param, $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02005525 or die_error(500, "Open git-diff-tree failed");
Jakub Narebskieee08902006-08-24 00:15:14 +02005526
Jakub Narebski04408c32006-11-18 23:35:38 +01005527 while (my $line = <$fd>) {
5528 chomp $line;
Jakub Narebskieee08902006-08-24 00:15:14 +02005529 # empty line ends raw part of diff-tree output
5530 last unless $line;
Jakub Narebski493e01d2007-05-07 01:10:06 +02005531 push @difftree, scalar parse_difftree_raw_line($line);
Jakub Narebskieee08902006-08-24 00:15:14 +02005532 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005533
Jakub Narebskieee08902006-08-24 00:15:14 +02005534 } elsif ($format eq 'plain') {
Dennis Stosberg25691fb2006-08-28 17:49:58 +02005535 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebskifb1dde42007-05-07 01:10:07 +02005536 '-p', $hash_parent_param, $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02005537 or die_error(500, "Open git-diff-tree failed");
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01005538 } elsif ($format eq 'patch') {
5539 # For commit ranges, we limit the output to the number of
5540 # patches specified in the 'patches' feature.
5541 # For single commits, we limit the output to a single patch,
5542 # diverging from the git-format-patch default.
5543 my @commit_spec = ();
5544 if ($hash_parent) {
5545 if ($patch_max > 0) {
5546 push @commit_spec, "-$patch_max";
5547 }
5548 push @commit_spec, '-n', "$hash_parent..$hash";
5549 } else {
Giuseppe Bilottaa3411f82008-12-18 08:13:18 +01005550 if ($params{-single}) {
5551 push @commit_spec, '-1';
5552 } else {
5553 if ($patch_max > 0) {
5554 push @commit_spec, "-$patch_max";
5555 }
5556 push @commit_spec, "-n";
5557 }
5558 push @commit_spec, '--root', $hash;
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01005559 }
5560 open $fd, "-|", git_cmd(), "format-patch", '--encoding=utf8',
5561 '--stdout', @commit_spec
5562 or die_error(500, "Open git-format-patch failed");
Jakub Narebskieee08902006-08-24 00:15:14 +02005563 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005564 die_error(400, "Unknown commitdiff format");
Jakub Narebskieee08902006-08-24 00:15:14 +02005565 }
Kay Sievers4c02e3c2005-08-07 19:52:52 +02005566
Kay Sievers11044292005-10-19 03:18:45 +02005567 # non-textual hash id's can be cached
5568 my $expires;
5569 if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
5570 $expires = "+1d";
5571 }
Kay Sievers09bd7892005-08-07 20:21:23 +02005572
Jakub Narebskieee08902006-08-24 00:15:14 +02005573 # write commit message
5574 if ($format eq 'html') {
5575 my $refs = git_get_references();
5576 my $ref = format_ref_marker($refs, $co{'id'});
Kay Sievers19806692005-08-07 20:26:27 +02005577
Jakub Narebskieee08902006-08-24 00:15:14 +02005578 git_header_html(undef, $expires);
5579 git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
5580 git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
Jakub Narebski6fd92a22006-08-28 14:48:12 +02005581 git_print_authorship(\%co);
Jakub Narebskieee08902006-08-24 00:15:14 +02005582 print "<div class=\"page_body\">\n";
Jakub Narebski82560982006-10-24 13:55:33 +02005583 if (@{$co{'comment'}} > 1) {
5584 print "<div class=\"log\">\n";
5585 git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1);
5586 print "</div>\n"; # class="log"
5587 }
Kay Sievers1b1cd422005-08-07 20:28:01 +02005588
Jakub Narebskieee08902006-08-24 00:15:14 +02005589 } elsif ($format eq 'plain') {
5590 my $refs = git_get_references("tags");
Jakub Narebskiedf735a2006-08-24 19:45:30 +02005591 my $tagname = git_get_rev_name_tags($hash);
Jakub Narebskieee08902006-08-24 00:15:14 +02005592 my $filename = basename($project) . "-$hash.patch";
5593
5594 print $cgi->header(
5595 -type => 'text/plain',
5596 -charset => 'utf-8',
5597 -expires => $expires,
Luben Tuikova2a3bf72006-09-28 16:51:43 -07005598 -content_disposition => 'inline; filename="' . "$filename" . '"');
Jakub Narebskieee08902006-08-24 00:15:14 +02005599 my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
Yasushi SHOJI77202242008-01-29 21:16:02 +09005600 print "From: " . to_utf8($co{'author'}) . "\n";
5601 print "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n";
5602 print "Subject: " . to_utf8($co{'title'}) . "\n";
5603
Jakub Narebskiedf735a2006-08-24 19:45:30 +02005604 print "X-Git-Tag: $tagname\n" if $tagname;
Jakub Narebskieee08902006-08-24 00:15:14 +02005605 print "X-Git-Url: " . $cgi->self_url() . "\n\n";
Jakub Narebskiedf735a2006-08-24 19:45:30 +02005606
Jakub Narebskieee08902006-08-24 00:15:14 +02005607 foreach my $line (@{$co{'comment'}}) {
Yasushi SHOJI77202242008-01-29 21:16:02 +09005608 print to_utf8($line) . "\n";
Kay Sievers19806692005-08-07 20:26:27 +02005609 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005610 print "---\n\n";
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01005611 } elsif ($format eq 'patch') {
5612 my $filename = basename($project) . "-$hash.patch";
5613
5614 print $cgi->header(
5615 -type => 'text/plain',
5616 -charset => 'utf-8',
5617 -expires => $expires,
5618 -content_disposition => 'inline; filename="' . "$filename" . '"');
Kay Sievers19806692005-08-07 20:26:27 +02005619 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005620
5621 # write patch
5622 if ($format eq 'html') {
Jakub Narebskicd030c32007-06-08 13:33:28 +02005623 my $use_parents = !defined $hash_parent ||
5624 $hash_parent eq '-c' || $hash_parent eq '--cc';
5625 git_difftree_body(\@difftree, $hash,
5626 $use_parents ? @{$co{'parents'}} : $hash_parent);
Jakub Narebskib4657e72006-08-28 14:48:14 +02005627 print "<br/>\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02005628
Jakub Narebskicd030c32007-06-08 13:33:28 +02005629 git_patchset_body($fd, \@difftree, $hash,
5630 $use_parents ? @{$co{'parents'}} : $hash_parent);
Jakub Narebski157e43b2006-08-24 19:34:36 +02005631 close $fd;
Jakub Narebskieee08902006-08-24 00:15:14 +02005632 print "</div>\n"; # class="page_body"
5633 git_footer_html();
5634
5635 } elsif ($format eq 'plain') {
5636 local $/ = undef;
5637 print <$fd>;
5638 close $fd
5639 or print "Reading git-diff-tree failed\n";
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01005640 } elsif ($format eq 'patch') {
5641 local $/ = undef;
5642 print <$fd>;
5643 close $fd
5644 or print "Reading git-format-patch failed\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02005645 }
5646}
5647
5648sub git_commitdiff_plain {
Giuseppe Bilotta20209852008-12-18 08:13:17 +01005649 git_commitdiff(-format => 'plain');
Kay Sievers19806692005-08-07 20:26:27 +02005650}
5651
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01005652# format-patch-style patches
5653sub git_patch {
Giuseppe Bilottaa3411f82008-12-18 08:13:18 +01005654 git_commitdiff(-format => 'patch', -single=> 1);
5655}
5656
5657sub git_patches {
Giuseppe Bilotta20209852008-12-18 08:13:17 +01005658 git_commitdiff(-format => 'patch');
Kay Sievers09bd7892005-08-07 20:21:23 +02005659}
5660
5661sub git_history {
Luben Tuikovc6e1d9e2006-07-23 13:26:30 -07005662 if (!defined $hash_base) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005663 $hash_base = git_get_head_hash($project);
Kay Sievers09bd7892005-08-07 20:21:23 +02005664 }
Jakub Narebski8be68352006-09-11 00:36:04 +02005665 if (!defined $page) {
5666 $page = 0;
5667 }
Luben Tuikov63433102006-07-23 13:31:15 -07005668 my $ftype;
Lea Wiemann074afaa2008-06-19 22:03:21 +02005669 my %co = parse_commit($hash_base)
5670 or die_error(404, "Unknown commit object");
Jakub Narebski8be68352006-09-11 00:36:04 +02005671
Jakub Narebski847e01f2006-08-14 02:05:47 +02005672 my $refs = git_get_references();
Jakub Narebski8be68352006-09-11 00:36:04 +02005673 my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
5674
Jakub Narebski5634cf22008-04-13 14:12:15 +02005675 my @commitlist = parse_commits($hash_base, 101, (100 * $page),
Lea Wiemann074afaa2008-06-19 22:03:21 +02005676 $file_name, "--full-history")
5677 or die_error(404, "No such file or directory on given branch");
Jakub Narebski5634cf22008-04-13 14:12:15 +02005678
Luben Tuikov93d5f062006-07-23 13:30:08 -07005679 if (!defined $hash && defined $file_name) {
Jakub Narebski5634cf22008-04-13 14:12:15 +02005680 # some commits could have deleted file in question,
5681 # and not have it in tree, but one of them has to have it
5682 for (my $i = 0; $i <= @commitlist; $i++) {
5683 $hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name);
5684 last if defined $hash;
5685 }
Luben Tuikov93d5f062006-07-23 13:30:08 -07005686 }
Luben Tuikovcff07712006-07-23 13:28:55 -07005687 if (defined $hash) {
Luben Tuikov63433102006-07-23 13:31:15 -07005688 $ftype = git_get_type($hash);
Luben Tuikovcff07712006-07-23 13:28:55 -07005689 }
Jakub Narebski5634cf22008-04-13 14:12:15 +02005690 if (!defined $ftype) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005691 die_error(500, "Unknown type of object");
Jakub Narebski5634cf22008-04-13 14:12:15 +02005692 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02005693
Jakub Narebski8be68352006-09-11 00:36:04 +02005694 my $paging_nav = '';
5695 if ($page > 0) {
5696 $paging_nav .=
5697 $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
5698 file_name=>$file_name)},
5699 "first");
5700 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005701 $cgi->a({-href => href(-replay=>1, page=>$page-1),
Jakub Narebski8be68352006-09-11 00:36:04 +02005702 -accesskey => "p", -title => "Alt-p"}, "prev");
5703 } else {
5704 $paging_nav .= "first";
5705 $paging_nav .= " &sdot; prev";
5706 }
Jakub Narebski8be68352006-09-11 00:36:04 +02005707 my $next_link = '';
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00005708 if ($#commitlist >= 100) {
Jakub Narebski8be68352006-09-11 00:36:04 +02005709 $next_link =
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005710 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00005711 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005712 $paging_nav .= " &sdot; $next_link";
5713 } else {
5714 $paging_nav .= " &sdot; next";
Jakub Narebski8be68352006-09-11 00:36:04 +02005715 }
Jakub Narebski581860e2006-08-14 02:09:08 +02005716
Jakub Narebski8be68352006-09-11 00:36:04 +02005717 git_header_html();
5718 git_print_page_nav('history','', $hash_base,$co{'tree'},$hash_base, $paging_nav);
5719 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
5720 git_print_page_path($file_name, $ftype, $hash_base);
5721
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00005722 git_history_body(\@commitlist, 0, 99,
Jakub Narebski8be68352006-09-11 00:36:04 +02005723 $refs, $hash_base, $ftype, $next_link);
5724
Kay Sieversd51e9022005-08-07 20:16:07 +02005725 git_footer_html();
Kay Sievers161332a2005-08-07 19:49:46 +02005726}
Kay Sievers19806692005-08-07 20:26:27 +02005727
5728sub git_search {
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08005729 gitweb_check_feature('search') or die_error(403, "Search is disabled");
Kay Sievers19806692005-08-07 20:26:27 +02005730 if (!defined $searchtext) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005731 die_error(400, "Text field is empty");
Kay Sievers19806692005-08-07 20:26:27 +02005732 }
5733 if (!defined $hash) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005734 $hash = git_get_head_hash($project);
Kay Sievers19806692005-08-07 20:26:27 +02005735 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02005736 my %co = parse_commit($hash);
Kay Sievers19806692005-08-07 20:26:27 +02005737 if (!%co) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005738 die_error(404, "Unknown commit object");
Kay Sievers19806692005-08-07 20:26:27 +02005739 }
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005740 if (!defined $page) {
5741 $page = 0;
5742 }
Jakub Narebski04f7a942006-09-11 00:29:27 +02005743
Petr Baudis88ad7292006-10-24 05:15:46 +02005744 $searchtype ||= 'commit';
5745 if ($searchtype eq 'pickaxe') {
Jakub Narebski04f7a942006-09-11 00:29:27 +02005746 # pickaxe may take all resources of your box and run for several minutes
5747 # with every query - so decide by yourself how public you make this feature
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08005748 gitweb_check_feature('pickaxe')
Lea Wiemann074afaa2008-06-19 22:03:21 +02005749 or die_error(403, "Pickaxe is disabled");
Kay Sieversc994d622005-08-07 20:27:18 +02005750 }
Petr Baudise7738552007-05-17 04:31:12 +02005751 if ($searchtype eq 'grep') {
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08005752 gitweb_check_feature('grep')
Lea Wiemann074afaa2008-06-19 22:03:21 +02005753 or die_error(403, "Grep is disabled");
Petr Baudise7738552007-05-17 04:31:12 +02005754 }
Petr Baudis88ad7292006-10-24 05:15:46 +02005755
Kay Sievers19806692005-08-07 20:26:27 +02005756 git_header_html();
Kay Sievers19806692005-08-07 20:26:27 +02005757
Petr Baudis88ad7292006-10-24 05:15:46 +02005758 if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') {
Robert Fitzsimons8e574fb2006-12-23 03:35:14 +00005759 my $greptype;
5760 if ($searchtype eq 'commit') {
5761 $greptype = "--grep=";
5762 } elsif ($searchtype eq 'author') {
5763 $greptype = "--author=";
5764 } elsif ($searchtype eq 'committer') {
5765 $greptype = "--committer=";
5766 }
Jakub Narebski0270cd02008-02-26 13:22:07 +01005767 $greptype .= $searchtext;
5768 my @commitlist = parse_commits($hash, 101, (100 * $page), undef,
Petr Baudis0e559912008-02-26 13:22:08 +01005769 $greptype, '--regexp-ignore-case',
5770 $search_use_regexp ? '--extended-regexp' : '--fixed-strings');
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005771
5772 my $paging_nav = '';
5773 if ($page > 0) {
5774 $paging_nav .=
5775 $cgi->a({-href => href(action=>"search", hash=>$hash,
Jakub Narebski0270cd02008-02-26 13:22:07 +01005776 searchtext=>$searchtext,
5777 searchtype=>$searchtype)},
Jakub Narebskia23f0a72007-04-01 22:21:38 +02005778 "first");
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005779 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005780 $cgi->a({-href => href(-replay=>1, page=>$page-1),
Jakub Narebskia23f0a72007-04-01 22:21:38 +02005781 -accesskey => "p", -title => "Alt-p"}, "prev");
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005782 } else {
5783 $paging_nav .= "first";
5784 $paging_nav .= " &sdot; prev";
5785 }
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005786 my $next_link = '';
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00005787 if ($#commitlist >= 100) {
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005788 $next_link =
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005789 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Jakub Narebskia23f0a72007-04-01 22:21:38 +02005790 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005791 $paging_nav .= " &sdot; $next_link";
5792 } else {
5793 $paging_nav .= " &sdot; next";
5794 }
5795
5796 if ($#commitlist >= 100) {
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005797 }
5798
5799 git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
5800 git_print_header_div('commit', esc_html($co{'title'}), $hash);
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00005801 git_search_grep_body(\@commitlist, 0, 99, $next_link);
Kay Sieversc994d622005-08-07 20:27:18 +02005802 }
5803
Petr Baudis88ad7292006-10-24 05:15:46 +02005804 if ($searchtype eq 'pickaxe') {
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005805 git_print_page_nav('','', $hash,$co{'tree'},$hash);
5806 git_print_header_div('commit', esc_html($co{'title'}), $hash);
5807
Jakub Narebski591ebf62007-11-19 14:16:11 +01005808 print "<table class=\"pickaxe search\">\n";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005809 my $alternate = 1;
Kay Sieversc994d622005-08-07 20:27:18 +02005810 $/ = "\n";
Jakub Narebskic582aba2008-03-05 09:31:55 +01005811 open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts,
5812 '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext",
5813 ($search_use_regexp ? '--pickaxe-regex' : ());
Kay Sieversc994d622005-08-07 20:27:18 +02005814 undef %co;
5815 my @files;
5816 while (my $line = <$fd>) {
Jakub Narebskic582aba2008-03-05 09:31:55 +01005817 chomp $line;
5818 next unless $line;
5819
5820 my %set = parse_difftree_raw_line($line);
5821 if (defined $set{'commit'}) {
5822 # finish previous commit
Kay Sieversc994d622005-08-07 20:27:18 +02005823 if (%co) {
Kay Sieversc994d622005-08-07 20:27:18 +02005824 print "</td>\n" .
5825 "<td class=\"link\">" .
Martin Waitz756d2f02006-08-17 00:28:36 +02005826 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02005827 " | " .
5828 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
Kay Sieversc994d622005-08-07 20:27:18 +02005829 print "</td>\n" .
5830 "</tr>\n";
5831 }
Jakub Narebskic582aba2008-03-05 09:31:55 +01005832
5833 if ($alternate) {
5834 print "<tr class=\"dark\">\n";
5835 } else {
5836 print "<tr class=\"light\">\n";
5837 }
5838 $alternate ^= 1;
5839 %co = parse_commit($set{'commit'});
5840 my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
5841 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
5842 "<td><i>$author</i></td>\n" .
5843 "<td>" .
5844 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
5845 -class => "list subject"},
5846 chop_and_escape_str($co{'title'}, 50) . "<br/>");
5847 } elsif (defined $set{'to_id'}) {
5848 next if ($set{'to_id'} =~ m/^0{40}$/);
5849
5850 print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
5851 hash=>$set{'to_id'}, file_name=>$set{'to_file'}),
5852 -class => "list"},
5853 "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
5854 "<br/>\n";
Kay Sieversc994d622005-08-07 20:27:18 +02005855 }
5856 }
5857 close $fd;
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005858
Jakub Narebskic582aba2008-03-05 09:31:55 +01005859 # finish last commit (warning: repetition!)
5860 if (%co) {
5861 print "</td>\n" .
5862 "<td class=\"link\">" .
5863 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
5864 " | " .
5865 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
5866 print "</td>\n" .
5867 "</tr>\n";
5868 }
5869
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005870 print "</table>\n";
Kay Sievers19806692005-08-07 20:26:27 +02005871 }
Petr Baudise7738552007-05-17 04:31:12 +02005872
5873 if ($searchtype eq 'grep') {
5874 git_print_page_nav('','', $hash,$co{'tree'},$hash);
5875 git_print_header_div('commit', esc_html($co{'title'}), $hash);
5876
Jakub Narebski591ebf62007-11-19 14:16:11 +01005877 print "<table class=\"grep_search\">\n";
Petr Baudise7738552007-05-17 04:31:12 +02005878 my $alternate = 1;
5879 my $matches = 0;
5880 $/ = "\n";
Petr Baudis0e559912008-02-26 13:22:08 +01005881 open my $fd, "-|", git_cmd(), 'grep', '-n',
5882 $search_use_regexp ? ('-E', '-i') : '-F',
5883 $searchtext, $co{'tree'};
Petr Baudise7738552007-05-17 04:31:12 +02005884 my $lastfile = '';
5885 while (my $line = <$fd>) {
5886 chomp $line;
5887 my ($file, $lno, $ltext, $binary);
5888 last if ($matches++ > 1000);
5889 if ($line =~ /^Binary file (.+) matches$/) {
5890 $file = $1;
5891 $binary = 1;
5892 } else {
5893 (undef, $file, $lno, $ltext) = split(/:/, $line, 4);
5894 }
5895 if ($file ne $lastfile) {
5896 $lastfile and print "</td></tr>\n";
5897 if ($alternate++) {
5898 print "<tr class=\"dark\">\n";
5899 } else {
5900 print "<tr class=\"light\">\n";
5901 }
5902 print "<td class=\"list\">".
5903 $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
5904 file_name=>"$file"),
5905 -class => "list"}, esc_path($file));
5906 print "</td><td>\n";
5907 $lastfile = $file;
5908 }
5909 if ($binary) {
5910 print "<div class=\"binary\">Binary file</div>\n";
5911 } else {
5912 $ltext = untabify($ltext);
Petr Baudis0e559912008-02-26 13:22:08 +01005913 if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) {
Petr Baudise7738552007-05-17 04:31:12 +02005914 $ltext = esc_html($1, -nbsp=>1);
5915 $ltext .= '<span class="match">';
5916 $ltext .= esc_html($2, -nbsp=>1);
5917 $ltext .= '</span>';
5918 $ltext .= esc_html($3, -nbsp=>1);
5919 } else {
5920 $ltext = esc_html($ltext, -nbsp=>1);
5921 }
5922 print "<div class=\"pre\">" .
5923 $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
5924 file_name=>"$file").'#l'.$lno,
5925 -class => "linenr"}, sprintf('%4i', $lno))
5926 . ' ' . $ltext . "</div>\n";
5927 }
5928 }
5929 if ($lastfile) {
5930 print "</td></tr>\n";
5931 if ($matches > 1000) {
5932 print "<div class=\"diff nodifferences\">Too many matches, listing trimmed</div>\n";
5933 }
5934 } else {
5935 print "<div class=\"diff nodifferences\">No matches found</div>\n";
5936 }
5937 close $fd;
5938
5939 print "</table>\n";
5940 }
Kay Sievers19806692005-08-07 20:26:27 +02005941 git_footer_html();
5942}
5943
Petr Baudis88ad7292006-10-24 05:15:46 +02005944sub git_search_help {
5945 git_header_html();
5946 git_print_page_nav('','', $hash,$hash,$hash);
5947 print <<EOT;
Petr Baudis0e559912008-02-26 13:22:08 +01005948<p><strong>Pattern</strong> is by default a normal string that is matched precisely (but without
5949regard to case, except in the case of pickaxe). However, when you check the <em>re</em> checkbox,
5950the pattern entered is recognized as the POSIX extended
5951<a href="http://en.wikipedia.org/wiki/Regular_expression">regular expression</a> (also case
5952insensitive).</p>
Petr Baudis88ad7292006-10-24 05:15:46 +02005953<dl>
5954<dt><b>commit</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01005955<dd>The commit messages and authorship information will be scanned for the given pattern.</dd>
Petr Baudise7738552007-05-17 04:31:12 +02005956EOT
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08005957 my $have_grep = gitweb_check_feature('grep');
Petr Baudise7738552007-05-17 04:31:12 +02005958 if ($have_grep) {
5959 print <<EOT;
5960<dt><b>grep</b></dt>
5961<dd>All files in the currently selected tree (HEAD unless you are explicitly browsing
Petr Baudis0e559912008-02-26 13:22:08 +01005962 a different one) are searched for the given pattern. On large trees, this search can take
5963a while and put some strain on the server, so please use it with some consideration. Note that
5964due to git-grep peculiarity, currently if regexp mode is turned off, the matches are
5965case-sensitive.</dd>
Petr Baudise7738552007-05-17 04:31:12 +02005966EOT
5967 }
5968 print <<EOT;
Petr Baudis88ad7292006-10-24 05:15:46 +02005969<dt><b>author</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01005970<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 +02005971<dt><b>committer</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01005972<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 +02005973EOT
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08005974 my $have_pickaxe = gitweb_check_feature('pickaxe');
Petr Baudis88ad7292006-10-24 05:15:46 +02005975 if ($have_pickaxe) {
5976 print <<EOT;
5977<dt><b>pickaxe</b></dt>
5978<dd>All commits that caused the string to appear or disappear from any file (changes that
5979added, removed or "modified" the string) will be listed. This search can take a while and
Petr Baudis0e559912008-02-26 13:22:08 +01005980takes a lot of strain on the server, so please use it wisely. Note that since you may be
5981interested even in changes just changing the case as well, this search is case sensitive.</dd>
Petr Baudis88ad7292006-10-24 05:15:46 +02005982EOT
5983 }
5984 print "</dl>\n";
5985 git_footer_html();
5986}
5987
Kay Sievers19806692005-08-07 20:26:27 +02005988sub git_shortlog {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005989 my $head = git_get_head_hash($project);
Kay Sievers19806692005-08-07 20:26:27 +02005990 if (!defined $hash) {
5991 $hash = $head;
5992 }
Kay Sieversea4a6df2005-08-07 20:26:49 +02005993 if (!defined $page) {
5994 $page = 0;
5995 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02005996 my $refs = git_get_references();
Kay Sieversea4a6df2005-08-07 20:26:49 +02005997
Giuseppe Bilottaec3e97b2008-08-08 16:12:11 +02005998 my $commit_hash = $hash;
5999 if (defined $hash_parent) {
6000 $commit_hash = "$hash_parent..$hash";
6001 }
6002 my @commitlist = parse_commits($commit_hash, 101, (100 * $page));
Kay Sieversea4a6df2005-08-07 20:26:49 +02006003
Lea Wiemann1f684dc2008-05-28 01:25:42 +02006004 my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#commitlist >= 100);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006005 my $next_link = '';
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00006006 if ($#commitlist >= 100) {
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006007 $next_link =
Jakub Narebski7afd77b2007-11-01 13:06:28 +01006008 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00006009 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006010 }
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01006011 my $patch_max = gitweb_check_feature('patches');
6012 if ($patch_max) {
6013 if ($patch_max < 0 || @commitlist <= $patch_max) {
6014 $paging_nav .= " &sdot; " .
6015 $cgi->a({-href => href(action=>"patches", -replay=>1)},
6016 "patches");
6017 }
6018 }
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006019
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02006020 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02006021 git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
6022 git_print_header_div('summary', $project);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02006023
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00006024 git_shortlog_body(\@commitlist, 0, 99, $refs, $next_link);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02006025
Kay Sievers19806692005-08-07 20:26:27 +02006026 git_footer_html();
6027}
Jakub Narebski717b8312006-07-31 21:22:15 +02006028
6029## ......................................................................
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006030## feeds (RSS, Atom; OPML)
Jakub Narebski717b8312006-07-31 21:22:15 +02006031
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006032sub git_feed {
6033 my $format = shift || 'atom';
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006034 my $have_blame = gitweb_check_feature('blame');
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006035
6036 # Atom: http://www.atomenabled.org/developers/syndication/
6037 # RSS: http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
6038 if ($format ne 'rss' && $format ne 'atom') {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006039 die_error(400, "Unknown web feed format");
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006040 }
6041
6042 # log/feed of current (HEAD) branch, log of given branch, history of file/directory
6043 my $head = $hash || 'HEAD';
Jakub Narebski311e5522008-02-26 13:22:06 +01006044 my @commitlist = parse_commits($head, 150, 0, $file_name);
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006045
6046 my %latest_commit;
6047 my %latest_date;
6048 my $content_type = "application/$format+xml";
6049 if (defined $cgi->http('HTTP_ACCEPT') &&
6050 $cgi->Accept('text/xml') > $cgi->Accept($content_type)) {
6051 # browser (feed reader) prefers text/xml
6052 $content_type = 'text/xml';
6053 }
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00006054 if (defined($commitlist[0])) {
6055 %latest_commit = %{$commitlist[0]};
Giuseppe Bilottacd956c72009-01-26 12:50:16 +01006056 my $latest_epoch = $latest_commit{'committer_epoch'};
6057 %latest_date = parse_date($latest_epoch);
6058 my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
6059 if (defined $if_modified) {
6060 my $since;
6061 if (eval { require HTTP::Date; 1; }) {
6062 $since = HTTP::Date::str2time($if_modified);
6063 } elsif (eval { require Time::ParseDate; 1; }) {
6064 $since = Time::ParseDate::parsedate($if_modified, GMT => 1);
6065 }
6066 if (defined $since && $latest_epoch <= $since) {
6067 print $cgi->header(
6068 -type => $content_type,
6069 -charset => 'utf-8',
6070 -last_modified => $latest_date{'rfc2822'},
6071 -status => '304 Not Modified');
6072 return;
6073 }
6074 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006075 print $cgi->header(
6076 -type => $content_type,
6077 -charset => 'utf-8',
6078 -last_modified => $latest_date{'rfc2822'});
6079 } else {
6080 print $cgi->header(
6081 -type => $content_type,
6082 -charset => 'utf-8');
6083 }
6084
6085 # Optimization: skip generating the body if client asks only
6086 # for Last-Modified date.
6087 return if ($cgi->request_method() eq 'HEAD');
6088
6089 # header variables
6090 my $title = "$site_name - $project/$action";
6091 my $feed_type = 'log';
6092 if (defined $hash) {
6093 $title .= " - '$hash'";
6094 $feed_type = 'branch log';
6095 if (defined $file_name) {
6096 $title .= " :: $file_name";
6097 $feed_type = 'history';
6098 }
6099 } elsif (defined $file_name) {
6100 $title .= " - $file_name";
6101 $feed_type = 'history';
6102 }
6103 $title .= " $feed_type";
6104 my $descr = git_get_project_description($project);
6105 if (defined $descr) {
6106 $descr = esc_html($descr);
6107 } else {
6108 $descr = "$project " .
6109 ($format eq 'rss' ? 'RSS' : 'Atom') .
6110 " feed";
6111 }
6112 my $owner = git_get_project_owner($project);
6113 $owner = esc_html($owner);
6114
6115 #header
6116 my $alt_url;
6117 if (defined $file_name) {
6118 $alt_url = href(-full=>1, action=>"history", hash=>$hash, file_name=>$file_name);
6119 } elsif (defined $hash) {
6120 $alt_url = href(-full=>1, action=>"log", hash=>$hash);
6121 } else {
6122 $alt_url = href(-full=>1, action=>"summary");
6123 }
6124 print qq!<?xml version="1.0" encoding="utf-8"?>\n!;
6125 if ($format eq 'rss') {
6126 print <<XML;
Jakub Narebski59b9f612006-08-22 23:42:53 +02006127<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
6128<channel>
Jakub Narebski59b9f612006-08-22 23:42:53 +02006129XML
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006130 print "<title>$title</title>\n" .
6131 "<link>$alt_url</link>\n" .
6132 "<description>$descr</description>\n" .
Giuseppe Bilotta3ac109a2009-01-26 12:50:13 +01006133 "<language>en</language>\n" .
6134 # project owner is responsible for 'editorial' content
6135 "<managingEditor>$owner</managingEditor>\n";
Giuseppe Bilotta1ba68ce2009-01-26 12:50:11 +01006136 if (defined $logo || defined $favicon) {
6137 # prefer the logo to the favicon, since RSS
6138 # doesn't allow both
6139 my $img = esc_url($logo || $favicon);
6140 print "<image>\n" .
6141 "<url>$img</url>\n" .
6142 "<title>$title</title>\n" .
6143 "<link>$alt_url</link>\n" .
6144 "</image>\n";
6145 }
Giuseppe Bilotta0cf31282009-01-26 12:50:14 +01006146 if (%latest_date) {
6147 print "<pubDate>$latest_date{'rfc2822'}</pubDate>\n";
6148 print "<lastBuildDate>$latest_date{'rfc2822'}</lastBuildDate>\n";
6149 }
Giuseppe Bilottaad59a7a2009-01-26 12:50:12 +01006150 print "<generator>gitweb v.$version/$git_version</generator>\n";
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006151 } elsif ($format eq 'atom') {
6152 print <<XML;
6153<feed xmlns="http://www.w3.org/2005/Atom">
6154XML
6155 print "<title>$title</title>\n" .
6156 "<subtitle>$descr</subtitle>\n" .
6157 '<link rel="alternate" type="text/html" href="' .
6158 $alt_url . '" />' . "\n" .
6159 '<link rel="self" type="' . $content_type . '" href="' .
6160 $cgi->self_url() . '" />' . "\n" .
6161 "<id>" . href(-full=>1) . "</id>\n" .
6162 # use project owner for feed author
6163 "<author><name>$owner</name></author>\n";
6164 if (defined $favicon) {
6165 print "<icon>" . esc_url($favicon) . "</icon>\n";
6166 }
6167 if (defined $logo_url) {
6168 # not twice as wide as tall: 72 x 27 pixels
Jakub Narebskie1147262006-12-04 14:09:43 +01006169 print "<logo>" . esc_url($logo) . "</logo>\n";
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006170 }
6171 if (! %latest_date) {
6172 # dummy date to keep the feed valid until commits trickle in:
6173 print "<updated>1970-01-01T00:00:00Z</updated>\n";
6174 } else {
6175 print "<updated>$latest_date{'iso-8601'}</updated>\n";
6176 }
Giuseppe Bilottaad59a7a2009-01-26 12:50:12 +01006177 print "<generator version='$version/$git_version'>gitweb</generator>\n";
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006178 }
Jakub Narebski717b8312006-07-31 21:22:15 +02006179
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006180 # contents
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00006181 for (my $i = 0; $i <= $#commitlist; $i++) {
6182 my %co = %{$commitlist[$i]};
6183 my $commit = $co{'id'};
Jakub Narebski717b8312006-07-31 21:22:15 +02006184 # we read 150, we always show 30 and the ones more recent than 48 hours
Jakub Narebski91fd2bf2006-11-25 15:54:34 +01006185 if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02006186 last;
6187 }
Jakub Narebski91fd2bf2006-11-25 15:54:34 +01006188 my %cd = parse_date($co{'author_epoch'});
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006189
6190 # get list of changed files
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00006191 open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebskic906b182007-05-19 02:47:51 +02006192 $co{'parent'} || "--root",
6193 $co{'id'}, "--", (defined $file_name ? $file_name : ())
Jakub Narebski6bcf4b42006-08-27 23:49:36 +02006194 or next;
Jakub Narebski717b8312006-07-31 21:22:15 +02006195 my @difftree = map { chomp; $_ } <$fd>;
Jakub Narebski6bcf4b42006-08-27 23:49:36 +02006196 close $fd
6197 or next;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006198
6199 # print element (entry, item)
Florian La Rochee62a6412008-02-03 12:38:46 +01006200 my $co_url = href(-full=>1, action=>"commitdiff", hash=>$commit);
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006201 if ($format eq 'rss') {
6202 print "<item>\n" .
6203 "<title>" . esc_html($co{'title'}) . "</title>\n" .
6204 "<author>" . esc_html($co{'author'}) . "</author>\n" .
6205 "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
6206 "<guid isPermaLink=\"true\">$co_url</guid>\n" .
6207 "<link>$co_url</link>\n" .
6208 "<description>" . esc_html($co{'title'}) . "</description>\n" .
6209 "<content:encoded>" .
6210 "<![CDATA[\n";
6211 } elsif ($format eq 'atom') {
6212 print "<entry>\n" .
6213 "<title type=\"html\">" . esc_html($co{'title'}) . "</title>\n" .
6214 "<updated>$cd{'iso-8601'}</updated>\n" .
Jakub Narebskiab23c192006-11-25 15:54:33 +01006215 "<author>\n" .
6216 " <name>" . esc_html($co{'author_name'}) . "</name>\n";
6217 if ($co{'author_email'}) {
6218 print " <email>" . esc_html($co{'author_email'}) . "</email>\n";
6219 }
6220 print "</author>\n" .
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006221 # use committer for contributor
Jakub Narebskiab23c192006-11-25 15:54:33 +01006222 "<contributor>\n" .
6223 " <name>" . esc_html($co{'committer_name'}) . "</name>\n";
6224 if ($co{'committer_email'}) {
6225 print " <email>" . esc_html($co{'committer_email'}) . "</email>\n";
6226 }
6227 print "</contributor>\n" .
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006228 "<published>$cd{'iso-8601'}</published>\n" .
6229 "<link rel=\"alternate\" type=\"text/html\" href=\"$co_url\" />\n" .
6230 "<id>$co_url</id>\n" .
6231 "<content type=\"xhtml\" xml:base=\"" . esc_url($my_url) . "\">\n" .
6232 "<div xmlns=\"http://www.w3.org/1999/xhtml\">\n";
6233 }
Jakub Narebski717b8312006-07-31 21:22:15 +02006234 my $comment = $co{'comment'};
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006235 print "<pre>\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02006236 foreach my $line (@$comment) {
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006237 $line = esc_html($line);
6238 print "$line\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02006239 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006240 print "</pre><ul>\n";
6241 foreach my $difftree_line (@difftree) {
6242 my %difftree = parse_difftree_raw_line($difftree_line);
6243 next if !$difftree{'from_id'};
6244
6245 my $file = $difftree{'file'} || $difftree{'to_file'};
6246
6247 print "<li>" .
6248 "[" .
6249 $cgi->a({-href => href(-full=>1, action=>"blobdiff",
6250 hash=>$difftree{'to_id'}, hash_parent=>$difftree{'from_id'},
6251 hash_base=>$co{'id'}, hash_parent_base=>$co{'parent'},
6252 file_name=>$file, file_parent=>$difftree{'from_file'}),
6253 -title => "diff"}, 'D');
6254 if ($have_blame) {
6255 print $cgi->a({-href => href(-full=>1, action=>"blame",
6256 file_name=>$file, hash_base=>$commit),
6257 -title => "blame"}, 'B');
Jakub Narebski717b8312006-07-31 21:22:15 +02006258 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006259 # if this is not a feed of a file history
6260 if (!defined $file_name || $file_name ne $file) {
6261 print $cgi->a({-href => href(-full=>1, action=>"history",
6262 file_name=>$file, hash=>$commit),
6263 -title => "history"}, 'H');
6264 }
6265 $file = esc_path($file);
6266 print "] ".
6267 "$file</li>\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02006268 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006269 if ($format eq 'rss') {
6270 print "</ul>]]>\n" .
6271 "</content:encoded>\n" .
6272 "</item>\n";
6273 } elsif ($format eq 'atom') {
6274 print "</ul>\n</div>\n" .
6275 "</content>\n" .
6276 "</entry>\n";
6277 }
Jakub Narebski717b8312006-07-31 21:22:15 +02006278 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006279
6280 # end of feed
6281 if ($format eq 'rss') {
6282 print "</channel>\n</rss>\n";
6283 } elsif ($format eq 'atom') {
6284 print "</feed>\n";
6285 }
6286}
6287
6288sub git_rss {
6289 git_feed('rss');
6290}
6291
6292sub git_atom {
6293 git_feed('atom');
Jakub Narebski717b8312006-07-31 21:22:15 +02006294}
6295
6296sub git_opml {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006297 my @list = git_get_projects_list();
Jakub Narebski717b8312006-07-31 21:22:15 +02006298
Giuseppe Bilottaae357852009-01-02 13:49:30 +01006299 print $cgi->header(
6300 -type => 'text/xml',
6301 -charset => 'utf-8',
6302 -content_disposition => 'inline; filename="opml.xml"');
6303
Jakub Narebski59b9f612006-08-22 23:42:53 +02006304 print <<XML;
6305<?xml version="1.0" encoding="utf-8"?>
6306<opml version="1.0">
6307<head>
Petr Baudis8be28902006-10-24 05:18:39 +02006308 <title>$site_name OPML Export</title>
Jakub Narebski59b9f612006-08-22 23:42:53 +02006309</head>
6310<body>
6311<outline text="git RSS feeds">
6312XML
Jakub Narebski717b8312006-07-31 21:22:15 +02006313
6314 foreach my $pr (@list) {
6315 my %proj = %$pr;
Jakub Narebski847e01f2006-08-14 02:05:47 +02006316 my $head = git_get_head_hash($proj{'path'});
Jakub Narebski717b8312006-07-31 21:22:15 +02006317 if (!defined $head) {
6318 next;
6319 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02006320 $git_dir = "$projectroot/$proj{'path'}";
Jakub Narebski847e01f2006-08-14 02:05:47 +02006321 my %co = parse_commit($head);
Jakub Narebski717b8312006-07-31 21:22:15 +02006322 if (!%co) {
6323 next;
6324 }
6325
6326 my $path = esc_html(chop_str($proj{'path'}, 25, 5));
Giuseppe Bilottadf63fbb2009-01-02 13:15:28 +01006327 my $rss = href('project' => $proj{'path'}, 'action' => 'rss', -full => 1);
6328 my $html = href('project' => $proj{'path'}, 'action' => 'summary', -full => 1);
Jakub Narebski717b8312006-07-31 21:22:15 +02006329 print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";
6330 }
Jakub Narebski59b9f612006-08-22 23:42:53 +02006331 print <<XML;
6332</outline>
6333</body>
6334</opml>
6335XML
Jakub Narebski717b8312006-07-31 21:22:15 +02006336}