blob: 2ccbb6aa3482924263ff0a110891cab82f71575c [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 Narebskiaa7dd052009-09-01 13:39:16 +020021our $t0;
22if (eval { require Time::HiRes; 1; }) {
23 $t0 = [Time::HiRes::gettimeofday()];
24}
25our $number_of_git_cmds = 0;
26
Jakub Narebskib1f5f642006-12-28 00:00:52 +010027BEGIN {
Jakub Narebski3be8e722007-04-01 22:22:21 +020028 CGI->compile() if $ENV{'MOD_PERL'};
Jakub Narebskib1f5f642006-12-28 00:00:52 +010029}
30
Dennis Stosberg4a87b432006-06-21 15:07:08 +020031our $cgi = new CGI;
Junio C Hamano06c084d2006-08-02 13:50:20 -070032our $version = "++GIT_VERSION++";
Dennis Stosberg4a87b432006-06-21 15:07:08 +020033our $my_url = $cgi->url();
34our $my_uri = $cgi->url(-absolute => 1);
Kay Sievers44ad2972005-08-07 19:59:24 +020035
Giuseppe Bilotta81d3fe92009-02-15 10:18:36 +010036# Base URL for relative URLs in gitweb ($logo, $favicon, ...),
37# needed and used only for URLs with nonempty PATH_INFO
38our $base_url = $my_url;
39
40# When the script is used as DirectoryIndex, the URL does not contain the name
41# of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
42# have to do it ourselves. We make $path_info global because it's also used
43# later on.
44#
45# Another issue with the script being the DirectoryIndex is that the resulting
46# $my_url data is not the full script URL: this is good, because we want
47# generated links to keep implying the script name if it wasn't explicitly
48# indicated in the URL we're handling, but it means that $my_url cannot be used
49# as base URL.
50# Therefore, if we needed to strip PATH_INFO, then we know that we have
51# to build the base URL ourselves:
Alexander Gavrilovdde80d92008-11-06 01:10:07 +030052our $path_info = $ENV{"PATH_INFO"};
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +020053if ($path_info) {
Giuseppe Bilotta81d3fe92009-02-15 10:18:36 +010054 if ($my_url =~ s,\Q$path_info\E$,, &&
55 $my_uri =~ s,\Q$path_info\E$,, &&
56 defined $ENV{'SCRIPT_NAME'}) {
57 $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
58 }
Giuseppe Bilottab65910f2008-09-29 15:07:42 +020059}
60
Alp Tokere130dda2006-07-12 23:55:10 +010061# core git executable to use
62# this can just be "git" if your webserver has a sensible PATH
Junio C Hamano06c084d2006-08-02 13:50:20 -070063our $GIT = "++GIT_BINDIR++/git";
Jakub Narebski3f7f27102006-06-21 09:48:04 +020064
Kay Sieversb87d78d2005-08-07 20:21:04 +020065# absolute fs-path which will be prepended to the project path
Dennis Stosberg4a87b432006-06-21 15:07:08 +020066#our $projectroot = "/pub/scm";
Junio C Hamano06c084d2006-08-02 13:50:20 -070067our $projectroot = "++GITWEB_PROJECTROOT++";
Kay Sieversb87d78d2005-08-07 20:21:04 +020068
Luke Luca5e9492007-10-16 20:45:25 -070069# fs traversing limit for getting project list
70# the number is relative to the projectroot
71our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++";
72
Kay Sieversb87d78d2005-08-07 20:21:04 +020073# target of the home link on top of all pages
Martin Waitz6132b7e2006-08-17 00:28:39 +020074our $home_link = $my_uri || "/";
Kay Sieversb87d78d2005-08-07 20:21:04 +020075
Yasushi SHOJI2de21fa2006-08-15 07:50:49 +090076# string of the home link on top of all pages
77our $home_link_str = "++GITWEB_HOME_LINK_STR++";
78
Alp Toker49da1da2006-07-11 21:10:26 +010079# name of your site or organization to appear in page titles
80# replace this with something more descriptive for clearer bookmarks
Petr Baudis8be28902006-10-24 05:18:39 +020081our $site_name = "++GITWEB_SITENAME++"
82 || ($ENV{'SERVER_NAME'} || "Untitled") . " Git";
Alp Toker49da1da2006-07-11 21:10:26 +010083
Alan Chandlerb2d34762006-10-03 13:49:03 +010084# filename of html text to include at top of each page
85our $site_header = "++GITWEB_SITE_HEADER++";
Kay Sievers8ab1da22005-08-07 20:22:53 +020086# html text to include at home page
Junio C Hamano06c084d2006-08-02 13:50:20 -070087our $home_text = "++GITWEB_HOMETEXT++";
Alan Chandlerb2d34762006-10-03 13:49:03 +010088# filename of html text to include at bottom of each page
89our $site_footer = "++GITWEB_SITE_FOOTER++";
Kay Sievers8ab1da22005-08-07 20:22:53 +020090
Alan Chandlerb2d34762006-10-03 13:49:03 +010091# URI of stylesheets
92our @stylesheets = ("++GITWEB_CSS++");
Petr Baudis887a6122006-10-26 14:41:25 +020093# URI of a single stylesheet, which can be overridden in GITWEB_CONFIG.
94our $stylesheet = undef;
Jakub Narebski9a7a62f2006-10-06 12:31:05 +020095# URI of GIT logo (72x27 size)
Junio C Hamano06c084d2006-08-02 13:50:20 -070096our $logo = "++GITWEB_LOGO++";
Jakub Narebski0b5deba2006-09-04 20:32:13 +020097# URI of GIT favicon, assumed to be image/png type
98our $favicon = "++GITWEB_FAVICON++";
Jakub Narebski4af819d2009-09-01 13:39:17 +020099# URI of gitweb.js (JavaScript code for gitweb)
100our $javascript = "++GITWEB_JS++";
Jakub Narebskiaedd9422006-06-17 11:23:56 +0200101
Jakub Narebski9a7a62f2006-10-06 12:31:05 +0200102# URI and label (title) of GIT logo link
103#our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/";
104#our $logo_label = "git documentation";
Wincent Colaiuta69fb8282009-07-12 14:31:28 +0200105our $logo_url = "http://git-scm.com/";
Jakub Narebski9a7a62f2006-10-06 12:31:05 +0200106our $logo_label = "git homepage";
Junio C Hamano51a7c662006-09-23 12:36:01 -0700107
Kay Sievers09bd7892005-08-07 20:21:23 +0200108# source of projects list
Junio C Hamano06c084d2006-08-02 13:50:20 -0700109our $projects_list = "++GITWEB_LIST++";
Kay Sieversb87d78d2005-08-07 20:21:04 +0200110
Michael Hendricks55feb122007-07-04 18:36:48 -0600111# the width (in characters) of the projects list "Description" column
112our $projects_list_description_width = 25;
113
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +0200114# default order of projects list
115# valid values are none, project, descr, owner, and age
116our $default_projects_order = "project";
117
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +0200118# show repository only if this file exists
119# (only effective if this variable evaluates to true)
120our $export_ok = "++GITWEB_EXPORT_OK++";
121
Alexander Gavrilovdd7f5f12008-11-06 01:36:23 +0300122# show repository only if this subroutine returns true
123# when given the path to the project, for example:
124# sub { return -e "$_[0]/git-daemon-export-ok"; }
125our $export_auth_hook = undef;
126
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +0200127# only allow viewing of repositories also shown on the overview page
128our $strict_export = "++GITWEB_STRICT_EXPORT++";
129
Jakub Narebski19a87212006-08-15 23:03:17 +0200130# list of git base URLs used for URL to where fetch project from,
131# i.e. full URL is "$git_base_url/$project"
Jakub Narebskid6b7e0b2006-10-26 12:26:44 +0200132our @git_base_url_list = grep { $_ ne '' } ("++GITWEB_BASE_URL++");
Jakub Narebski19a87212006-08-15 23:03:17 +0200133
Jakub Narebskif5aa79d2006-06-17 13:32:15 +0200134# default blob_plain mimetype and default charset for text/plain blob
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200135our $default_blob_plain_mimetype = 'text/plain';
136our $default_text_plain_charset = undef;
Jakub Narebskif5aa79d2006-06-17 13:32:15 +0200137
Petr Baudis2d007372006-06-18 00:01:06 +0200138# file to use for guessing MIME types before trying /etc/mime.types
139# (relative to the current git repository)
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200140our $mimetypes_file = undef;
Petr Baudis2d007372006-06-18 00:01:06 +0200141
Martin Koegler00f429a2007-06-03 17:42:44 +0200142# assume this charset if line contains non-UTF-8 characters;
143# it should be valid encoding (see Encoding::Supported(3pm) for list),
144# for which encoding all byte sequences are valid, for example
145# 'iso-8859-1' aka 'latin1' (it is decoded without checking, so it
146# could be even 'utf-8' for the old behavior)
147our $fallback_encoding = 'latin1';
148
Jakub Narebski69a9b412007-07-20 02:15:09 +0200149# rename detection options for git-diff and git-diff-tree
150# - default is '-M', with the cost proportional to
151# (number of removed files) * (number of new files).
152# - more costly is '-C' (which implies '-M'), with the cost proportional to
153# (number of changed files + number of removed files) * (number of new files)
154# - even more costly is '-C', '--find-copies-harder' with cost
155# (number of files in the original tree) * (number of new files)
156# - one might want to include '-B' option, e.g. '-B', '-M'
157our @diff_opts = ('-M'); # taken from git_commit
158
Matt McCutchen7e1100e2009-02-07 19:00:09 -0500159# Disables features that would allow repository owners to inject script into
160# the gitweb domain.
161our $prevent_xss = 0;
162
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200163# information about snapshot formats that gitweb is capable of serving
164our %known_snapshot_formats = (
165 # name => {
166 # 'display' => display name,
167 # 'type' => mime type,
168 # 'suffix' => filename suffix,
169 # 'format' => --format for git-archive,
170 # 'compressor' => [compressor command and arguments]
Mark A Rada1bfd3632009-08-06 10:25:39 -0400171 # (array reference, optional)
172 # 'disabled' => boolean (optional)}
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200173 #
174 'tgz' => {
175 'display' => 'tar.gz',
176 'type' => 'application/x-gzip',
177 'suffix' => '.tar.gz',
178 'format' => 'tar',
179 'compressor' => ['gzip']},
180
181 'tbz2' => {
182 'display' => 'tar.bz2',
183 'type' => 'application/x-bzip2',
184 'suffix' => '.tar.bz2',
185 'format' => 'tar',
186 'compressor' => ['bzip2']},
187
Mark A Radacbdefb52009-08-06 10:28:25 -0400188 'txz' => {
189 'display' => 'tar.xz',
190 'type' => 'application/x-xz',
191 'suffix' => '.tar.xz',
192 'format' => 'tar',
193 'compressor' => ['xz'],
194 'disabled' => 1},
195
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200196 'zip' => {
197 'display' => 'zip',
198 'type' => 'application/x-zip',
199 'suffix' => '.zip',
200 'format' => 'zip'},
201);
202
203# Aliases so we understand old gitweb.snapshot values in repository
204# configuration.
205our %known_snapshot_format_aliases = (
206 'gzip' => 'tgz',
207 'bzip2' => 'tbz2',
Mark A Radacbdefb52009-08-06 10:28:25 -0400208 'xz' => 'txz',
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200209
210 # backward compatibility: legacy gitweb config support
211 'x-gzip' => undef, 'gz' => undef,
212 'x-bzip2' => undef, 'bz2' => undef,
213 'x-zip' => undef, '' => undef,
214);
215
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200216# Pixel sizes for icons and avatars. If the default font sizes or lineheights
217# are changed, it may be appropriate to change these values too via
218# $GITWEB_CONFIG.
219our %avatar_size = (
220 'default' => 16,
221 'double' => 32
222);
223
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +0100224# Used to set the maximum load that we will still respond to gitweb queries.
225# If server load exceed this value then return "503 server busy" error.
226# If gitweb cannot determined server load, it is taken to be 0.
227# Leave it undefined (or set to 'undef') to turn off load checking.
228our $maxload = 300;
229
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530230# You define site-wide feature defaults here; override them with
231# $GITWEB_CONFIG as necessary.
Jakub Narebski952c65f2006-08-22 16:52:50 +0200232our %feature = (
Jakub Narebski17848fc2006-08-26 19:14:22 +0200233 # feature => {
234 # 'sub' => feature-sub (subroutine),
235 # 'override' => allow-override (boolean),
236 # 'default' => [ default options...] (array reference)}
237 #
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200238 # if feature is overridable (it means that allow-override has true value),
Jakub Narebski17848fc2006-08-26 19:14:22 +0200239 # then feature-sub will be called with default options as parameters;
240 # return value of feature-sub indicates if to enable specified feature
241 #
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200242 # if there is no 'sub' key (no feature-sub), then feature cannot be
243 # overriden
244 #
Giuseppe Bilottaff3c0ff2008-12-02 14:57:28 -0800245 # use gitweb_get_feature(<feature>) to retrieve the <feature> value
246 # (an array) or gitweb_check_feature(<feature>) to check if <feature>
247 # is enabled
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530248
Petr Baudis45a3b122006-10-07 15:17:47 +0200249 # Enable the 'blame' blob view, showing the last commit that modified
250 # each line in the file. This can be very CPU-intensive.
251
252 # To enable system wide have in $GITWEB_CONFIG
253 # $feature{'blame'}{'default'} = [1];
254 # To have project specific config enable override in $GITWEB_CONFIG
255 # $feature{'blame'}{'override'} = 1;
256 # and in project config gitweb.blame = 0|1;
Jakub Narebski952c65f2006-08-22 16:52:50 +0200257 'blame' => {
Matt Kraaicdad8172008-12-15 22:16:19 -0800258 'sub' => sub { feature_bool('blame', @_) },
Jakub Narebski952c65f2006-08-22 16:52:50 +0200259 'override' => 0,
260 'default' => [0]},
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530261
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200262 # Enable the 'snapshot' link, providing a compressed archive of any
Petr Baudis45a3b122006-10-07 15:17:47 +0200263 # tree. This can potentially generate high traffic if you have large
264 # project.
265
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200266 # Value is a list of formats defined in %known_snapshot_formats that
267 # you wish to offer.
Petr Baudis45a3b122006-10-07 15:17:47 +0200268 # To disable system wide have in $GITWEB_CONFIG
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200269 # $feature{'snapshot'}{'default'} = [];
Petr Baudis45a3b122006-10-07 15:17:47 +0200270 # To have project specific config enable override in $GITWEB_CONFIG
Uwe Zeisbergerbbee1d92006-12-08 12:44:31 +0100271 # $feature{'snapshot'}{'override'} = 1;
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200272 # and in project config, a comma-separated list of formats or "none"
273 # to disable. Example: gitweb.snapshot = tbz2,zip;
Jakub Narebski952c65f2006-08-22 16:52:50 +0200274 'snapshot' => {
275 'sub' => \&feature_snapshot,
276 'override' => 0,
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200277 'default' => ['tgz']},
Jakub Narebski04f7a942006-09-11 00:29:27 +0200278
Robert Fitzsimons6be93512006-12-23 03:35:16 +0000279 # Enable text search, which will list the commits which match author,
280 # committer or commit text to a given string. Enabled by default.
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200281 # Project specific override is not supported.
Robert Fitzsimons6be93512006-12-23 03:35:16 +0000282 'search' => {
283 'override' => 0,
284 'default' => [1]},
285
Petr Baudise7738552007-05-17 04:31:12 +0200286 # Enable grep search, which will list the files in currently selected
287 # tree containing the given string. Enabled by default. This can be
288 # potentially CPU-intensive, of course.
289
290 # To enable system wide have in $GITWEB_CONFIG
291 # $feature{'grep'}{'default'} = [1];
292 # To have project specific config enable override in $GITWEB_CONFIG
293 # $feature{'grep'}{'override'} = 1;
294 # and in project config gitweb.grep = 0|1;
295 'grep' => {
Matt Kraaicdad8172008-12-15 22:16:19 -0800296 'sub' => sub { feature_bool('grep', @_) },
Petr Baudise7738552007-05-17 04:31:12 +0200297 'override' => 0,
298 'default' => [1]},
299
Petr Baudis45a3b122006-10-07 15:17:47 +0200300 # Enable the pickaxe search, which will list the commits that modified
301 # a given string in a file. This can be practical and quite faster
302 # alternative to 'blame', but still potentially CPU-intensive.
303
304 # To enable system wide have in $GITWEB_CONFIG
305 # $feature{'pickaxe'}{'default'} = [1];
306 # To have project specific config enable override in $GITWEB_CONFIG
307 # $feature{'pickaxe'}{'override'} = 1;
308 # and in project config gitweb.pickaxe = 0|1;
Jakub Narebski04f7a942006-09-11 00:29:27 +0200309 'pickaxe' => {
Matt Kraaicdad8172008-12-15 22:16:19 -0800310 'sub' => sub { feature_bool('pickaxe', @_) },
Jakub Narebski04f7a942006-09-11 00:29:27 +0200311 'override' => 0,
312 'default' => [1]},
Martin Waitz9e756902006-10-01 23:57:48 +0200313
Jakub Narebskie4b48ea2009-09-07 14:40:00 +0200314 # Enable showing size of blobs in a 'tree' view, in a separate
315 # column, similar to what 'ls -l' does. This cost a bit of IO.
316
317 # To disable system wide have in $GITWEB_CONFIG
318 # $feature{'show-sizes'}{'default'} = [0];
319 # To have project specific config enable override in $GITWEB_CONFIG
320 # $feature{'show-sizes'}{'override'} = 1;
321 # and in project config gitweb.showsizes = 0|1;
322 'show-sizes' => {
323 'sub' => sub { feature_bool('showsizes', @_) },
324 'override' => 0,
325 'default' => [1]},
326
Petr Baudis45a3b122006-10-07 15:17:47 +0200327 # Make gitweb use an alternative format of the URLs which can be
328 # more readable and natural-looking: project name is embedded
329 # directly in the path and the query string contains other
330 # auxiliary information. All gitweb installations recognize
331 # URL in either format; this configures in which formats gitweb
332 # generates links.
333
334 # To enable system wide have in $GITWEB_CONFIG
335 # $feature{'pathinfo'}{'default'} = [1];
336 # Project specific override is not supported.
337
338 # Note that you will need to change the default location of CSS,
339 # favicon, logo and possibly other files to an absolute URL. Also,
340 # if gitweb.cgi serves as your indexfile, you will need to force
341 # $my_uri to contain the script name in your $GITWEB_CONFIG.
Martin Waitz9e756902006-10-01 23:57:48 +0200342 'pathinfo' => {
343 'override' => 0,
344 'default' => [0]},
Petr Baudise30496d2006-10-24 05:33:17 +0200345
346 # Make gitweb consider projects in project root subdirectories
347 # to be forks of existing projects. Given project $projname.git,
348 # projects matching $projname/*.git will not be shown in the main
349 # projects list, instead a '+' mark will be added to $projname
350 # there and a 'forks' view will be enabled for the project, listing
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +0200351 # all the forks. If project list is taken from a file, forks have
352 # to be listed after the main project.
Petr Baudise30496d2006-10-24 05:33:17 +0200353
354 # To enable system wide have in $GITWEB_CONFIG
355 # $feature{'forks'}{'default'} = [1];
356 # Project specific override is not supported.
357 'forks' => {
358 'override' => 0,
359 'default' => [0]},
Petr Baudisd627f682008-10-02 16:36:52 +0200360
361 # Insert custom links to the action bar of all project pages.
362 # This enables you mainly to link to third-party scripts integrating
363 # into gitweb; e.g. git-browser for graphical history representation
364 # or custom web-based repository administration interface.
365
366 # The 'default' value consists of a list of triplets in the form
367 # (label, link, position) where position is the label after which
Jakub Narebski2b11e052008-10-12 00:02:32 +0200368 # to insert the link and link is a format string where %n expands
Petr Baudisd627f682008-10-02 16:36:52 +0200369 # to the project name, %f to the project path within the filesystem,
370 # %h to the current hash (h gitweb parameter) and %b to the current
Jakub Narebski2b11e052008-10-12 00:02:32 +0200371 # hash base (hb gitweb parameter); %% expands to %.
Petr Baudisd627f682008-10-02 16:36:52 +0200372
373 # To enable system wide have in $GITWEB_CONFIG e.g.
374 # $feature{'actions'}{'default'} = [('graphiclog',
375 # '/git-browser/by-commit.html?r=%n', 'summary')];
376 # Project specific override is not supported.
377 'actions' => {
378 'override' => 0,
379 'default' => []},
Shawn O. Pearce3e3d4ee2008-10-03 07:41:25 -0700380
Petr Baudisaed93de2008-10-02 17:13:02 +0200381 # Allow gitweb scan project content tags described in ctags/
382 # of project repository, and display the popular Web 2.0-ish
383 # "tag cloud" near the project list. Note that this is something
384 # COMPLETELY different from the normal Git tags.
385
386 # gitweb by itself can show existing tags, but it does not handle
387 # tagging itself; you need an external application for that.
388 # For an example script, check Girocco's cgi/tagproj.cgi.
389 # You may want to install the HTML::TagCloud Perl module to get
390 # a pretty tag cloud instead of just a list of tags.
391
392 # To enable system wide have in $GITWEB_CONFIG
393 # $feature{'ctags'}{'default'} = ['path_to_tag_script'];
394 # Project specific override is not supported.
395 'ctags' => {
396 'override' => 0,
397 'default' => [0]},
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +0100398
399 # The maximum number of patches in a patchset generated in patch
400 # view. Set this to 0 or undef to disable patch view, or to a
401 # negative number to remove any limit.
402
403 # To disable system wide have in $GITWEB_CONFIG
404 # $feature{'patches'}{'default'} = [0];
405 # To have project specific config enable override in $GITWEB_CONFIG
406 # $feature{'patches'}{'override'} = 1;
407 # and in project config gitweb.patches = 0|n;
408 # where n is the maximum number of patches allowed in a patchset.
409 'patches' => {
410 'sub' => \&feature_patches,
411 'override' => 0,
412 'default' => [16]},
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200413
414 # Avatar support. When this feature is enabled, views such as
415 # shortlog or commit will display an avatar associated with
416 # the email of the committer(s) and/or author(s).
417
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +0200418 # Currently available providers are gravatar and picon.
419 # If an unknown provider is specified, the feature is disabled.
420
421 # Gravatar depends on Digest::MD5.
422 # Picon currently relies on the indiana.edu database.
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200423
424 # To enable system wide have in $GITWEB_CONFIG
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +0200425 # $feature{'avatar'}{'default'} = ['<provider>'];
426 # where <provider> is either gravatar or picon.
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200427 # To have project specific config enable override in $GITWEB_CONFIG
428 # $feature{'avatar'}{'override'} = 1;
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +0200429 # and in project config gitweb.avatar = <provider>;
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200430 'avatar' => {
431 'sub' => \&feature_avatar,
432 'override' => 0,
433 'default' => ['']},
Jakub Narebskiaa7dd052009-09-01 13:39:16 +0200434
435 # Enable displaying how much time and how many git commands
436 # it took to generate and display page. Disabled by default.
437 # Project specific override is not supported.
438 'timed' => {
439 'override' => 0,
440 'default' => [0]},
Jakub Narebskie627e502009-11-26 21:12:15 +0100441
442 # Enable turning some links into links to actions which require
443 # JavaScript to run (like 'blame_incremental'). Not enabled by
444 # default. Project specific override is currently not supported.
445 'javascript-actions' => {
446 'override' => 0,
447 'default' => [0]},
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530448);
449
Junio C Hamanoa7c5a282008-11-29 13:02:08 -0800450sub gitweb_get_feature {
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530451 my ($name) = @_;
Jakub Narebskidd1ad5f2006-09-26 01:56:17 +0200452 return unless exists $feature{$name};
Jakub Narebski952c65f2006-08-22 16:52:50 +0200453 my ($sub, $override, @defaults) = (
454 $feature{$name}{'sub'},
455 $feature{$name}{'override'},
456 @{$feature{$name}{'default'}});
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530457 if (!$override) { return @defaults; }
Martin Waitza9455912006-10-03 20:07:43 +0200458 if (!defined $sub) {
Nanako Shiraishi93197892009-08-28 12:18:49 +0900459 warn "feature $name is not overridable";
Martin Waitza9455912006-10-03 20:07:43 +0200460 return @defaults;
461 }
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530462 return $sub->(@defaults);
463}
464
Giuseppe Bilotta25b27902008-11-29 13:07:29 -0800465# A wrapper to check if a given feature is enabled.
466# With this, you can say
467#
468# my $bool_feat = gitweb_check_feature('bool_feat');
469# gitweb_check_feature('bool_feat') or somecode;
470#
471# instead of
472#
473# my ($bool_feat) = gitweb_get_feature('bool_feat');
474# (gitweb_get_feature('bool_feat'))[0] or somecode;
475#
476sub gitweb_check_feature {
477 return (gitweb_get_feature(@_))[0];
478}
479
480
Matt Kraaicdad8172008-12-15 22:16:19 -0800481sub feature_bool {
482 my $key = shift;
483 my ($val) = git_get_project_config($key, '--bool');
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530484
Marcel M. Carydf5d10a2009-02-18 14:09:41 +0100485 if (!defined $val) {
486 return ($_[0]);
487 } elsif ($val eq 'true') {
Matt Kraaicdad8172008-12-15 22:16:19 -0800488 return (1);
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530489 } elsif ($val eq 'false') {
Matt Kraaicdad8172008-12-15 22:16:19 -0800490 return (0);
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530491 }
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530492}
493
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530494sub feature_snapshot {
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200495 my (@fmts) = @_;
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530496
497 my ($val) = git_get_project_config('snapshot');
498
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200499 if ($val) {
500 @fmts = ($val eq 'none' ? () : split /\s*[,\s]\s*/, $val);
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530501 }
502
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200503 return @fmts;
Luben Tuikovde9272f2006-09-28 16:49:21 -0700504}
505
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +0100506sub feature_patches {
507 my @val = (git_get_project_config('patches', '--int'));
508
509 if (@val) {
510 return @val;
511 }
512
513 return ($_[0]);
514}
515
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200516sub feature_avatar {
517 my @val = (git_get_project_config('avatar'));
518
519 return @val ? @val : @_;
520}
521
Junio C Hamano2172ce42006-10-03 02:30:47 -0700522# checking HEAD file with -e is fragile if the repository was
523# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
524# and then pruned.
525sub check_head_link {
526 my ($dir) = @_;
527 my $headfile = "$dir/HEAD";
528 return ((-e $headfile) ||
529 (-l $headfile && readlink($headfile) =~ /^refs\/heads\//));
530}
531
532sub check_export_ok {
533 my ($dir) = @_;
534 return (check_head_link($dir) &&
Alexander Gavrilovdd7f5f12008-11-06 01:36:23 +0300535 (!$export_ok || -e "$dir/$export_ok") &&
536 (!$export_auth_hook || $export_auth_hook->($dir)));
Junio C Hamano2172ce42006-10-03 02:30:47 -0700537}
538
Jakub Narebskia7817852007-07-22 23:41:20 +0200539# process alternate names for backward compatibility
540# filter out unsupported (unknown) snapshot formats
541sub filter_snapshot_fmts {
542 my @fmts = @_;
543
544 @fmts = map {
545 exists $known_snapshot_format_aliases{$_} ?
546 $known_snapshot_format_aliases{$_} : $_} @fmts;
Jakub Narebski68cedb12009-05-10 02:40:37 +0200547 @fmts = grep {
Mark A Rada1bfd3632009-08-06 10:25:39 -0400548 exists $known_snapshot_formats{$_} &&
549 !$known_snapshot_formats{$_}{'disabled'}} @fmts;
Jakub Narebskia7817852007-07-22 23:41:20 +0200550}
551
Junio C Hamano06c084d2006-08-02 13:50:20 -0700552our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
Gerrit Pape17a8b252008-03-26 18:11:19 +0000553if (-e $GITWEB_CONFIG) {
554 do $GITWEB_CONFIG;
555} else {
556 our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
557 do $GITWEB_CONFIG_SYSTEM if -e $GITWEB_CONFIG_SYSTEM;
558}
Jeff Kingc8d138a2006-08-02 15:23:34 -0400559
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +0100560# Get loadavg of system, to compare against $maxload.
561# Currently it requires '/proc/loadavg' present to get loadavg;
562# if it is not present it returns 0, which means no load checking.
563sub get_loadavg {
564 if( -e '/proc/loadavg' ){
565 open my $fd, '<', '/proc/loadavg'
566 or return 0;
567 my @load = split(/\s+/, scalar <$fd>);
568 close $fd;
569
570 # The first three columns measure CPU and IO utilization of the last one,
571 # five, and 10 minute periods. The fourth column shows the number of
572 # currently running processes and the total number of processes in the m/n
573 # format. The last column displays the last process ID used.
574 return $load[0] || 0;
575 }
576 # additional checks for load average should go here for things that don't export
577 # /proc/loadavg
578
579 return 0;
580}
581
Jeff Kingc8d138a2006-08-02 15:23:34 -0400582# version of the core git binary
Jakub Narebski66115d32008-06-14 20:37:59 +0200583our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
Jakub Narebskiaa7dd052009-09-01 13:39:16 +0200584$number_of_git_cmds++;
Jeff Kingc8d138a2006-08-02 15:23:34 -0400585
586$projects_list ||= $projectroot;
Jeff Kingc8d138a2006-08-02 15:23:34 -0400587
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +0100588if (defined $maxload && get_loadavg() > $maxload) {
589 die_error(503, "The load average on the server is too high");
590}
591
Jakub Narebski154b4d72006-08-05 12:55:20 +0200592# ======================================================================
Kay Sievers09bd7892005-08-07 20:21:23 +0200593# input validation and dispatch
Kay Sieversb87d78d2005-08-07 20:21:04 +0200594
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200595# input parameters can be collected from a variety of sources (presently, CGI
596# and PATH_INFO), so we define an %input_params hash that collects them all
597# together during validation: this allows subsequent uses (e.g. href()) to be
598# agnostic of the parameter origin
Kay Sievers6191f8e2005-08-07 20:19:56 +0200599
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300600our %input_params = ();
Martin Waitz5c95fab2006-08-17 00:28:38 +0200601
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200602# input parameters are stored with the long parameter name as key. This will
603# also be used in the href subroutine to convert parameters to their CGI
604# equivalent, and since the href() usage is the most frequent one, we store
605# the name -> CGI key mapping here, instead of the reverse.
606#
607# XXX: Warning: If you touch this, check the search form for updating,
608# too.
Jakub Narebski24d06932006-09-26 01:57:02 +0200609
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300610our @cgi_param_mapping = (
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200611 project => "p",
612 action => "a",
613 file_name => "f",
614 file_parent => "fp",
615 hash => "h",
616 hash_parent => "hp",
617 hash_base => "hb",
618 hash_parent_base => "hpb",
619 page => "pg",
620 order => "o",
621 searchtext => "s",
622 searchtype => "st",
623 snapshot_format => "sf",
624 extra_options => "opt",
625 search_use_regexp => "sr",
Jakub Narebskic4ccf612009-09-01 13:39:19 +0200626 # this must be last entry (for manipulation from JavaScript)
627 javascript => "js"
Miklos Vajna868bc062007-07-12 20:39:27 +0200628);
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300629our %cgi_param_mapping = @cgi_param_mapping;
Miklos Vajna868bc062007-07-12 20:39:27 +0200630
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200631# we will also need to know the possible actions, for validation
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300632our %actions = (
Rafael Garcia-Suarez3a5b9192008-06-06 09:53:32 +0200633 "blame" => \&git_blame,
Jakub Narebski4af819d2009-09-01 13:39:17 +0200634 "blame_incremental" => \&git_blame_incremental,
635 "blame_data" => \&git_blame_data,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200636 "blobdiff" => \&git_blobdiff,
637 "blobdiff_plain" => \&git_blobdiff_plain,
638 "blob" => \&git_blob,
639 "blob_plain" => \&git_blob_plain,
640 "commitdiff" => \&git_commitdiff,
641 "commitdiff_plain" => \&git_commitdiff_plain,
642 "commit" => \&git_commit,
Petr Baudise30496d2006-10-24 05:33:17 +0200643 "forks" => \&git_forks,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200644 "heads" => \&git_heads,
645 "history" => \&git_history,
646 "log" => \&git_log,
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +0100647 "patch" => \&git_patch,
Giuseppe Bilottaa3411f82008-12-18 08:13:18 +0100648 "patches" => \&git_patches,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200649 "rss" => \&git_rss,
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +0100650 "atom" => \&git_atom,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200651 "search" => \&git_search,
Petr Baudis88ad7292006-10-24 05:15:46 +0200652 "search_help" => \&git_search_help,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200653 "shortlog" => \&git_shortlog,
654 "summary" => \&git_summary,
655 "tag" => \&git_tag,
656 "tags" => \&git_tags,
657 "tree" => \&git_tree,
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +0530658 "snapshot" => \&git_snapshot,
Jakub Narebskica946012006-12-10 13:25:47 +0100659 "object" => \&git_object,
Jakub Narebski77a153f2006-08-22 16:59:20 +0200660 # those below don't need $project
661 "opml" => \&git_opml,
662 "project_list" => \&git_project_list,
Jakub Narebskifc2b2be2006-09-15 04:56:03 +0200663 "project_index" => \&git_project_index,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200664);
665
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200666# finally, we have the hash of allowed extra_options for the commands that
667# allow them
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300668our %allowed_options = (
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200669 "--no-merges" => [ qw(rss atom log shortlog history) ],
670);
671
672# fill %input_params with the CGI parameters. All values except for 'opt'
673# should be single values, but opt can be an array. We should probably
674# build an array of parameters that can be multi-valued, but since for the time
675# being it's only this one, we just single it out
676while (my ($name, $symbol) = each %cgi_param_mapping) {
677 if ($symbol eq 'opt') {
678 $input_params{$name} = [ $cgi->param($symbol) ];
679 } else {
680 $input_params{$name} = $cgi->param($symbol);
681 }
682}
683
684# now read PATH_INFO and update the parameter list for missing parameters
685sub evaluate_path_info {
686 return if defined $input_params{'project'};
687 return if !$path_info;
688 $path_info =~ s,^/+,,;
689 return if !$path_info;
690
691 # find which part of PATH_INFO is project
692 my $project = $path_info;
693 $project =~ s,/+$,,;
694 while ($project && !check_head_link("$projectroot/$project")) {
695 $project =~ s,/*[^/]*$,,;
696 }
697 return unless $project;
698 $input_params{'project'} = $project;
699
700 # do not change any parameters if an action is given using the query string
701 return if $input_params{'action'};
702 $path_info =~ s,^\Q$project\E/*,,;
703
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200704 # next, check if we have an action
705 my $action = $path_info;
706 $action =~ s,/.*$,,;
707 if (exists $actions{$action}) {
708 $path_info =~ s,^$action/*,,;
709 $input_params{'action'} = $action;
710 }
711
712 # list of actions that want hash_base instead of hash, but can have no
713 # pathname (f) parameter
714 my @wants_base = (
715 'tree',
716 'history',
717 );
718
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200719 # we want to catch
720 # [$hash_parent_base[:$file_parent]..]$hash_parent[:$file_name]
721 my ($parentrefname, $parentpathname, $refname, $pathname) =
722 ($path_info =~ /^(?:(.+?)(?::(.+))?\.\.)?(.+?)(?::(.+))?$/);
723
724 # first, analyze the 'current' part
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200725 if (defined $pathname) {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200726 # we got "branch:filename" or "branch:dir/"
727 # we could use git_get_type(branch:pathname), but:
728 # - it needs $git_dir
729 # - it does a git() call
730 # - the convention of terminating directories with a slash
731 # makes it superfluous
732 # - embedding the action in the PATH_INFO would make it even
733 # more superfluous
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200734 $pathname =~ s,^/+,,;
735 if (!$pathname || substr($pathname, -1) eq "/") {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200736 $input_params{'action'} ||= "tree";
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200737 $pathname =~ s,/$,,;
738 } else {
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200739 # the default action depends on whether we had parent info
740 # or not
741 if ($parentrefname) {
742 $input_params{'action'} ||= "blobdiff_plain";
743 } else {
744 $input_params{'action'} ||= "blob_plain";
745 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200746 }
747 $input_params{'hash_base'} ||= $refname;
748 $input_params{'file_name'} ||= $pathname;
749 } elsif (defined $refname) {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200750 # we got "branch". In this case we have to choose if we have to
751 # set hash or hash_base.
752 #
753 # Most of the actions without a pathname only want hash to be
754 # set, except for the ones specified in @wants_base that want
755 # hash_base instead. It should also be noted that hand-crafted
756 # links having 'history' as an action and no pathname or hash
757 # set will fail, but that happens regardless of PATH_INFO.
758 $input_params{'action'} ||= "shortlog";
759 if (grep { $_ eq $input_params{'action'} } @wants_base) {
760 $input_params{'hash_base'} ||= $refname;
761 } else {
762 $input_params{'hash'} ||= $refname;
763 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200764 }
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200765
766 # next, handle the 'parent' part, if present
767 if (defined $parentrefname) {
768 # a missing pathspec defaults to the 'current' filename, allowing e.g.
769 # someproject/blobdiff/oldrev..newrev:/filename
770 if ($parentpathname) {
771 $parentpathname =~ s,^/+,,;
772 $parentpathname =~ s,/$,,;
773 $input_params{'file_parent'} ||= $parentpathname;
774 } else {
775 $input_params{'file_parent'} ||= $input_params{'file_name'};
776 }
777 # we assume that hash_parent_base is wanted if a path was specified,
778 # or if the action wants hash_base instead of hash
779 if (defined $input_params{'file_parent'} ||
780 grep { $_ eq $input_params{'action'} } @wants_base) {
781 $input_params{'hash_parent_base'} ||= $parentrefname;
782 } else {
783 $input_params{'hash_parent'} ||= $parentrefname;
784 }
785 }
Giuseppe Bilotta1ec2fb52008-11-02 10:21:38 +0100786
787 # for the snapshot action, we allow URLs in the form
788 # $project/snapshot/$hash.ext
789 # where .ext determines the snapshot and gets removed from the
790 # passed $refname to provide the $hash.
791 #
792 # To be able to tell that $refname includes the format extension, we
793 # require the following two conditions to be satisfied:
794 # - the hash input parameter MUST have been set from the $refname part
795 # of the URL (i.e. they must be equal)
796 # - the snapshot format MUST NOT have been defined already (e.g. from
797 # CGI parameter sf)
798 # It's also useless to try any matching unless $refname has a dot,
799 # so we check for that too
800 if (defined $input_params{'action'} &&
801 $input_params{'action'} eq 'snapshot' &&
802 defined $refname && index($refname, '.') != -1 &&
803 $refname eq $input_params{'hash'} &&
804 !defined $input_params{'snapshot_format'}) {
805 # We loop over the known snapshot formats, checking for
806 # extensions. Allowed extensions are both the defined suffix
807 # (which includes the initial dot already) and the snapshot
808 # format key itself, with a prepended dot
Holger Weißccb4b532009-03-31 18:16:36 +0200809 while (my ($fmt, $opt) = each %known_snapshot_formats) {
Giuseppe Bilotta1ec2fb52008-11-02 10:21:38 +0100810 my $hash = $refname;
Jakub Narebski095e9142009-05-11 19:42:47 +0200811 unless ($hash =~ s/(\Q$opt->{'suffix'}\E|\Q.$fmt\E)$//) {
812 next;
813 }
814 my $sfx = $1;
Giuseppe Bilotta1ec2fb52008-11-02 10:21:38 +0100815 # a valid suffix was found, so set the snapshot format
816 # and reset the hash parameter
817 $input_params{'snapshot_format'} = $fmt;
818 $input_params{'hash'} = $hash;
819 # we also set the format suffix to the one requested
820 # in the URL: this way a request for e.g. .tgz returns
821 # a .tgz instead of a .tar.gz
822 $known_snapshot_formats{$fmt}{'suffix'} = $sfx;
823 last;
824 }
825 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200826}
827evaluate_path_info();
828
829our $action = $input_params{'action'};
830if (defined $action) {
831 if (!validate_action($action)) {
832 die_error(400, "Invalid action parameter");
833 }
834}
835
836# parameters which are pathnames
837our $project = $input_params{'project'};
838if (defined $project) {
839 if (!validate_project($project)) {
840 undef $project;
841 die_error(404, "No such project");
842 }
843}
844
845our $file_name = $input_params{'file_name'};
846if (defined $file_name) {
847 if (!validate_pathname($file_name)) {
848 die_error(400, "Invalid file parameter");
849 }
850}
851
852our $file_parent = $input_params{'file_parent'};
853if (defined $file_parent) {
854 if (!validate_pathname($file_parent)) {
855 die_error(400, "Invalid file parent parameter");
856 }
857}
858
859# parameters which are refnames
860our $hash = $input_params{'hash'};
861if (defined $hash) {
862 if (!validate_refname($hash)) {
863 die_error(400, "Invalid hash parameter");
864 }
865}
866
867our $hash_parent = $input_params{'hash_parent'};
868if (defined $hash_parent) {
869 if (!validate_refname($hash_parent)) {
870 die_error(400, "Invalid hash parent parameter");
871 }
872}
873
874our $hash_base = $input_params{'hash_base'};
875if (defined $hash_base) {
876 if (!validate_refname($hash_base)) {
877 die_error(400, "Invalid hash base parameter");
878 }
879}
880
881our @extra_options = @{$input_params{'extra_options'}};
882# @extra_options is always defined, since it can only be (currently) set from
883# CGI, and $cgi->param() returns the empty array in array context if the param
884# is not set
885foreach my $opt (@extra_options) {
886 if (not exists $allowed_options{$opt}) {
887 die_error(400, "Invalid option parameter");
888 }
889 if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
890 die_error(400, "Invalid option parameter for this action");
891 }
892}
893
894our $hash_parent_base = $input_params{'hash_parent_base'};
895if (defined $hash_parent_base) {
896 if (!validate_refname($hash_parent_base)) {
897 die_error(400, "Invalid hash parent base parameter");
898 }
899}
900
901# other parameters
902our $page = $input_params{'page'};
903if (defined $page) {
904 if ($page =~ m/[^0-9]/) {
905 die_error(400, "Invalid page parameter");
906 }
907}
908
909our $searchtype = $input_params{'searchtype'};
910if (defined $searchtype) {
911 if ($searchtype =~ m/[^a-z]/) {
912 die_error(400, "Invalid searchtype parameter");
913 }
914}
915
916our $search_use_regexp = $input_params{'search_use_regexp'};
917
918our $searchtext = $input_params{'searchtext'};
919our $search_regexp;
920if (defined $searchtext) {
921 if (length($searchtext) < 2) {
922 die_error(403, "At least two characters are required for search parameter");
923 }
924 $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
925}
926
927# path to the current git repository
928our $git_dir;
929$git_dir = "$projectroot/$project" if $project;
930
Giuseppe Bilotta5e166842008-11-02 10:21:37 +0100931# list of supported snapshot formats
Junio C Hamanoa7c5a282008-11-29 13:02:08 -0800932our @snapshot_fmts = gitweb_get_feature('snapshot');
Giuseppe Bilotta5e166842008-11-02 10:21:37 +0100933@snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
934
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200935# check that the avatar feature is set to a known provider name,
936# and for each provider check if the dependencies are satisfied.
937# if the provider name is invalid or the dependencies are not met,
938# reset $git_avatar to the empty string.
939our ($git_avatar) = gitweb_get_feature('avatar');
940if ($git_avatar eq 'gravatar') {
941 $git_avatar = '' unless (eval { require Digest::MD5; 1; });
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +0200942} elsif ($git_avatar eq 'picon') {
943 # no dependencies
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200944} else {
945 $git_avatar = '';
946}
947
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200948# dispatch
Gerrit Pape7f9778b2007-05-10 07:32:07 +0000949if (!defined $action) {
950 if (defined $hash) {
951 $action = git_get_type($hash);
952 } elsif (defined $hash_base && defined $file_name) {
953 $action = git_get_type("$hash_base:$file_name");
954 } elsif (defined $project) {
955 $action = 'summary';
956 } else {
957 $action = 'project_list';
958 }
Jakub Narebski77a153f2006-08-22 16:59:20 +0200959}
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200960if (!defined($actions{$action})) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200961 die_error(400, "Unknown action");
Kay Sievers09bd7892005-08-07 20:21:23 +0200962}
Jakub Narebskie8bb4b32009-05-11 19:39:43 +0200963if ($action !~ m/^(?:opml|project_list|project_index)$/ &&
Jakub Narebskid04d3d42006-09-19 21:53:22 +0200964 !$project) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200965 die_error(400, "Project needed");
Jakub Narebskid04d3d42006-09-19 21:53:22 +0200966}
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200967$actions{$action}->();
968exit;
Kay Sievers09bd7892005-08-07 20:21:23 +0200969
Jakub Narebski717b8312006-07-31 21:22:15 +0200970## ======================================================================
Martin Waitz06a9d862006-08-16 00:23:50 +0200971## action links
972
Jakub Narebski74fd8722009-05-07 19:11:29 +0200973sub href {
Jakub Narebski498fe002006-08-22 19:05:25 +0200974 my %params = @_;
Jakub Narebskibd5d1e42006-11-19 15:05:21 +0100975 # default is to use -absolute url() i.e. $my_uri
976 my $href = $params{-full} ? $my_url : $my_uri;
Jakub Narebski498fe002006-08-22 19:05:25 +0200977
Jakub Narebskiafa9b622008-02-14 09:22:30 +0100978 $params{'project'} = $project unless exists $params{'project'};
979
Jakub Narebski1cad2832007-11-01 13:06:27 +0100980 if ($params{-replay}) {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200981 while (my ($name, $symbol) = each %cgi_param_mapping) {
Jakub Narebski1cad2832007-11-01 13:06:27 +0100982 if (!exists $params{$name}) {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200983 $params{$name} = $input_params{$name};
Jakub Narebski1cad2832007-11-01 13:06:27 +0100984 }
985 }
986 }
987
Giuseppe Bilotta25b27902008-11-29 13:07:29 -0800988 my $use_pathinfo = gitweb_check_feature('pathinfo');
Giuseppe Bilottafb098a92009-01-02 12:34:40 +0100989 if ($use_pathinfo and defined $params{'project'}) {
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +0200990 # try to put as many parameters as possible in PATH_INFO:
991 # - project name
992 # - action
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +0200993 # - hash_parent or hash_parent_base:/file_parent
Giuseppe Bilotta3550ea72008-10-21 21:34:52 +0200994 # - hash or hash_base:/filename
Giuseppe Bilottac752a0e2008-11-02 10:21:39 +0100995 # - the snapshot_format as an appropriate suffix
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +0200996
997 # When the script is the root DirectoryIndex for the domain,
998 # $href here would be something like http://gitweb.example.com/
999 # Thus, we strip any trailing / from $href, to spare us double
1000 # slashes in the final URL
1001 $href =~ s,/$,,;
1002
1003 # Then add the project name, if present
Giuseppe Bilottafb098a92009-01-02 12:34:40 +01001004 $href .= "/".esc_url($params{'project'});
Martin Waitz9e756902006-10-01 23:57:48 +02001005 delete $params{'project'};
1006
Giuseppe Bilottac752a0e2008-11-02 10:21:39 +01001007 # since we destructively absorb parameters, we keep this
1008 # boolean that remembers if we're handling a snapshot
1009 my $is_snapshot = $params{'action'} eq 'snapshot';
1010
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001011 # Summary just uses the project path URL, any other action is
1012 # added to the URL
1013 if (defined $params{'action'}) {
1014 $href .= "/".esc_url($params{'action'}) unless $params{'action'} eq 'summary';
Martin Waitz9e756902006-10-01 23:57:48 +02001015 delete $params{'action'};
1016 }
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001017
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001018 # Next, we put hash_parent_base:/file_parent..hash_base:/file_name,
1019 # stripping nonexistent or useless pieces
1020 $href .= "/" if ($params{'hash_base'} || $params{'hash_parent_base'}
1021 || $params{'hash_parent'} || $params{'hash'});
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001022 if (defined $params{'hash_base'}) {
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001023 if (defined $params{'hash_parent_base'}) {
1024 $href .= esc_url($params{'hash_parent_base'});
1025 # skip the file_parent if it's the same as the file_name
Giuseppe Bilottab7da7212009-07-31 08:48:49 +02001026 if (defined $params{'file_parent'}) {
1027 if (defined $params{'file_name'} && $params{'file_parent'} eq $params{'file_name'}) {
1028 delete $params{'file_parent'};
1029 } elsif ($params{'file_parent'} !~ /\.\./) {
1030 $href .= ":/".esc_url($params{'file_parent'});
1031 delete $params{'file_parent'};
1032 }
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001033 }
1034 $href .= "..";
1035 delete $params{'hash_parent'};
1036 delete $params{'hash_parent_base'};
1037 } elsif (defined $params{'hash_parent'}) {
1038 $href .= esc_url($params{'hash_parent'}). "..";
1039 delete $params{'hash_parent'};
1040 }
1041
1042 $href .= esc_url($params{'hash_base'});
1043 if (defined $params{'file_name'} && $params{'file_name'} !~ /\.\./) {
Giuseppe Bilotta3550ea72008-10-21 21:34:52 +02001044 $href .= ":/".esc_url($params{'file_name'});
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001045 delete $params{'file_name'};
1046 }
1047 delete $params{'hash'};
1048 delete $params{'hash_base'};
1049 } elsif (defined $params{'hash'}) {
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001050 $href .= esc_url($params{'hash'});
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001051 delete $params{'hash'};
1052 }
Giuseppe Bilottac752a0e2008-11-02 10:21:39 +01001053
1054 # If the action was a snapshot, we can absorb the
1055 # snapshot_format parameter too
1056 if ($is_snapshot) {
1057 my $fmt = $params{'snapshot_format'};
1058 # snapshot_format should always be defined when href()
1059 # is called, but just in case some code forgets, we
1060 # fall back to the default
1061 $fmt ||= $snapshot_fmts[0];
1062 $href .= $known_snapshot_formats{$fmt}{'suffix'};
1063 delete $params{'snapshot_format'};
1064 }
Martin Waitz9e756902006-10-01 23:57:48 +02001065 }
1066
1067 # now encode the parameters explicitly
Jakub Narebski498fe002006-08-22 19:05:25 +02001068 my @result = ();
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001069 for (my $i = 0; $i < @cgi_param_mapping; $i += 2) {
1070 my ($name, $symbol) = ($cgi_param_mapping[$i], $cgi_param_mapping[$i+1]);
Jakub Narebski498fe002006-08-22 19:05:25 +02001071 if (defined $params{$name}) {
Jakub Narebskif22cca42007-07-29 01:04:09 +02001072 if (ref($params{$name}) eq "ARRAY") {
1073 foreach my $par (@{$params{$name}}) {
1074 push @result, $symbol . "=" . esc_param($par);
1075 }
1076 } else {
1077 push @result, $symbol . "=" . esc_param($params{$name});
1078 }
Jakub Narebski498fe002006-08-22 19:05:25 +02001079 }
1080 }
Martin Waitz9e756902006-10-01 23:57:48 +02001081 $href .= "?" . join(';', @result) if scalar @result;
1082
1083 return $href;
Martin Waitz06a9d862006-08-16 00:23:50 +02001084}
1085
1086
1087## ======================================================================
Jakub Narebski717b8312006-07-31 21:22:15 +02001088## validation, quoting/unquoting and escaping
1089
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001090sub validate_action {
1091 my $input = shift || return undef;
1092 return undef unless exists $actions{$input};
1093 return $input;
1094}
1095
1096sub validate_project {
1097 my $input = shift || return undef;
1098 if (!validate_pathname($input) ||
1099 !(-d "$projectroot/$input") ||
Alexander Gavrilovec26f092008-11-06 01:15:56 +03001100 !check_export_ok("$projectroot/$input") ||
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001101 ($strict_export && !project_in_list($input))) {
1102 return undef;
1103 } else {
1104 return $input;
1105 }
1106}
1107
Jakub Narebski24d06932006-09-26 01:57:02 +02001108sub validate_pathname {
1109 my $input = shift || return undef;
Jakub Narebski717b8312006-07-31 21:22:15 +02001110
Jakub Narebski24d06932006-09-26 01:57:02 +02001111 # no '.' or '..' as elements of path, i.e. no '.' nor '..'
1112 # at the beginning, at the end, and between slashes.
1113 # also this catches doubled slashes
1114 if ($input =~ m!(^|/)(|\.|\.\.)(/|$)!) {
1115 return undef;
1116 }
1117 # no null characters
1118 if ($input =~ m!\0!) {
1119 return undef;
1120 }
1121 return $input;
1122}
1123
1124sub validate_refname {
1125 my $input = shift || return undef;
1126
1127 # textual hashes are O.K.
Jakub Narebski717b8312006-07-31 21:22:15 +02001128 if ($input =~ m/^[0-9a-fA-F]{40}$/) {
1129 return $input;
1130 }
Jakub Narebski24d06932006-09-26 01:57:02 +02001131 # it must be correct pathname
1132 $input = validate_pathname($input)
1133 or return undef;
1134 # restrictions on ref name according to git-check-ref-format
1135 if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001136 return undef;
1137 }
1138 return $input;
1139}
1140
Martin Koegler00f429a2007-06-03 17:42:44 +02001141# decode sequences of octets in utf8 into Perl's internal form,
1142# which is utf-8 with utf8 flag set if needed. gitweb writes out
1143# in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning
1144sub to_utf8 {
1145 my $str = shift;
İsmail Dönmeze5d3de52007-12-04 10:55:41 +02001146 if (utf8::valid($str)) {
1147 utf8::decode($str);
1148 return $str;
Martin Koegler00f429a2007-06-03 17:42:44 +02001149 } else {
1150 return decode($fallback_encoding, $str, Encode::FB_DEFAULT);
1151 }
1152}
1153
Kay Sievers232ff552005-11-24 16:56:55 +01001154# quote unsafe chars, but keep the slash, even when it's not
1155# correct, but quoted slashes look too horrible in bookmarks
1156sub esc_param {
Kay Sievers353347b2005-11-14 05:47:18 +01001157 my $str = shift;
Giuseppe Bilotta452e2252009-10-13 21:51:36 +02001158 $str =~ s/([^A-Za-z0-9\-_.~()\/:@ ]+)/CGI::escape($1)/eg;
Kay Sieversa9e60b72005-11-14 15:15:12 +01001159 $str =~ s/ /\+/g;
Kay Sievers353347b2005-11-14 05:47:18 +01001160 return $str;
1161}
1162
Jakub Narebskif93bff82006-09-26 01:58:41 +02001163# quote unsafe chars in whole URL, so some charactrs cannot be quoted
1164sub esc_url {
1165 my $str = shift;
1166 $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
1167 $str =~ s/\+/%2B/g;
1168 $str =~ s/ /\+/g;
1169 return $str;
1170}
1171
Kay Sievers232ff552005-11-24 16:56:55 +01001172# replace invalid utf8 character with SUBSTITUTION sequence
Jakub Narebski74fd8722009-05-07 19:11:29 +02001173sub esc_html {
Kay Sievers40c13812005-11-19 17:41:29 +01001174 my $str = shift;
Jakub Narebski6255ef02006-11-01 14:33:21 +01001175 my %opts = @_;
1176
Martin Koegler00f429a2007-06-03 17:42:44 +02001177 $str = to_utf8($str);
Li Yangc390ae92007-03-06 11:58:56 +08001178 $str = $cgi->escapeHTML($str);
Jakub Narebski6255ef02006-11-01 14:33:21 +01001179 if ($opts{'-nbsp'}) {
1180 $str =~ s/ /&nbsp;/g;
1181 }
Junio C Hamano25ffbb22006-11-08 15:11:10 -08001182 $str =~ s|([[:cntrl:]])|(($1 ne "\t") ? quot_cec($1) : $1)|eg;
Kay Sievers40c13812005-11-19 17:41:29 +01001183 return $str;
1184}
1185
Jakub Narebski391862e2006-11-25 09:43:59 +01001186# quote control characters and escape filename to HTML
1187sub esc_path {
1188 my $str = shift;
1189 my %opts = @_;
1190
Martin Koegler00f429a2007-06-03 17:42:44 +02001191 $str = to_utf8($str);
Li Yangc390ae92007-03-06 11:58:56 +08001192 $str = $cgi->escapeHTML($str);
Jakub Narebski391862e2006-11-25 09:43:59 +01001193 if ($opts{'-nbsp'}) {
1194 $str =~ s/ /&nbsp;/g;
1195 }
1196 $str =~ s|([[:cntrl:]])|quot_cec($1)|eg;
1197 return $str;
1198}
1199
1200# Make control characters "printable", using character escape codes (CEC)
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001201sub quot_cec {
1202 my $cntrl = shift;
Jakub Narebskic84c4832008-02-17 18:48:13 +01001203 my %opts = @_;
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001204 my %es = ( # character escape codes, aka escape sequences
Jakub Narebskic84c4832008-02-17 18:48:13 +01001205 "\t" => '\t', # tab (HT)
1206 "\n" => '\n', # line feed (LF)
1207 "\r" => '\r', # carrige return (CR)
1208 "\f" => '\f', # form feed (FF)
1209 "\b" => '\b', # backspace (BS)
1210 "\a" => '\a', # alarm (bell) (BEL)
1211 "\e" => '\e', # escape (ESC)
1212 "\013" => '\v', # vertical tab (VT)
1213 "\000" => '\0', # nul character (NUL)
1214 );
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001215 my $chr = ( (exists $es{$cntrl})
1216 ? $es{$cntrl}
Petr Baudis25dfd172008-10-01 22:11:54 +02001217 : sprintf('\%2x', ord($cntrl)) );
Jakub Narebskic84c4832008-02-17 18:48:13 +01001218 if ($opts{-nohtml}) {
1219 return $chr;
1220 } else {
1221 return "<span class=\"cntrl\">$chr</span>";
1222 }
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001223}
1224
Jakub Narebski391862e2006-11-25 09:43:59 +01001225# Alternatively use unicode control pictures codepoints,
1226# Unicode "printable representation" (PR)
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001227sub quot_upr {
1228 my $cntrl = shift;
Jakub Narebskic84c4832008-02-17 18:48:13 +01001229 my %opts = @_;
1230
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001231 my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
Jakub Narebskic84c4832008-02-17 18:48:13 +01001232 if ($opts{-nohtml}) {
1233 return $chr;
1234 } else {
1235 return "<span class=\"cntrl\">$chr</span>";
1236 }
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001237}
1238
Kay Sievers232ff552005-11-24 16:56:55 +01001239# git may return quoted and escaped filenames
1240sub unquote {
1241 my $str = shift;
Jakub Narebski403d0902006-11-08 11:48:56 +01001242
1243 sub unq {
1244 my $seq = shift;
1245 my %es = ( # character escape codes, aka escape sequences
1246 't' => "\t", # tab (HT, TAB)
1247 'n' => "\n", # newline (NL)
1248 'r' => "\r", # return (CR)
1249 'f' => "\f", # form feed (FF)
1250 'b' => "\b", # backspace (BS)
1251 'a' => "\a", # alarm (bell) (BEL)
1252 'e' => "\e", # escape (ESC)
1253 'v' => "\013", # vertical tab (VT)
1254 );
1255
1256 if ($seq =~ m/^[0-7]{1,3}$/) {
1257 # octal char sequence
1258 return chr(oct($seq));
1259 } elsif (exists $es{$seq}) {
1260 # C escape sequence, aka character escape code
Jakub Narebskic84c4832008-02-17 18:48:13 +01001261 return $es{$seq};
Jakub Narebski403d0902006-11-08 11:48:56 +01001262 }
1263 # quoted ordinary character
1264 return $seq;
1265 }
1266
Kay Sievers232ff552005-11-24 16:56:55 +01001267 if ($str =~ m/^"(.*)"$/) {
Jakub Narebski403d0902006-11-08 11:48:56 +01001268 # needs unquoting
Kay Sievers232ff552005-11-24 16:56:55 +01001269 $str = $1;
Jakub Narebski403d0902006-11-08 11:48:56 +01001270 $str =~ s/\\([^0-7]|[0-7]{1,3})/unq($1)/eg;
Kay Sievers232ff552005-11-24 16:56:55 +01001271 }
1272 return $str;
1273}
1274
Jakub Narebskif16db172006-08-06 02:08:31 +02001275# escape tabs (convert tabs to spaces)
1276sub untabify {
1277 my $line = shift;
1278
1279 while ((my $pos = index($line, "\t")) != -1) {
1280 if (my $count = (8 - ($pos % 8))) {
1281 my $spaces = ' ' x $count;
1282 $line =~ s/\t/$spaces/;
1283 }
1284 }
1285
1286 return $line;
1287}
1288
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +02001289sub project_in_list {
1290 my $project = shift;
1291 my @list = git_get_projects_list();
1292 return @list && scalar(grep { $_->{'path'} eq $project } @list);
1293}
1294
Jakub Narebski717b8312006-07-31 21:22:15 +02001295## ----------------------------------------------------------------------
1296## HTML aware string manipulation
1297
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001298# Try to chop given string on a word boundary between position
1299# $len and $len+$add_len. If there is no word boundary there,
1300# chop at $len+$add_len. Do not chop if chopped part plus ellipsis
1301# (marking chopped part) would be longer than given string.
Jakub Narebski717b8312006-07-31 21:22:15 +02001302sub chop_str {
1303 my $str = shift;
1304 my $len = shift;
1305 my $add_len = shift || 10;
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001306 my $where = shift || 'right'; # 'left' | 'center' | 'right'
Jakub Narebski717b8312006-07-31 21:22:15 +02001307
Anders Waldenborgdee27752008-05-21 13:44:43 +02001308 # Make sure perl knows it is utf8 encoded so we don't
1309 # cut in the middle of a utf8 multibyte char.
1310 $str = to_utf8($str);
1311
Jakub Narebski717b8312006-07-31 21:22:15 +02001312 # allow only $len chars, but don't cut a word if it would fit in $add_len
1313 # 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 +01001314 # remove chopped character entities entirely
1315
1316 # when chopping in the middle, distribute $len into left and right part
1317 # return early if chopping wouldn't make string shorter
1318 if ($where eq 'center') {
1319 return $str if ($len + 5 >= length($str)); # filler is length 5
1320 $len = int($len/2);
1321 } else {
1322 return $str if ($len + 4 >= length($str)); # filler is length 4
Jakub Narebski717b8312006-07-31 21:22:15 +02001323 }
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001324
1325 # regexps: ending and beginning with word part up to $add_len
1326 my $endre = qr/.{$len}\w{0,$add_len}/;
1327 my $begre = qr/\w{0,$add_len}.{$len}/;
1328
1329 if ($where eq 'left') {
1330 $str =~ m/^(.*?)($begre)$/;
1331 my ($lead, $body) = ($1, $2);
1332 if (length($lead) > 4) {
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001333 $lead = " ...";
1334 }
1335 return "$lead$body";
1336
1337 } elsif ($where eq 'center') {
1338 $str =~ m/^($endre)(.*)$/;
1339 my ($left, $str) = ($1, $2);
1340 $str =~ m/^(.*?)($begre)$/;
1341 my ($mid, $right) = ($1, $2);
1342 if (length($mid) > 5) {
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001343 $mid = " ... ";
1344 }
1345 return "$left$mid$right";
1346
1347 } else {
1348 $str =~ m/^($endre)(.*)$/;
1349 my $body = $1;
1350 my $tail = $2;
1351 if (length($tail) > 4) {
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001352 $tail = "... ";
1353 }
1354 return "$body$tail";
1355 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001356}
1357
David Symondsce58ec92007-10-23 11:31:22 +10001358# takes the same arguments as chop_str, but also wraps a <span> around the
1359# result with a title attribute if it does get chopped. Additionally, the
1360# string is HTML-escaped.
1361sub chop_and_escape_str {
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001362 my ($str) = @_;
David Symondsce58ec92007-10-23 11:31:22 +10001363
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001364 my $chopped = chop_str(@_);
David Symondsce58ec92007-10-23 11:31:22 +10001365 if ($chopped eq $str) {
1366 return esc_html($chopped);
1367 } else {
Jakub Narebski14afe772009-05-22 17:35:46 +02001368 $str =~ s/[[:cntrl:]]/?/g;
Jakub Narebski850b90a2008-02-16 23:07:46 +01001369 return $cgi->span({-title=>$str}, esc_html($chopped));
David Symondsce58ec92007-10-23 11:31:22 +10001370 }
1371}
1372
Jakub Narebski717b8312006-07-31 21:22:15 +02001373## ----------------------------------------------------------------------
1374## functions returning short strings
1375
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00001376# CSS class for given age value (in seconds)
1377sub age_class {
1378 my $age = shift;
1379
Jakub Narebski785cdea2007-05-13 12:39:22 +02001380 if (!defined $age) {
1381 return "noage";
1382 } elsif ($age < 60*60*2) {
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00001383 return "age0";
1384 } elsif ($age < 60*60*24*2) {
1385 return "age1";
1386 } else {
1387 return "age2";
1388 }
1389}
1390
Jakub Narebski717b8312006-07-31 21:22:15 +02001391# convert age in seconds to "nn units ago" string
1392sub age_string {
1393 my $age = shift;
1394 my $age_str;
1395
1396 if ($age > 60*60*24*365*2) {
1397 $age_str = (int $age/60/60/24/365);
1398 $age_str .= " years ago";
1399 } elsif ($age > 60*60*24*(365/12)*2) {
1400 $age_str = int $age/60/60/24/(365/12);
1401 $age_str .= " months ago";
1402 } elsif ($age > 60*60*24*7*2) {
1403 $age_str = int $age/60/60/24/7;
1404 $age_str .= " weeks ago";
1405 } elsif ($age > 60*60*24*2) {
1406 $age_str = int $age/60/60/24;
1407 $age_str .= " days ago";
1408 } elsif ($age > 60*60*2) {
1409 $age_str = int $age/60/60;
1410 $age_str .= " hours ago";
1411 } elsif ($age > 60*2) {
1412 $age_str = int $age/60;
1413 $age_str .= " min ago";
1414 } elsif ($age > 2) {
1415 $age_str = int $age;
1416 $age_str .= " sec ago";
1417 } else {
1418 $age_str .= " right now";
1419 }
1420 return $age_str;
1421}
1422
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001423use constant {
1424 S_IFINVALID => 0030000,
1425 S_IFGITLINK => 0160000,
1426};
1427
1428# submodule/subproject, a commit object reference
Jakub Narebski74fd8722009-05-07 19:11:29 +02001429sub S_ISGITLINK {
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001430 my $mode = shift;
1431
1432 return (($mode & S_IFMT) == S_IFGITLINK)
1433}
1434
Jakub Narebski717b8312006-07-31 21:22:15 +02001435# convert file mode in octal to symbolic file mode string
1436sub mode_str {
1437 my $mode = oct shift;
1438
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001439 if (S_ISGITLINK($mode)) {
1440 return 'm---------';
1441 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001442 return 'drwxr-xr-x';
1443 } elsif (S_ISLNK($mode)) {
1444 return 'lrwxrwxrwx';
1445 } elsif (S_ISREG($mode)) {
1446 # git cares only about the executable bit
1447 if ($mode & S_IXUSR) {
1448 return '-rwxr-xr-x';
1449 } else {
1450 return '-rw-r--r--';
1451 };
1452 } else {
1453 return '----------';
1454 }
1455}
1456
1457# convert file mode in octal to file type string
1458sub file_type {
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02001459 my $mode = shift;
1460
1461 if ($mode !~ m/^[0-7]+$/) {
1462 return $mode;
1463 } else {
1464 $mode = oct $mode;
1465 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001466
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001467 if (S_ISGITLINK($mode)) {
1468 return "submodule";
1469 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001470 return "directory";
1471 } elsif (S_ISLNK($mode)) {
1472 return "symlink";
1473 } elsif (S_ISREG($mode)) {
1474 return "file";
1475 } else {
1476 return "unknown";
1477 }
1478}
1479
Jakub Narebski744d0ac2006-11-08 17:59:41 +01001480# convert file mode in octal to file type description string
1481sub file_type_long {
1482 my $mode = shift;
1483
1484 if ($mode !~ m/^[0-7]+$/) {
1485 return $mode;
1486 } else {
1487 $mode = oct $mode;
1488 }
1489
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001490 if (S_ISGITLINK($mode)) {
1491 return "submodule";
1492 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski744d0ac2006-11-08 17:59:41 +01001493 return "directory";
1494 } elsif (S_ISLNK($mode)) {
1495 return "symlink";
1496 } elsif (S_ISREG($mode)) {
1497 if ($mode & S_IXUSR) {
1498 return "executable";
1499 } else {
1500 return "file";
1501 };
1502 } else {
1503 return "unknown";
1504 }
1505}
1506
1507
Jakub Narebski717b8312006-07-31 21:22:15 +02001508## ----------------------------------------------------------------------
1509## functions returning short HTML fragments, or transforming HTML fragments
Pavel Roskin3dff5372007-02-03 23:49:16 -05001510## which don't belong to other sections
Jakub Narebski717b8312006-07-31 21:22:15 +02001511
Junio C Hamano225932e2006-11-09 00:57:13 -08001512# format line of commit message.
Jakub Narebski717b8312006-07-31 21:22:15 +02001513sub format_log_line_html {
1514 my $line = shift;
1515
Junio C Hamano225932e2006-11-09 00:57:13 -08001516 $line = esc_html($line, -nbsp=>1);
Marcel M. Cary7d233de2009-02-17 19:00:43 -08001517 $line =~ s{\b([0-9a-fA-F]{8,40})\b}{
1518 $cgi->a({-href => href(action=>"object", hash=>$1),
1519 -class => "text"}, $1);
1520 }eg;
1521
Jakub Narebski717b8312006-07-31 21:22:15 +02001522 return $line;
1523}
1524
1525# format marker of refs pointing to given object
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02001526
1527# the destination action is chosen based on object type and current context:
1528# - for annotated tags, we choose the tag view unless it's the current view
1529# already, in which case we go to shortlog view
1530# - for other refs, we keep the current view if we're in history, shortlog or
1531# log view, and select shortlog otherwise
Jakub Narebski847e01f2006-08-14 02:05:47 +02001532sub format_ref_marker {
Jakub Narebski717b8312006-07-31 21:22:15 +02001533 my ($refs, $id) = @_;
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001534 my $markers = '';
Jakub Narebski717b8312006-07-31 21:22:15 +02001535
1536 if (defined $refs->{$id}) {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001537 foreach my $ref (@{$refs->{$id}}) {
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02001538 # this code exploits the fact that non-lightweight tags are the
1539 # only indirect objects, and that they are the only objects for which
1540 # we want to use tag instead of shortlog as action
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001541 my ($type, $name) = qw();
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02001542 my $indirect = ($ref =~ s/\^\{\}$//);
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001543 # e.g. tags/v2.6.11 or heads/next
1544 if ($ref =~ m!^(.*?)s?/(.*)$!) {
1545 $type = $1;
1546 $name = $2;
1547 } else {
1548 $type = "ref";
1549 $name = $ref;
1550 }
1551
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02001552 my $class = $type;
1553 $class .= " indirect" if $indirect;
1554
1555 my $dest_action = "shortlog";
1556
1557 if ($indirect) {
1558 $dest_action = "tag" unless $action eq "tag";
1559 } elsif ($action =~ /^(history|(short)?log)$/) {
1560 $dest_action = $action;
1561 }
1562
1563 my $dest = "";
1564 $dest .= "refs/" unless $ref =~ m!^refs/!;
1565 $dest .= $ref;
1566
1567 my $link = $cgi->a({
1568 -href => href(
1569 action=>$dest_action,
1570 hash=>$dest
1571 )}, $name);
1572
1573 $markers .= " <span class=\"$class\" title=\"$ref\">" .
1574 $link . "</span>";
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001575 }
1576 }
1577
1578 if ($markers) {
1579 return ' <span class="refs">'. $markers . '</span>';
Jakub Narebski717b8312006-07-31 21:22:15 +02001580 } else {
1581 return "";
1582 }
1583}
1584
Jakub Narebski17d07442006-08-14 02:08:27 +02001585# format, perhaps shortened and with markers, title line
1586sub format_subject_html {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02001587 my ($long, $short, $href, $extra) = @_;
Jakub Narebski17d07442006-08-14 02:08:27 +02001588 $extra = '' unless defined($extra);
1589
1590 if (length($short) < length($long)) {
Jakub Narebski14afe772009-05-22 17:35:46 +02001591 $long =~ s/[[:cntrl:]]/?/g;
Jakub Narebski7c278012006-08-22 12:02:48 +02001592 return $cgi->a({-href => $href, -class => "list subject",
Martin Koegler00f429a2007-06-03 17:42:44 +02001593 -title => to_utf8($long)},
Giuseppe Bilotta01b89f02009-08-23 10:28:09 +02001594 esc_html($short)) . $extra;
Jakub Narebski17d07442006-08-14 02:08:27 +02001595 } else {
Jakub Narebski7c278012006-08-22 12:02:48 +02001596 return $cgi->a({-href => $href, -class => "list subject"},
Giuseppe Bilotta01b89f02009-08-23 10:28:09 +02001597 esc_html($long)) . $extra;
Jakub Narebski17d07442006-08-14 02:08:27 +02001598 }
1599}
1600
Giuseppe Bilotta5a371b72009-06-30 00:00:52 +02001601# Rather than recomputing the url for an email multiple times, we cache it
1602# after the first hit. This gives a visible benefit in views where the avatar
1603# for the same email is used repeatedly (e.g. shortlog).
1604# The cache is shared by all avatar engines (currently gravatar only), which
1605# are free to use it as preferred. Since only one avatar engine is used for any
1606# given page, there's no risk for cache conflicts.
1607our %avatar_cache = ();
1608
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +02001609# Compute the picon url for a given email, by using the picon search service over at
1610# http://www.cs.indiana.edu/picons/search.html
1611sub picon_url {
1612 my $email = lc shift;
1613 if (!$avatar_cache{$email}) {
1614 my ($user, $domain) = split('@', $email);
1615 $avatar_cache{$email} =
1616 "http://www.cs.indiana.edu/cgi-pub/kinzler/piconsearch.cgi/" .
1617 "$domain/$user/" .
1618 "users+domains+unknown/up/single";
1619 }
1620 return $avatar_cache{$email};
1621}
1622
Giuseppe Bilotta5a371b72009-06-30 00:00:52 +02001623# Compute the gravatar url for a given email, if it's not in the cache already.
1624# Gravatar stores only the part of the URL before the size, since that's the
1625# one computationally more expensive. This also allows reuse of the cache for
1626# different sizes (for this particular engine).
1627sub gravatar_url {
1628 my $email = lc shift;
1629 my $size = shift;
1630 $avatar_cache{$email} ||=
1631 "http://www.gravatar.com/avatar/" .
1632 Digest::MD5::md5_hex($email) . "?s=";
1633 return $avatar_cache{$email} . $size;
1634}
1635
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02001636# Insert an avatar for the given $email at the given $size if the feature
1637# is enabled.
1638sub git_get_avatar {
1639 my ($email, %opts) = @_;
1640 my $pre_white = ($opts{-pad_before} ? "&nbsp;" : "");
1641 my $post_white = ($opts{-pad_after} ? "&nbsp;" : "");
1642 $opts{-size} ||= 'default';
1643 my $size = $avatar_size{$opts{-size}} || $avatar_size{'default'};
1644 my $url = "";
1645 if ($git_avatar eq 'gravatar') {
Giuseppe Bilotta5a371b72009-06-30 00:00:52 +02001646 $url = gravatar_url($email, $size);
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +02001647 } elsif ($git_avatar eq 'picon') {
1648 $url = picon_url($email);
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02001649 }
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +02001650 # Other providers can be added by extending the if chain, defining $url
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02001651 # as needed. If no variant puts something in $url, we assume avatars
1652 # are completely disabled/unavailable.
1653 if ($url) {
1654 return $pre_white .
1655 "<img width=\"$size\" " .
1656 "class=\"avatar\" " .
1657 "src=\"$url\" " .
Giuseppe Bilotta7d25ef42009-06-30 00:00:54 +02001658 "alt=\"\" " .
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02001659 "/>" . $post_white;
1660 } else {
1661 return "";
1662 }
1663}
1664
Stephen Boyde133d652009-10-15 21:14:59 -07001665sub format_search_author {
1666 my ($author, $searchtype, $displaytext) = @_;
1667 my $have_search = gitweb_check_feature('search');
1668
1669 if ($have_search) {
1670 my $performed = "";
1671 if ($searchtype eq 'author') {
1672 $performed = "authored";
1673 } elsif ($searchtype eq 'committer') {
1674 $performed = "committed";
1675 }
1676
1677 return $cgi->a({-href => href(action=>"search", hash=>$hash,
1678 searchtext=>$author,
1679 searchtype=>$searchtype), class=>"list",
1680 title=>"Search for commits $performed by $author"},
1681 $displaytext);
1682
1683 } else {
1684 return $displaytext;
1685 }
1686}
1687
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02001688# format the author name of the given commit with the given tag
1689# the author name is chopped and escaped according to the other
1690# optional parameters (see chop_str).
1691sub format_author_html {
1692 my $tag = shift;
1693 my $co = shift;
1694 my $author = chop_and_escape_str($co->{'author_name'}, @_);
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02001695 return "<$tag class=\"author\">" .
Stephen Boyde133d652009-10-15 21:14:59 -07001696 format_search_author($co->{'author_name'}, "author",
1697 git_get_avatar($co->{'author_email'}, -pad_after => 1) .
1698 $author) .
1699 "</$tag>";
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02001700}
1701
Jakub Narebski90921742007-06-08 13:27:42 +02001702# format git diff header line, i.e. "diff --(git|combined|cc) ..."
1703sub format_git_diff_header_line {
1704 my $line = shift;
1705 my $diffinfo = shift;
1706 my ($from, $to) = @_;
1707
1708 if ($diffinfo->{'nparents'}) {
1709 # combined diff
1710 $line =~ s!^(diff (.*?) )"?.*$!$1!;
1711 if ($to->{'href'}) {
1712 $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
1713 esc_path($to->{'file'}));
1714 } else { # file was deleted (no href)
1715 $line .= esc_path($to->{'file'});
1716 }
1717 } else {
1718 # "ordinary" diff
1719 $line =~ s!^(diff (.*?) )"?a/.*$!$1!;
1720 if ($from->{'href'}) {
1721 $line .= $cgi->a({-href => $from->{'href'}, -class => "path"},
1722 'a/' . esc_path($from->{'file'}));
1723 } else { # file was added (no href)
1724 $line .= 'a/' . esc_path($from->{'file'});
1725 }
1726 $line .= ' ';
1727 if ($to->{'href'}) {
1728 $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
1729 'b/' . esc_path($to->{'file'}));
1730 } else { # file was deleted
1731 $line .= 'b/' . esc_path($to->{'file'});
1732 }
1733 }
1734
1735 return "<div class=\"diff header\">$line</div>\n";
1736}
1737
1738# format extended diff header line, before patch itself
1739sub format_extended_diff_header_line {
1740 my $line = shift;
1741 my $diffinfo = shift;
1742 my ($from, $to) = @_;
1743
1744 # match <path>
1745 if ($line =~ s!^((copy|rename) from ).*$!$1! && $from->{'href'}) {
1746 $line .= $cgi->a({-href=>$from->{'href'}, -class=>"path"},
1747 esc_path($from->{'file'}));
1748 }
1749 if ($line =~ s!^((copy|rename) to ).*$!$1! && $to->{'href'}) {
1750 $line .= $cgi->a({-href=>$to->{'href'}, -class=>"path"},
1751 esc_path($to->{'file'}));
1752 }
1753 # match single <mode>
1754 if ($line =~ m/\s(\d{6})$/) {
1755 $line .= '<span class="info"> (' .
1756 file_type_long($1) .
1757 ')</span>';
1758 }
1759 # match <hash>
1760 if ($line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
1761 # can match only for combined diff
1762 $line = 'index ';
1763 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
1764 if ($from->{'href'}[$i]) {
1765 $line .= $cgi->a({-href=>$from->{'href'}[$i],
1766 -class=>"hash"},
1767 substr($diffinfo->{'from_id'}[$i],0,7));
1768 } else {
1769 $line .= '0' x 7;
1770 }
1771 # separator
1772 $line .= ',' if ($i < $diffinfo->{'nparents'} - 1);
1773 }
1774 $line .= '..';
1775 if ($to->{'href'}) {
1776 $line .= $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
1777 substr($diffinfo->{'to_id'},0,7));
1778 } else {
1779 $line .= '0' x 7;
1780 }
1781
1782 } elsif ($line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
1783 # can match only for ordinary diff
1784 my ($from_link, $to_link);
1785 if ($from->{'href'}) {
1786 $from_link = $cgi->a({-href=>$from->{'href'}, -class=>"hash"},
1787 substr($diffinfo->{'from_id'},0,7));
1788 } else {
1789 $from_link = '0' x 7;
1790 }
1791 if ($to->{'href'}) {
1792 $to_link = $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
1793 substr($diffinfo->{'to_id'},0,7));
1794 } else {
1795 $to_link = '0' x 7;
1796 }
1797 my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
1798 $line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
1799 }
1800
1801 return $line . "<br/>\n";
1802}
1803
1804# format from-file/to-file diff header
1805sub format_diff_from_to_header {
Jakub Narebski91af4ce2007-06-08 13:32:44 +02001806 my ($from_line, $to_line, $diffinfo, $from, $to, @parents) = @_;
Jakub Narebski90921742007-06-08 13:27:42 +02001807 my $line;
1808 my $result = '';
1809
1810 $line = $from_line;
1811 #assert($line =~ m/^---/) if DEBUG;
Jakub Narebskideaa01a2007-06-08 13:29:49 +02001812 # no extra formatting for "^--- /dev/null"
1813 if (! $diffinfo->{'nparents'}) {
1814 # ordinary (single parent) diff
1815 if ($line =~ m!^--- "?a/!) {
1816 if ($from->{'href'}) {
1817 $line = '--- a/' .
1818 $cgi->a({-href=>$from->{'href'}, -class=>"path"},
1819 esc_path($from->{'file'}));
1820 } else {
1821 $line = '--- a/' .
1822 esc_path($from->{'file'});
1823 }
1824 }
1825 $result .= qq!<div class="diff from_file">$line</div>\n!;
1826
1827 } else {
1828 # combined diff (merge commit)
1829 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
1830 if ($from->{'href'}[$i]) {
1831 $line = '--- ' .
Jakub Narebski91af4ce2007-06-08 13:32:44 +02001832 $cgi->a({-href=>href(action=>"blobdiff",
1833 hash_parent=>$diffinfo->{'from_id'}[$i],
1834 hash_parent_base=>$parents[$i],
1835 file_parent=>$from->{'file'}[$i],
1836 hash=>$diffinfo->{'to_id'},
1837 hash_base=>$hash,
1838 file_name=>$to->{'file'}),
1839 -class=>"path",
1840 -title=>"diff" . ($i+1)},
1841 $i+1) .
1842 '/' .
Jakub Narebskideaa01a2007-06-08 13:29:49 +02001843 $cgi->a({-href=>$from->{'href'}[$i], -class=>"path"},
1844 esc_path($from->{'file'}[$i]));
1845 } else {
1846 $line = '--- /dev/null';
1847 }
1848 $result .= qq!<div class="diff from_file">$line</div>\n!;
Jakub Narebski90921742007-06-08 13:27:42 +02001849 }
1850 }
Jakub Narebski90921742007-06-08 13:27:42 +02001851
1852 $line = $to_line;
1853 #assert($line =~ m/^\+\+\+/) if DEBUG;
1854 # no extra formatting for "^+++ /dev/null"
1855 if ($line =~ m!^\+\+\+ "?b/!) {
1856 if ($to->{'href'}) {
1857 $line = '+++ b/' .
1858 $cgi->a({-href=>$to->{'href'}, -class=>"path"},
1859 esc_path($to->{'file'}));
1860 } else {
1861 $line = '+++ b/' .
1862 esc_path($to->{'file'});
1863 }
1864 }
1865 $result .= qq!<div class="diff to_file">$line</div>\n!;
1866
1867 return $result;
1868}
1869
Jakub Narebskicd030c32007-06-08 13:33:28 +02001870# create note for patch simplified by combined diff
1871sub format_diff_cc_simplified {
1872 my ($diffinfo, @parents) = @_;
1873 my $result = '';
1874
1875 $result .= "<div class=\"diff header\">" .
1876 "diff --cc ";
1877 if (!is_deleted($diffinfo)) {
1878 $result .= $cgi->a({-href => href(action=>"blob",
1879 hash_base=>$hash,
1880 hash=>$diffinfo->{'to_id'},
1881 file_name=>$diffinfo->{'to_file'}),
1882 -class => "path"},
1883 esc_path($diffinfo->{'to_file'}));
1884 } else {
1885 $result .= esc_path($diffinfo->{'to_file'});
1886 }
1887 $result .= "</div>\n" . # class="diff header"
1888 "<div class=\"diff nodifferences\">" .
1889 "Simple merge" .
1890 "</div>\n"; # class="diff nodifferences"
1891
1892 return $result;
1893}
1894
Jakub Narebski90921742007-06-08 13:27:42 +02001895# format patch (diff) line (not to be used for diff headers)
Jakub Narebskieee08902006-08-24 00:15:14 +02001896sub format_diff_line {
1897 my $line = shift;
Jakub Narebski59e3b142006-11-18 23:35:40 +01001898 my ($from, $to) = @_;
Jakub Narebskieee08902006-08-24 00:15:14 +02001899 my $diff_class = "";
1900
1901 chomp $line;
1902
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02001903 if ($from && $to && ref($from->{'href'}) eq "ARRAY") {
1904 # combined diff
1905 my $prefix = substr($line, 0, scalar @{$from->{'href'}});
1906 if ($line =~ m/^\@{3}/) {
1907 $diff_class = " chunk_header";
1908 } elsif ($line =~ m/^\\/) {
1909 $diff_class = " incomplete";
1910 } elsif ($prefix =~ tr/+/+/) {
1911 $diff_class = " add";
1912 } elsif ($prefix =~ tr/-/-/) {
1913 $diff_class = " rem";
1914 }
1915 } else {
1916 # assume ordinary diff
1917 my $char = substr($line, 0, 1);
1918 if ($char eq '+') {
1919 $diff_class = " add";
1920 } elsif ($char eq '-') {
1921 $diff_class = " rem";
1922 } elsif ($char eq '@') {
1923 $diff_class = " chunk_header";
1924 } elsif ($char eq "\\") {
1925 $diff_class = " incomplete";
1926 }
Jakub Narebskieee08902006-08-24 00:15:14 +02001927 }
1928 $line = untabify($line);
Jakub Narebski59e3b142006-11-18 23:35:40 +01001929 if ($from && $to && $line =~ m/^\@{2} /) {
1930 my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
1931 $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
1932
1933 $from_lines = 0 unless defined $from_lines;
1934 $to_lines = 0 unless defined $to_lines;
1935
1936 if ($from->{'href'}) {
1937 $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
1938 -class=>"list"}, $from_text);
1939 }
1940 if ($to->{'href'}) {
1941 $to_text = $cgi->a({-href=>"$to->{'href'}#l$to_start",
1942 -class=>"list"}, $to_text);
1943 }
1944 $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
1945 "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
1946 return "<div class=\"diff$diff_class\">$line</div>\n";
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02001947 } elsif ($from && $to && $line =~ m/^\@{3}/) {
1948 my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
1949 my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
1950
1951 @from_text = split(' ', $ranges);
1952 for (my $i = 0; $i < @from_text; ++$i) {
1953 ($from_start[$i], $from_nlines[$i]) =
1954 (split(',', substr($from_text[$i], 1)), 0);
1955 }
1956
1957 $to_text = pop @from_text;
1958 $to_start = pop @from_start;
1959 $to_nlines = pop @from_nlines;
1960
1961 $line = "<span class=\"chunk_info\">$prefix ";
1962 for (my $i = 0; $i < @from_text; ++$i) {
1963 if ($from->{'href'}[$i]) {
1964 $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
1965 -class=>"list"}, $from_text[$i]);
1966 } else {
1967 $line .= $from_text[$i];
1968 }
1969 $line .= " ";
1970 }
1971 if ($to->{'href'}) {
1972 $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
1973 -class=>"list"}, $to_text);
1974 } else {
1975 $line .= $to_text;
1976 }
1977 $line .= " $prefix</span>" .
1978 "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
1979 return "<div class=\"diff$diff_class\">$line</div>\n";
Jakub Narebski59e3b142006-11-18 23:35:40 +01001980 }
Jakub Narebski6255ef02006-11-01 14:33:21 +01001981 return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02001982}
1983
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001984# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
1985# linked. Pass the hash of the tree/commit to snapshot.
1986sub format_snapshot_links {
1987 my ($hash) = @_;
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001988 my $num_fmts = @snapshot_fmts;
1989 if ($num_fmts > 1) {
1990 # A parenthesized list of links bearing format names.
Jakub Narebskia7817852007-07-22 23:41:20 +02001991 # e.g. "snapshot (_tar.gz_ _zip_)"
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001992 return "snapshot (" . join(' ', map
1993 $cgi->a({
1994 -href => href(
1995 action=>"snapshot",
1996 hash=>$hash,
1997 snapshot_format=>$_
1998 )
1999 }, $known_snapshot_formats{$_}{'display'})
2000 , @snapshot_fmts) . ")";
2001 } elsif ($num_fmts == 1) {
2002 # A single "snapshot" link whose tooltip bears the format name.
Jakub Narebskia7817852007-07-22 23:41:20 +02002003 # i.e. "_snapshot_"
Matt McCutchena3c8ab32007-07-22 01:30:27 +02002004 my ($fmt) = @snapshot_fmts;
Jakub Narebskia7817852007-07-22 23:41:20 +02002005 return
2006 $cgi->a({
Matt McCutchena3c8ab32007-07-22 01:30:27 +02002007 -href => href(
2008 action=>"snapshot",
2009 hash=>$hash,
2010 snapshot_format=>$fmt
2011 ),
2012 -title => "in format: $known_snapshot_formats{$fmt}{'display'}"
2013 }, "snapshot");
2014 } else { # $num_fmts == 0
2015 return undef;
2016 }
2017}
2018
Jakub Narebski35621982008-04-20 22:09:48 +02002019## ......................................................................
2020## functions returning values to be passed, perhaps after some
2021## transformation, to other functions; e.g. returning arguments to href()
2022
2023# returns hash to be passed to href to generate gitweb URL
2024# in -title key it returns description of link
2025sub get_feed_info {
2026 my $format = shift || 'Atom';
2027 my %res = (action => lc($format));
2028
2029 # feed links are possible only for project views
2030 return unless (defined $project);
2031 # some views should link to OPML, or to generic project feed,
2032 # or don't have specific feed yet (so they should use generic)
2033 return if ($action =~ /^(?:tags|heads|forks|tag|search)$/x);
2034
2035 my $branch;
2036 # branches refs uses 'refs/heads/' prefix (fullname) to differentiate
2037 # from tag links; this also makes possible to detect branch links
2038 if ((defined $hash_base && $hash_base =~ m!^refs/heads/(.*)$!) ||
2039 (defined $hash && $hash =~ m!^refs/heads/(.*)$!)) {
2040 $branch = $1;
2041 }
2042 # find log type for feed description (title)
2043 my $type = 'log';
2044 if (defined $file_name) {
2045 $type = "history of $file_name";
2046 $type .= "/" if ($action eq 'tree');
2047 $type .= " on '$branch'" if (defined $branch);
2048 } else {
2049 $type = "log of $branch" if (defined $branch);
2050 }
2051
2052 $res{-title} = $type;
2053 $res{'hash'} = (defined $branch ? "refs/heads/$branch" : undef);
2054 $res{'file_name'} = $file_name;
2055
2056 return %res;
2057}
2058
Jakub Narebski717b8312006-07-31 21:22:15 +02002059## ----------------------------------------------------------------------
2060## git utility subroutines, invoking git commands
2061
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002062# returns path to the core git executable and the --git-dir parameter as list
2063sub git_cmd {
Jakub Narebskiaa7dd052009-09-01 13:39:16 +02002064 $number_of_git_cmds++;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002065 return $GIT, '--git-dir='.$git_dir;
2066}
2067
Lea Wiemann516381d2008-06-17 23:46:35 +02002068# quote the given arguments for passing them to the shell
2069# quote_command("command", "arg 1", "arg with ' and ! characters")
2070# => "'command' 'arg 1' 'arg with '\'' and '\!' characters'"
2071# Try to avoid using this function wherever possible.
2072sub quote_command {
2073 return join(' ',
Jakub Narebski68cedb12009-05-10 02:40:37 +02002074 map { my $a = $_; $a =~ s/(['!])/'\\$1'/g; "'$a'" } @_ );
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002075}
2076
Jakub Narebski717b8312006-07-31 21:22:15 +02002077# get HEAD ref of given project as hash
Jakub Narebski847e01f2006-08-14 02:05:47 +02002078sub git_get_head_hash {
Mark Radab6292752009-11-07 16:13:29 +01002079 return git_get_full_hash(shift, 'HEAD');
2080}
2081
2082sub git_get_full_hash {
2083 return git_get_hash(@_);
2084}
2085
2086sub git_get_short_hash {
2087 return git_get_hash(@_, '--short=7');
2088}
2089
2090sub git_get_hash {
2091 my ($project, $hash, @options) = @_;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002092 my $o_git_dir = $git_dir;
Jakub Narebski717b8312006-07-31 21:22:15 +02002093 my $retval = undef;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002094 $git_dir = "$projectroot/$project";
Mark Radab6292752009-11-07 16:13:29 +01002095 if (open my $fd, '-|', git_cmd(), 'rev-parse',
2096 '--verify', '-q', @options, $hash) {
2097 $retval = <$fd>;
2098 chomp $retval if defined $retval;
Jakub Narebski717b8312006-07-31 21:22:15 +02002099 close $fd;
Jakub Narebski717b8312006-07-31 21:22:15 +02002100 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002101 if (defined $o_git_dir) {
2102 $git_dir = $o_git_dir;
Jakub Narebski717b8312006-07-31 21:22:15 +02002103 }
2104 return $retval;
2105}
2106
2107# get type of given object
2108sub git_get_type {
2109 my $hash = shift;
2110
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002111 open my $fd, "-|", git_cmd(), "cat-file", '-t', $hash or return;
Jakub Narebski717b8312006-07-31 21:22:15 +02002112 my $type = <$fd>;
2113 close $fd or return;
2114 chomp $type;
2115 return $type;
2116}
2117
Jakub Narebskib2019272007-11-03 00:41:19 +01002118# repository configuration
2119our $config_file = '';
2120our %config;
2121
2122# store multiple values for single key as anonymous array reference
2123# single values stored directly in the hash, not as [ <value> ]
2124sub hash_set_multi {
2125 my ($hash, $key, $value) = @_;
2126
2127 if (!exists $hash->{$key}) {
2128 $hash->{$key} = $value;
2129 } elsif (!ref $hash->{$key}) {
2130 $hash->{$key} = [ $hash->{$key}, $value ];
2131 } else {
2132 push @{$hash->{$key}}, $value;
2133 }
2134}
2135
2136# return hash of git project configuration
2137# optionally limited to some section, e.g. 'gitweb'
2138sub git_parse_project_config {
2139 my $section_regexp = shift;
2140 my %config;
2141
2142 local $/ = "\0";
2143
2144 open my $fh, "-|", git_cmd(), "config", '-z', '-l',
2145 or return;
2146
2147 while (my $keyval = <$fh>) {
2148 chomp $keyval;
2149 my ($key, $value) = split(/\n/, $keyval, 2);
2150
2151 hash_set_multi(\%config, $key, $value)
2152 if (!defined $section_regexp || $key =~ /^(?:$section_regexp)\./o);
2153 }
2154 close $fh;
2155
2156 return %config;
2157}
2158
Marcel M. Carydf5d10a2009-02-18 14:09:41 +01002159# convert config value to boolean: 'true' or 'false'
Jakub Narebskib2019272007-11-03 00:41:19 +01002160# no value, number > 0, 'true' and 'yes' values are true
2161# rest of values are treated as false (never as error)
2162sub config_to_bool {
2163 my $val = shift;
2164
Marcel M. Carydf5d10a2009-02-18 14:09:41 +01002165 return 1 if !defined $val; # section.key
2166
Jakub Narebskib2019272007-11-03 00:41:19 +01002167 # strip leading and trailing whitespace
2168 $val =~ s/^\s+//;
2169 $val =~ s/\s+$//;
2170
Marcel M. Carydf5d10a2009-02-18 14:09:41 +01002171 return (($val =~ /^\d+$/ && $val) || # section.key = 1
Jakub Narebskib2019272007-11-03 00:41:19 +01002172 ($val =~ /^(?:true|yes)$/i)); # section.key = true
2173}
2174
2175# convert config value to simple decimal number
2176# an optional value suffix of 'k', 'm', or 'g' will cause the value
2177# to be multiplied by 1024, 1048576, or 1073741824
2178sub config_to_int {
2179 my $val = shift;
2180
2181 # strip leading and trailing whitespace
2182 $val =~ s/^\s+//;
2183 $val =~ s/\s+$//;
2184
2185 if (my ($num, $unit) = ($val =~ /^([0-9]*)([kmg])$/i)) {
2186 $unit = lc($unit);
2187 # unknown unit is treated as 1
2188 return $num * ($unit eq 'g' ? 1073741824 :
2189 $unit eq 'm' ? 1048576 :
2190 $unit eq 'k' ? 1024 : 1);
2191 }
2192 return $val;
2193}
2194
2195# convert config value to array reference, if needed
2196sub config_to_multi {
2197 my $val = shift;
2198
Jakub Narebskid76a5852007-12-20 10:48:09 +01002199 return ref($val) ? $val : (defined($val) ? [ $val ] : []);
Jakub Narebskib2019272007-11-03 00:41:19 +01002200}
2201
Jakub Narebski717b8312006-07-31 21:22:15 +02002202sub git_get_project_config {
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05302203 my ($key, $type) = @_;
Jakub Narebski717b8312006-07-31 21:22:15 +02002204
Jakub Narebskib2019272007-11-03 00:41:19 +01002205 # key sanity check
Jakub Narebski717b8312006-07-31 21:22:15 +02002206 return unless ($key);
2207 $key =~ s/^gitweb\.//;
2208 return if ($key =~ m/\W/);
2209
Jakub Narebskib2019272007-11-03 00:41:19 +01002210 # type sanity check
2211 if (defined $type) {
2212 $type =~ s/^--//;
2213 $type = undef
2214 unless ($type eq 'bool' || $type eq 'int');
2215 }
2216
2217 # get config
2218 if (!defined $config_file ||
2219 $config_file ne "$git_dir/config") {
2220 %config = git_parse_project_config('gitweb');
2221 $config_file = "$git_dir/config";
2222 }
2223
Marcel M. Carydf5d10a2009-02-18 14:09:41 +01002224 # check if config variable (key) exists
2225 return unless exists $config{"gitweb.$key"};
2226
Jakub Narebskib2019272007-11-03 00:41:19 +01002227 # ensure given type
2228 if (!defined $type) {
2229 return $config{"gitweb.$key"};
2230 } elsif ($type eq 'bool') {
2231 # backward compatibility: 'git config --bool' returns true/false
2232 return config_to_bool($config{"gitweb.$key"}) ? 'true' : 'false';
2233 } elsif ($type eq 'int') {
2234 return config_to_int($config{"gitweb.$key"});
2235 }
2236 return $config{"gitweb.$key"};
Jakub Narebski717b8312006-07-31 21:22:15 +02002237}
2238
Jakub Narebski717b8312006-07-31 21:22:15 +02002239# get hash of given path at given ref
2240sub git_get_hash_by_path {
2241 my $base = shift;
2242 my $path = shift || return undef;
Jakub Narebski1d782b02006-09-21 18:09:12 +02002243 my $type = shift;
Jakub Narebski717b8312006-07-31 21:22:15 +02002244
Jakub Narebski4b02f482006-09-26 01:54:24 +02002245 $path =~ s,/+$,,;
Jakub Narebski717b8312006-07-31 21:22:15 +02002246
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002247 open my $fd, "-|", git_cmd(), "ls-tree", $base, "--", $path
Lea Wiemann074afaa2008-06-19 22:03:21 +02002248 or die_error(500, "Open git-ls-tree failed");
Jakub Narebski717b8312006-07-31 21:22:15 +02002249 my $line = <$fd>;
2250 close $fd or return undef;
2251
Jakub Narebski198a2a82007-05-12 21:16:34 +02002252 if (!defined $line) {
2253 # there is no tree or hash given by $path at $base
2254 return undef;
2255 }
2256
Jakub Narebski717b8312006-07-31 21:22:15 +02002257 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
Jakub Narebski8b4b94c2006-10-30 22:25:11 +01002258 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/;
Jakub Narebski1d782b02006-09-21 18:09:12 +02002259 if (defined $type && $type ne $2) {
2260 # type doesn't match
2261 return undef;
2262 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002263 return $3;
2264}
2265
Jakub Narebskied224de2007-05-07 01:10:04 +02002266# get path of entry with given hash at given tree-ish (ref)
2267# used to get 'from' filename for combined diff (merge commit) for renames
2268sub git_get_path_by_hash {
2269 my $base = shift || return;
2270 my $hash = shift || return;
2271
2272 local $/ = "\0";
2273
2274 open my $fd, "-|", git_cmd(), "ls-tree", '-r', '-t', '-z', $base
2275 or return undef;
2276 while (my $line = <$fd>) {
2277 chomp $line;
2278
2279 #'040000 tree 595596a6a9117ddba9fe379b6b012b558bac8423 gitweb'
2280 #'100644 blob e02e90f0429be0d2a69b76571101f20b8f75530f gitweb/README'
2281 if ($line =~ m/(?:[0-9]+) (?:.+) $hash\t(.+)$/) {
2282 close $fd;
2283 return $1;
2284 }
2285 }
2286 close $fd;
2287 return undef;
2288}
2289
Jakub Narebski717b8312006-07-31 21:22:15 +02002290## ......................................................................
2291## git utility functions, directly accessing git repository
2292
Jakub Narebski847e01f2006-08-14 02:05:47 +02002293sub git_get_project_description {
Jakub Narebski717b8312006-07-31 21:22:15 +02002294 my $path = shift;
2295
Jakub Narebski0e121a22007-11-03 00:41:20 +01002296 $git_dir = "$projectroot/$path";
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02002297 open my $fd, '<', "$git_dir/description"
Jakub Narebski0e121a22007-11-03 00:41:20 +01002298 or return git_get_project_config('description');
Jakub Narebski717b8312006-07-31 21:22:15 +02002299 my $descr = <$fd>;
2300 close $fd;
Junio C Hamano2eb54ef2007-05-16 21:04:16 -07002301 if (defined $descr) {
2302 chomp $descr;
2303 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002304 return $descr;
2305}
2306
Petr Baudisaed93de2008-10-02 17:13:02 +02002307sub git_get_project_ctags {
2308 my $path = shift;
2309 my $ctags = {};
2310
2311 $git_dir = "$projectroot/$path";
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02002312 opendir my $dh, "$git_dir/ctags"
2313 or return $ctags;
2314 foreach (grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh)) {
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02002315 open my $ct, '<', $_ or next;
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02002316 my $val = <$ct>;
Petr Baudisaed93de2008-10-02 17:13:02 +02002317 chomp $val;
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02002318 close $ct;
Petr Baudisaed93de2008-10-02 17:13:02 +02002319 my $ctag = $_; $ctag =~ s#.*/##;
2320 $ctags->{$ctag} = $val;
2321 }
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02002322 closedir $dh;
Petr Baudisaed93de2008-10-02 17:13:02 +02002323 $ctags;
2324}
2325
2326sub git_populate_project_tagcloud {
2327 my $ctags = shift;
2328
2329 # First, merge different-cased tags; tags vote on casing
2330 my %ctags_lc;
2331 foreach (keys %$ctags) {
2332 $ctags_lc{lc $_}->{count} += $ctags->{$_};
2333 if (not $ctags_lc{lc $_}->{topcount}
2334 or $ctags_lc{lc $_}->{topcount} < $ctags->{$_}) {
2335 $ctags_lc{lc $_}->{topcount} = $ctags->{$_};
2336 $ctags_lc{lc $_}->{topname} = $_;
2337 }
2338 }
2339
2340 my $cloud;
2341 if (eval { require HTML::TagCloud; 1; }) {
2342 $cloud = HTML::TagCloud->new;
2343 foreach (sort keys %ctags_lc) {
2344 # Pad the title with spaces so that the cloud looks
2345 # less crammed.
2346 my $title = $ctags_lc{$_}->{topname};
2347 $title =~ s/ /&nbsp;/g;
2348 $title =~ s/^/&nbsp;/g;
2349 $title =~ s/$/&nbsp;/g;
2350 $cloud->add($title, $home_link."?by_tag=".$_, $ctags_lc{$_}->{count});
2351 }
2352 } else {
2353 $cloud = \%ctags_lc;
2354 }
2355 $cloud;
2356}
2357
2358sub git_show_project_tagcloud {
2359 my ($cloud, $count) = @_;
2360 print STDERR ref($cloud)."..\n";
2361 if (ref $cloud eq 'HTML::TagCloud') {
2362 return $cloud->html_and_css($count);
2363 } else {
2364 my @tags = sort { $cloud->{$a}->{count} <=> $cloud->{$b}->{count} } keys %$cloud;
2365 return '<p align="center">' . join (', ', map {
2366 "<a href=\"$home_link?by_tag=$_\">$cloud->{$_}->{topname}</a>"
2367 } splice(@tags, 0, $count)) . '</p>';
2368 }
2369}
2370
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02002371sub git_get_project_url_list {
2372 my $path = shift;
2373
Jakub Narebski0e121a22007-11-03 00:41:20 +01002374 $git_dir = "$projectroot/$path";
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02002375 open my $fd, '<', "$git_dir/cloneurl"
Jakub Narebski0e121a22007-11-03 00:41:20 +01002376 or return wantarray ?
2377 @{ config_to_multi(git_get_project_config('url')) } :
2378 config_to_multi(git_get_project_config('url'));
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02002379 my @git_project_url_list = map { chomp; $_ } <$fd>;
2380 close $fd;
2381
2382 return wantarray ? @git_project_url_list : \@git_project_url_list;
2383}
2384
Jakub Narebski847e01f2006-08-14 02:05:47 +02002385sub git_get_projects_list {
Petr Baudise30496d2006-10-24 05:33:17 +02002386 my ($filter) = @_;
Jakub Narebski717b8312006-07-31 21:22:15 +02002387 my @list;
2388
Petr Baudise30496d2006-10-24 05:33:17 +02002389 $filter ||= '';
2390 $filter =~ s/\.git$//;
2391
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08002392 my $check_forks = gitweb_check_feature('forks');
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002393
Jakub Narebski717b8312006-07-31 21:22:15 +02002394 if (-d $projects_list) {
2395 # search in directory
Petr Baudise30496d2006-10-24 05:33:17 +02002396 my $dir = $projects_list . ($filter ? "/$filter" : '');
Aneesh Kumar K.V6768d6b2006-11-03 10:41:45 +05302397 # remove the trailing "/"
2398 $dir =~ s!/+$!!;
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002399 my $pfxlen = length("$dir");
Luke Luca5e9492007-10-16 20:45:25 -07002400 my $pfxdepth = ($dir =~ tr!/!!);
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002401
2402 File::Find::find({
2403 follow_fast => 1, # follow symbolic links
Junio C Hamanod20602e2007-07-27 01:23:03 -07002404 follow_skip => 2, # ignore duplicates
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002405 dangling_symlinks => 0, # ignore dangling symlinks, silently
2406 wanted => sub {
2407 # skip project-list toplevel, if we get it.
2408 return if (m!^[/.]$!);
2409 # only directories can be git repositories
2410 return unless (-d $_);
Luke Luca5e9492007-10-16 20:45:25 -07002411 # don't traverse too deep (Find is super slow on os x)
2412 if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) {
2413 $File::Find::prune = 1;
2414 return;
2415 }
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002416
2417 my $subdir = substr($File::Find::name, $pfxlen + 1);
2418 # we check related file in $projectroot
Devin Doucettefb3bb3d2008-12-27 02:39:31 -07002419 my $path = ($filter ? "$filter/" : '') . $subdir;
2420 if (check_export_ok("$projectroot/$path")) {
2421 push @list, { path => $path };
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002422 $File::Find::prune = 1;
2423 }
2424 },
2425 }, "$dir");
2426
Jakub Narebski717b8312006-07-31 21:22:15 +02002427 } elsif (-f $projects_list) {
2428 # read from file(url-encoded):
2429 # 'git%2Fgit.git Linus+Torvalds'
2430 # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
2431 # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002432 my %paths;
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02002433 open my $fd, '<', $projects_list or return;
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002434 PROJECT:
Jakub Narebski717b8312006-07-31 21:22:15 +02002435 while (my $line = <$fd>) {
2436 chomp $line;
2437 my ($path, $owner) = split ' ', $line;
2438 $path = unescape($path);
2439 $owner = unescape($owner);
2440 if (!defined $path) {
2441 next;
2442 }
Junio C Hamano83ee94c2006-11-07 22:37:17 -08002443 if ($filter ne '') {
2444 # looking for forks;
2445 my $pfx = substr($path, 0, length($filter));
2446 if ($pfx ne $filter) {
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002447 next PROJECT;
Junio C Hamano83ee94c2006-11-07 22:37:17 -08002448 }
2449 my $sfx = substr($path, length($filter));
2450 if ($sfx !~ /^\/.*\.git$/) {
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002451 next PROJECT;
2452 }
2453 } elsif ($check_forks) {
2454 PATH:
2455 foreach my $filter (keys %paths) {
2456 # looking for forks;
2457 my $pfx = substr($path, 0, length($filter));
2458 if ($pfx ne $filter) {
2459 next PATH;
2460 }
2461 my $sfx = substr($path, length($filter));
2462 if ($sfx !~ /^\/.*\.git$/) {
2463 next PATH;
2464 }
2465 # is a fork, don't include it in
2466 # the list
2467 next PROJECT;
Junio C Hamano83ee94c2006-11-07 22:37:17 -08002468 }
2469 }
Junio C Hamano2172ce42006-10-03 02:30:47 -07002470 if (check_export_ok("$projectroot/$path")) {
Jakub Narebski717b8312006-07-31 21:22:15 +02002471 my $pr = {
2472 path => $path,
Martin Koegler00f429a2007-06-03 17:42:44 +02002473 owner => to_utf8($owner),
Jakub Narebski717b8312006-07-31 21:22:15 +02002474 };
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002475 push @list, $pr;
2476 (my $forks_path = $path) =~ s/\.git$//;
2477 $paths{$forks_path}++;
Jakub Narebski717b8312006-07-31 21:22:15 +02002478 }
2479 }
2480 close $fd;
2481 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002482 return @list;
2483}
2484
Junio C Hamano47852452007-07-03 22:10:42 -07002485our $gitweb_project_owner = undef;
2486sub git_get_project_list_from_file {
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002487
Junio C Hamano47852452007-07-03 22:10:42 -07002488 return if (defined $gitweb_project_owner);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002489
Junio C Hamano47852452007-07-03 22:10:42 -07002490 $gitweb_project_owner = {};
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002491 # read from file (url-encoded):
2492 # 'git%2Fgit.git Linus+Torvalds'
2493 # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
2494 # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
2495 if (-f $projects_list) {
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02002496 open(my $fd, '<', $projects_list);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002497 while (my $line = <$fd>) {
2498 chomp $line;
2499 my ($pr, $ow) = split ' ', $line;
2500 $pr = unescape($pr);
2501 $ow = unescape($ow);
Junio C Hamano47852452007-07-03 22:10:42 -07002502 $gitweb_project_owner->{$pr} = to_utf8($ow);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002503 }
2504 close $fd;
2505 }
Junio C Hamano47852452007-07-03 22:10:42 -07002506}
2507
2508sub git_get_project_owner {
2509 my $project = shift;
2510 my $owner;
2511
2512 return undef unless $project;
Bruno Ribasb59012e2008-02-08 14:38:04 -02002513 $git_dir = "$projectroot/$project";
Junio C Hamano47852452007-07-03 22:10:42 -07002514
2515 if (!defined $gitweb_project_owner) {
2516 git_get_project_list_from_file();
2517 }
2518
2519 if (exists $gitweb_project_owner->{$project}) {
2520 $owner = $gitweb_project_owner->{$project};
2521 }
Bruno Ribasb59012e2008-02-08 14:38:04 -02002522 if (!defined $owner){
2523 $owner = git_get_project_config('owner');
2524 }
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002525 if (!defined $owner) {
Bruno Ribasb59012e2008-02-08 14:38:04 -02002526 $owner = get_file_owner("$git_dir");
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002527 }
2528
2529 return $owner;
2530}
2531
Jakub Narebskic60c56c2006-10-28 19:43:40 +02002532sub git_get_last_activity {
2533 my ($path) = @_;
2534 my $fd;
2535
2536 $git_dir = "$projectroot/$path";
2537 open($fd, "-|", git_cmd(), 'for-each-ref',
Robert Fitzsimons0ff5ec72006-12-22 19:38:13 +00002538 '--format=%(committer)',
Jakub Narebskic60c56c2006-10-28 19:43:40 +02002539 '--sort=-committerdate',
Robert Fitzsimons0ff5ec72006-12-22 19:38:13 +00002540 '--count=1',
Jakub Narebskic60c56c2006-10-28 19:43:40 +02002541 'refs/heads') or return;
2542 my $most_recent = <$fd>;
2543 close $fd or return;
Jakub Narebski785cdea2007-05-13 12:39:22 +02002544 if (defined $most_recent &&
2545 $most_recent =~ / (\d+) [-+][01]\d\d\d$/) {
Jakub Narebskic60c56c2006-10-28 19:43:40 +02002546 my $timestamp = $1;
2547 my $age = time - $timestamp;
2548 return ($age, age_string($age));
2549 }
Matt McCutchenc9563952007-06-28 18:15:22 -04002550 return (undef, undef);
Jakub Narebskic60c56c2006-10-28 19:43:40 +02002551}
2552
Jakub Narebski847e01f2006-08-14 02:05:47 +02002553sub git_get_references {
Jakub Narebski717b8312006-07-31 21:22:15 +02002554 my $type = shift || "";
2555 my %refs;
Jakub Narebski28b9d9f2006-11-25 11:32:08 +01002556 # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
2557 # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
2558 open my $fd, "-|", git_cmd(), "show-ref", "--dereference",
2559 ($type ? ("--", "refs/$type") : ()) # use -- <pattern> if $type
Jakub Narebski9704d752006-09-19 14:31:49 +02002560 or return;
Jakub Narebskid294e1c2006-08-14 02:14:20 +02002561
Jakub Narebski717b8312006-07-31 21:22:15 +02002562 while (my $line = <$fd>) {
2563 chomp $line;
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02002564 if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type.*)$!) {
Jakub Narebski717b8312006-07-31 21:22:15 +02002565 if (defined $refs{$1}) {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02002566 push @{$refs{$1}}, $2;
Jakub Narebski717b8312006-07-31 21:22:15 +02002567 } else {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02002568 $refs{$1} = [ $2 ];
Jakub Narebski717b8312006-07-31 21:22:15 +02002569 }
2570 }
2571 }
2572 close $fd or return;
2573 return \%refs;
2574}
2575
Jakub Narebski56a322f2006-08-24 19:41:23 +02002576sub git_get_rev_name_tags {
2577 my $hash = shift || return undef;
2578
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002579 open my $fd, "-|", git_cmd(), "name-rev", "--tags", $hash
Jakub Narebski56a322f2006-08-24 19:41:23 +02002580 or return;
2581 my $name_rev = <$fd>;
2582 close $fd;
2583
2584 if ($name_rev =~ m|^$hash tags/(.*)$|) {
2585 return $1;
2586 } else {
2587 # catches also '$hash undefined' output
2588 return undef;
2589 }
2590}
2591
Jakub Narebski717b8312006-07-31 21:22:15 +02002592## ----------------------------------------------------------------------
2593## parse to hash functions
2594
Jakub Narebski847e01f2006-08-14 02:05:47 +02002595sub parse_date {
Jakub Narebski717b8312006-07-31 21:22:15 +02002596 my $epoch = shift;
2597 my $tz = shift || "-0000";
2598
2599 my %date;
2600 my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
2601 my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
2602 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
2603 $date{'hour'} = $hour;
2604 $date{'minute'} = $min;
2605 $date{'mday'} = $mday;
2606 $date{'day'} = $days[$wday];
2607 $date{'month'} = $months[$mon];
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01002608 $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
2609 $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
Jakub Narebski952c65f2006-08-22 16:52:50 +02002610 $date{'mday-time'} = sprintf "%d %s %02d:%02d",
2611 $mday, $months[$mon], $hour ,$min;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01002612 $date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
Vincent Zanottia62d6d82007-11-10 19:55:27 +01002613 1900+$year, 1+$mon, $mday, $hour ,$min, $sec;
Jakub Narebski717b8312006-07-31 21:22:15 +02002614
2615 $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
2616 my $local = $epoch + ((int $1 + ($2/60)) * 3600);
2617 ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
2618 $date{'hour_local'} = $hour;
2619 $date{'minute_local'} = $min;
2620 $date{'tz_local'} = $tz;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01002621 $date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
2622 1900+$year, $mon+1, $mday,
2623 $hour, $min, $sec, $tz);
Jakub Narebski717b8312006-07-31 21:22:15 +02002624 return %date;
2625}
2626
Jakub Narebski847e01f2006-08-14 02:05:47 +02002627sub parse_tag {
Jakub Narebski717b8312006-07-31 21:22:15 +02002628 my $tag_id = shift;
2629 my %tag;
2630 my @comment;
2631
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002632 open my $fd, "-|", git_cmd(), "cat-file", "tag", $tag_id or return;
Jakub Narebski717b8312006-07-31 21:22:15 +02002633 $tag{'id'} = $tag_id;
2634 while (my $line = <$fd>) {
2635 chomp $line;
2636 if ($line =~ m/^object ([0-9a-fA-F]{40})$/) {
2637 $tag{'object'} = $1;
2638 } elsif ($line =~ m/^type (.+)$/) {
2639 $tag{'type'} = $1;
2640 } elsif ($line =~ m/^tag (.+)$/) {
2641 $tag{'name'} = $1;
2642 } elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
2643 $tag{'author'} = $1;
Giuseppe Bilottaba924732009-06-30 00:00:50 +02002644 $tag{'author_epoch'} = $2;
2645 $tag{'author_tz'} = $3;
2646 if ($tag{'author'} =~ m/^([^<]+) <([^>]*)>/) {
2647 $tag{'author_name'} = $1;
2648 $tag{'author_email'} = $2;
2649 } else {
2650 $tag{'author_name'} = $tag{'author'};
2651 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002652 } elsif ($line =~ m/--BEGIN/) {
2653 push @comment, $line;
2654 last;
2655 } elsif ($line eq "") {
2656 last;
2657 }
2658 }
2659 push @comment, <$fd>;
2660 $tag{'comment'} = \@comment;
2661 close $fd or return;
2662 if (!defined $tag{'name'}) {
2663 return
2664 };
2665 return %tag
2666}
2667
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002668sub parse_commit_text {
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002669 my ($commit_text, $withparents) = @_;
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002670 my @commit_lines = split '\n', $commit_text;
Jakub Narebski717b8312006-07-31 21:22:15 +02002671 my %co;
2672
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002673 pop @commit_lines; # Remove '\0'
2674
Jakub Narebski198a2a82007-05-12 21:16:34 +02002675 if (! @commit_lines) {
2676 return;
2677 }
2678
Jakub Narebski717b8312006-07-31 21:22:15 +02002679 my $header = shift @commit_lines;
Jakub Narebski198a2a82007-05-12 21:16:34 +02002680 if ($header !~ m/^[0-9a-fA-F]{40}/) {
Jakub Narebski717b8312006-07-31 21:22:15 +02002681 return;
2682 }
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002683 ($co{'id'}, my @parents) = split ' ', $header;
Jakub Narebski717b8312006-07-31 21:22:15 +02002684 while (my $line = shift @commit_lines) {
2685 last if $line eq "\n";
2686 if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
2687 $co{'tree'} = $1;
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002688 } elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) {
Robert Fitzsimons208b2df2006-12-24 14:31:43 +00002689 push @parents, $1;
Jakub Narebski717b8312006-07-31 21:22:15 +02002690 } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
Zoltán Füzesi5ed5bbc2009-08-02 09:42:24 +02002691 $co{'author'} = to_utf8($1);
Jakub Narebski717b8312006-07-31 21:22:15 +02002692 $co{'author_epoch'} = $2;
2693 $co{'author_tz'} = $3;
Jakub Narebskiba00b8c2006-11-25 15:54:32 +01002694 if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
2695 $co{'author_name'} = $1;
2696 $co{'author_email'} = $2;
Jakub Narebski717b8312006-07-31 21:22:15 +02002697 } else {
2698 $co{'author_name'} = $co{'author'};
2699 }
2700 } elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
Zoltán Füzesi5ed5bbc2009-08-02 09:42:24 +02002701 $co{'committer'} = to_utf8($1);
Jakub Narebski717b8312006-07-31 21:22:15 +02002702 $co{'committer_epoch'} = $2;
2703 $co{'committer_tz'} = $3;
Jakub Narebskiba00b8c2006-11-25 15:54:32 +01002704 if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
2705 $co{'committer_name'} = $1;
2706 $co{'committer_email'} = $2;
2707 } else {
2708 $co{'committer_name'} = $co{'committer'};
2709 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002710 }
2711 }
2712 if (!defined $co{'tree'}) {
2713 return;
2714 };
Robert Fitzsimons208b2df2006-12-24 14:31:43 +00002715 $co{'parents'} = \@parents;
2716 $co{'parent'} = $parents[0];
Jakub Narebski717b8312006-07-31 21:22:15 +02002717
2718 foreach my $title (@commit_lines) {
2719 $title =~ s/^ //;
2720 if ($title ne "") {
2721 $co{'title'} = chop_str($title, 80, 5);
2722 # remove leading stuff of merges to make the interesting part visible
2723 if (length($title) > 50) {
2724 $title =~ s/^Automatic //;
2725 $title =~ s/^merge (of|with) /Merge ... /i;
2726 if (length($title) > 50) {
2727 $title =~ s/(http|rsync):\/\///;
2728 }
2729 if (length($title) > 50) {
2730 $title =~ s/(master|www|rsync)\.//;
2731 }
2732 if (length($title) > 50) {
2733 $title =~ s/kernel.org:?//;
2734 }
2735 if (length($title) > 50) {
2736 $title =~ s/\/pub\/scm//;
2737 }
2738 }
2739 $co{'title_short'} = chop_str($title, 50, 5);
2740 last;
2741 }
2742 }
Joey Hess53c39672008-09-05 14:26:29 -04002743 if (! defined $co{'title'} || $co{'title'} eq "") {
Petr Baudis7e0fe5c2006-10-06 18:55:04 +02002744 $co{'title'} = $co{'title_short'} = '(no commit message)';
2745 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002746 # remove added spaces
2747 foreach my $line (@commit_lines) {
2748 $line =~ s/^ //;
2749 }
2750 $co{'comment'} = \@commit_lines;
2751
2752 my $age = time - $co{'committer_epoch'};
2753 $co{'age'} = $age;
2754 $co{'age_string'} = age_string($age);
2755 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'});
2756 if ($age > 60*60*24*7*2) {
2757 $co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
2758 $co{'age_string_age'} = $co{'age_string'};
2759 } else {
2760 $co{'age_string_date'} = $co{'age_string'};
2761 $co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
2762 }
2763 return %co;
2764}
2765
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002766sub parse_commit {
2767 my ($commit_id) = @_;
2768 my %co;
2769
2770 local $/ = "\0";
2771
2772 open my $fd, "-|", git_cmd(), "rev-list",
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002773 "--parents",
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002774 "--header",
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002775 "--max-count=1",
2776 $commit_id,
2777 "--",
Lea Wiemann074afaa2008-06-19 22:03:21 +02002778 or die_error(500, "Open git-rev-list failed");
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002779 %co = parse_commit_text(<$fd>, 1);
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002780 close $fd;
2781
2782 return %co;
2783}
2784
2785sub parse_commits {
Jakub Narebski311e5522008-02-26 13:22:06 +01002786 my ($commit_id, $maxcount, $skip, $filename, @args) = @_;
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002787 my @cos;
2788
2789 $maxcount ||= 1;
2790 $skip ||= 0;
2791
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002792 local $/ = "\0";
2793
2794 open my $fd, "-|", git_cmd(), "rev-list",
2795 "--header",
Jakub Narebski311e5522008-02-26 13:22:06 +01002796 @args,
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002797 ("--max-count=" . $maxcount),
Robert Fitzsimonsf47efbb2006-12-24 14:31:49 +00002798 ("--skip=" . $skip),
Miklos Vajna868bc062007-07-12 20:39:27 +02002799 @extra_options,
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002800 $commit_id,
2801 "--",
2802 ($filename ? ($filename) : ())
Lea Wiemann074afaa2008-06-19 22:03:21 +02002803 or die_error(500, "Open git-rev-list failed");
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002804 while (my $line = <$fd>) {
2805 my %co = parse_commit_text($line);
2806 push @cos, \%co;
2807 }
2808 close $fd;
2809
2810 return wantarray ? @cos : \@cos;
2811}
2812
Jakub Narebskie8e41a92006-08-21 23:08:52 +02002813# parse line of git-diff-tree "raw" output
Jakub Narebski740e67f2006-08-21 23:07:00 +02002814sub parse_difftree_raw_line {
2815 my $line = shift;
2816 my %res;
2817
2818 # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c'
2819 # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c'
2820 if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) {
2821 $res{'from_mode'} = $1;
2822 $res{'to_mode'} = $2;
2823 $res{'from_id'} = $3;
2824 $res{'to_id'} = $4;
Jakub Narebski4ed4a342008-04-05 21:13:24 +01002825 $res{'status'} = $5;
Jakub Narebski740e67f2006-08-21 23:07:00 +02002826 $res{'similarity'} = $6;
2827 if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied
Jakub Narebskie8e41a92006-08-21 23:08:52 +02002828 ($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7);
Jakub Narebski740e67f2006-08-21 23:07:00 +02002829 } else {
Jakub Narebski9d301452007-11-01 12:38:08 +01002830 $res{'from_file'} = $res{'to_file'} = $res{'file'} = unquote($7);
Jakub Narebski740e67f2006-08-21 23:07:00 +02002831 }
2832 }
Jakub Narebski78bc4032007-05-07 01:10:03 +02002833 # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh'
2834 # combined diff (for merge commit)
2835 elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) {
2836 $res{'nparents'} = length($1);
2837 $res{'from_mode'} = [ split(' ', $2) ];
2838 $res{'to_mode'} = pop @{$res{'from_mode'}};
2839 $res{'from_id'} = [ split(' ', $3) ];
2840 $res{'to_id'} = pop @{$res{'from_id'}};
2841 $res{'status'} = [ split('', $4) ];
2842 $res{'to_file'} = unquote($5);
2843 }
Jakub Narebski740e67f2006-08-21 23:07:00 +02002844 # 'c512b523472485aef4fff9e57b229d9d243c967f'
Jakub Narebski0edcb372006-08-31 00:36:04 +02002845 elsif ($line =~ m/^([0-9a-fA-F]{40})$/) {
2846 $res{'commit'} = $1;
2847 }
Jakub Narebski740e67f2006-08-21 23:07:00 +02002848
2849 return wantarray ? %res : \%res;
2850}
2851
Jakub Narebski0cec6db2007-10-30 01:35:05 +01002852# wrapper: return parsed line of git-diff-tree "raw" output
2853# (the argument might be raw line, or parsed info)
2854sub parsed_difftree_line {
2855 my $line_or_ref = shift;
2856
2857 if (ref($line_or_ref) eq "HASH") {
2858 # pre-parsed (or generated by hand)
2859 return $line_or_ref;
2860 } else {
2861 return parse_difftree_raw_line($line_or_ref);
2862 }
2863}
2864
Jakub Narebskicb849b42006-08-31 00:32:15 +02002865# parse line of git-ls-tree output
Jakub Narebski74fd8722009-05-07 19:11:29 +02002866sub parse_ls_tree_line {
Jakub Narebskicb849b42006-08-31 00:32:15 +02002867 my $line = shift;
2868 my %opts = @_;
2869 my %res;
2870
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02002871 if ($opts{'-l'}) {
2872 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa 16717 panic.c'
2873 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40}) +(-|[0-9]+)\t(.+)$/s;
Jakub Narebskicb849b42006-08-31 00:32:15 +02002874
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02002875 $res{'mode'} = $1;
2876 $res{'type'} = $2;
2877 $res{'hash'} = $3;
2878 $res{'size'} = $4;
2879 if ($opts{'-z'}) {
2880 $res{'name'} = $5;
2881 } else {
2882 $res{'name'} = unquote($5);
2883 }
Jakub Narebskicb849b42006-08-31 00:32:15 +02002884 } else {
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02002885 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
2886 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
2887
2888 $res{'mode'} = $1;
2889 $res{'type'} = $2;
2890 $res{'hash'} = $3;
2891 if ($opts{'-z'}) {
2892 $res{'name'} = $4;
2893 } else {
2894 $res{'name'} = unquote($4);
2895 }
Jakub Narebskicb849b42006-08-31 00:32:15 +02002896 }
2897
2898 return wantarray ? %res : \%res;
2899}
2900
Jakub Narebski90921742007-06-08 13:27:42 +02002901# generates _two_ hashes, references to which are passed as 2 and 3 argument
2902sub parse_from_to_diffinfo {
2903 my ($diffinfo, $from, $to, @parents) = @_;
2904
2905 if ($diffinfo->{'nparents'}) {
2906 # combined diff
2907 $from->{'file'} = [];
2908 $from->{'href'} = [];
2909 fill_from_file_info($diffinfo, @parents)
2910 unless exists $diffinfo->{'from_file'};
2911 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
Jakub Narebski9d301452007-11-01 12:38:08 +01002912 $from->{'file'}[$i] =
2913 defined $diffinfo->{'from_file'}[$i] ?
2914 $diffinfo->{'from_file'}[$i] :
2915 $diffinfo->{'to_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02002916 if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
2917 $from->{'href'}[$i] = href(action=>"blob",
2918 hash_base=>$parents[$i],
2919 hash=>$diffinfo->{'from_id'}[$i],
2920 file_name=>$from->{'file'}[$i]);
2921 } else {
2922 $from->{'href'}[$i] = undef;
2923 }
2924 }
2925 } else {
Jakub Narebski0cec6db2007-10-30 01:35:05 +01002926 # ordinary (not combined) diff
Jakub Narebski9d301452007-11-01 12:38:08 +01002927 $from->{'file'} = $diffinfo->{'from_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02002928 if ($diffinfo->{'status'} ne "A") { # not new (added) file
2929 $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent,
2930 hash=>$diffinfo->{'from_id'},
2931 file_name=>$from->{'file'});
2932 } else {
2933 delete $from->{'href'};
2934 }
2935 }
2936
Jakub Narebski9d301452007-11-01 12:38:08 +01002937 $to->{'file'} = $diffinfo->{'to_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02002938 if (!is_deleted($diffinfo)) { # file exists in result
2939 $to->{'href'} = href(action=>"blob", hash_base=>$hash,
2940 hash=>$diffinfo->{'to_id'},
2941 file_name=>$to->{'file'});
2942 } else {
2943 delete $to->{'href'};
2944 }
2945}
2946
Jakub Narebski717b8312006-07-31 21:22:15 +02002947## ......................................................................
2948## parse to array of hashes functions
2949
Jakub Narebskicd146402006-11-02 20:23:11 +01002950sub git_get_heads_list {
2951 my $limit = shift;
2952 my @headslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02002953
Jakub Narebskicd146402006-11-02 20:23:11 +01002954 open my $fd, '-|', git_cmd(), 'for-each-ref',
2955 ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
2956 '--format=%(objectname) %(refname) %(subject)%00%(committer)',
2957 'refs/heads'
Jakub Narebskic83a77e2006-09-15 03:43:28 +02002958 or return;
2959 while (my $line = <$fd>) {
Jakub Narebskicd146402006-11-02 20:23:11 +01002960 my %ref_item;
Jakub Narebski120ddde2006-09-19 14:33:22 +02002961
Jakub Narebskicd146402006-11-02 20:23:11 +01002962 chomp $line;
2963 my ($refinfo, $committerinfo) = split(/\0/, $line);
2964 my ($hash, $name, $title) = split(' ', $refinfo, 3);
2965 my ($committer, $epoch, $tz) =
2966 ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
Jakub Narebskibf901f82007-12-15 15:40:28 +01002967 $ref_item{'fullname'} = $name;
Jakub Narebskicd146402006-11-02 20:23:11 +01002968 $name =~ s!^refs/heads/!!;
2969
2970 $ref_item{'name'} = $name;
2971 $ref_item{'id'} = $hash;
2972 $ref_item{'title'} = $title || '(no commit message)';
2973 $ref_item{'epoch'} = $epoch;
2974 if ($epoch) {
2975 $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
2976 } else {
2977 $ref_item{'age'} = "unknown";
Jakub Narebski717b8312006-07-31 21:22:15 +02002978 }
Jakub Narebskicd146402006-11-02 20:23:11 +01002979
2980 push @headslist, \%ref_item;
Jakub Narebskic83a77e2006-09-15 03:43:28 +02002981 }
2982 close $fd;
Junio C Hamano7a13b992006-07-31 19:18:34 -07002983
Jakub Narebskicd146402006-11-02 20:23:11 +01002984 return wantarray ? @headslist : \@headslist;
2985}
Jakub Narebskic83a77e2006-09-15 03:43:28 +02002986
Jakub Narebskicd146402006-11-02 20:23:11 +01002987sub git_get_tags_list {
2988 my $limit = shift;
2989 my @tagslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02002990
Jakub Narebskicd146402006-11-02 20:23:11 +01002991 open my $fd, '-|', git_cmd(), 'for-each-ref',
2992 ($limit ? '--count='.($limit+1) : ()), '--sort=-creatordate',
2993 '--format=%(objectname) %(objecttype) %(refname) '.
2994 '%(*objectname) %(*objecttype) %(subject)%00%(creator)',
2995 'refs/tags'
2996 or return;
2997 while (my $line = <$fd>) {
2998 my %ref_item;
2999
3000 chomp $line;
3001 my ($refinfo, $creatorinfo) = split(/\0/, $line);
3002 my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6);
3003 my ($creator, $epoch, $tz) =
3004 ($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/);
Jakub Narebskibf901f82007-12-15 15:40:28 +01003005 $ref_item{'fullname'} = $name;
Jakub Narebskicd146402006-11-02 20:23:11 +01003006 $name =~ s!^refs/tags/!!;
3007
3008 $ref_item{'type'} = $type;
3009 $ref_item{'id'} = $id;
3010 $ref_item{'name'} = $name;
3011 if ($type eq "tag") {
3012 $ref_item{'subject'} = $title;
3013 $ref_item{'reftype'} = $reftype;
3014 $ref_item{'refid'} = $refid;
3015 } else {
3016 $ref_item{'reftype'} = $type;
3017 $ref_item{'refid'} = $id;
3018 }
3019
3020 if ($type eq "tag" || $type eq "commit") {
3021 $ref_item{'epoch'} = $epoch;
3022 if ($epoch) {
3023 $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
3024 } else {
3025 $ref_item{'age'} = "unknown";
3026 }
3027 }
3028
3029 push @tagslist, \%ref_item;
Jakub Narebski717b8312006-07-31 21:22:15 +02003030 }
Jakub Narebskicd146402006-11-02 20:23:11 +01003031 close $fd;
3032
3033 return wantarray ? @tagslist : \@tagslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02003034}
3035
3036## ----------------------------------------------------------------------
3037## filesystem-related functions
3038
3039sub get_file_owner {
3040 my $path = shift;
3041
3042 my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path);
3043 my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid);
3044 if (!defined $gcos) {
3045 return undef;
3046 }
3047 my $owner = $gcos;
3048 $owner =~ s/[,;].*$//;
Martin Koegler00f429a2007-06-03 17:42:44 +02003049 return to_utf8($owner);
Jakub Narebski717b8312006-07-31 21:22:15 +02003050}
3051
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01003052# assume that file exists
3053sub insert_file {
3054 my $filename = shift;
3055
3056 open my $fd, '<', $filename;
Jakub Narebski45868642008-12-08 14:13:21 +01003057 print map { to_utf8($_) } <$fd>;
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01003058 close $fd;
3059}
3060
Jakub Narebski717b8312006-07-31 21:22:15 +02003061## ......................................................................
3062## mimetype related functions
3063
3064sub mimetype_guess_file {
3065 my $filename = shift;
3066 my $mimemap = shift;
3067 -r $mimemap or return undef;
3068
3069 my %mimemap;
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02003070 open(my $mh, '<', $mimemap) or return undef;
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02003071 while (<$mh>) {
Jakub Narebski618918e2006-08-14 02:15:22 +02003072 next if m/^#/; # skip comments
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02003073 my ($mimetype, $exts) = split(/\t+/);
Junio C Hamano46b059d2006-07-31 19:24:37 -07003074 if (defined $exts) {
3075 my @exts = split(/\s+/, $exts);
3076 foreach my $ext (@exts) {
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02003077 $mimemap{$ext} = $mimetype;
Junio C Hamano46b059d2006-07-31 19:24:37 -07003078 }
Jakub Narebski717b8312006-07-31 21:22:15 +02003079 }
3080 }
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02003081 close($mh);
Jakub Narebski717b8312006-07-31 21:22:15 +02003082
Jakub Narebski80593192006-09-19 13:57:03 +02003083 $filename =~ /\.([^.]*)$/;
Jakub Narebski717b8312006-07-31 21:22:15 +02003084 return $mimemap{$1};
3085}
3086
3087sub mimetype_guess {
3088 my $filename = shift;
3089 my $mime;
3090 $filename =~ /\./ or return undef;
3091
3092 if ($mimetypes_file) {
3093 my $file = $mimetypes_file;
Jakub Narebskid5aa50d2006-08-14 02:16:33 +02003094 if ($file !~ m!^/!) { # if it is relative path
3095 # it is relative to project
3096 $file = "$projectroot/$project/$file";
3097 }
Jakub Narebski717b8312006-07-31 21:22:15 +02003098 $mime = mimetype_guess_file($filename, $file);
3099 }
3100 $mime ||= mimetype_guess_file($filename, '/etc/mime.types');
3101 return $mime;
3102}
3103
Jakub Narebski847e01f2006-08-14 02:05:47 +02003104sub blob_mimetype {
Jakub Narebski717b8312006-07-31 21:22:15 +02003105 my $fd = shift;
3106 my $filename = shift;
3107
3108 if ($filename) {
3109 my $mime = mimetype_guess($filename);
3110 $mime and return $mime;
3111 }
3112
3113 # just in case
3114 return $default_blob_plain_mimetype unless $fd;
3115
3116 if (-T $fd) {
Jakub Narebski7f718e82008-06-03 16:47:10 +02003117 return 'text/plain';
Jakub Narebski717b8312006-07-31 21:22:15 +02003118 } elsif (! $filename) {
3119 return 'application/octet-stream';
3120 } elsif ($filename =~ m/\.png$/i) {
3121 return 'image/png';
3122 } elsif ($filename =~ m/\.gif$/i) {
3123 return 'image/gif';
3124 } elsif ($filename =~ m/\.jpe?g$/i) {
3125 return 'image/jpeg';
3126 } else {
3127 return 'application/octet-stream';
3128 }
3129}
3130
Jakub Narebski7f718e82008-06-03 16:47:10 +02003131sub blob_contenttype {
3132 my ($fd, $file_name, $type) = @_;
3133
3134 $type ||= blob_mimetype($fd, $file_name);
3135 if ($type eq 'text/plain' && defined $default_text_plain_charset) {
3136 $type .= "; charset=$default_text_plain_charset";
3137 }
3138
3139 return $type;
3140}
3141
Jakub Narebski717b8312006-07-31 21:22:15 +02003142## ======================================================================
3143## functions printing HTML: header, footer, error page
3144
Kay Sievers12a88f22005-08-07 20:02:47 +02003145sub git_header_html {
Kay Sieversa59d4af2005-08-07 20:15:44 +02003146 my $status = shift || "200 OK";
Kay Sievers11044292005-10-19 03:18:45 +02003147 my $expires = shift;
Kay Sieversa59d4af2005-08-07 20:15:44 +02003148
Petr Baudis8be28902006-10-24 05:18:39 +02003149 my $title = "$site_name";
Kay Sieversb87d78d2005-08-07 20:21:04 +02003150 if (defined $project) {
Martin Koegler00f429a2007-06-03 17:42:44 +02003151 $title .= " - " . to_utf8($project);
Kay Sieversb87d78d2005-08-07 20:21:04 +02003152 if (defined $action) {
3153 $title .= "/$action";
Jakub Narebski7bedd9f2006-06-20 06:17:03 +00003154 if (defined $file_name) {
Jakub Narebski403d0902006-11-08 11:48:56 +01003155 $title .= " - " . esc_path($file_name);
Jakub Narebski7bedd9f2006-06-20 06:17:03 +00003156 if ($action eq "tree" && $file_name !~ m|/$|) {
3157 $title .= "/";
3158 }
3159 }
Kay Sieversb87d78d2005-08-07 20:21:04 +02003160 }
3161 }
Alp Tokerf6801d62006-07-11 11:19:34 +01003162 my $content_type;
3163 # require explicit support from the UA if we are to send the page as
3164 # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
3165 # we have to do this because MSIE sometimes globs '*/*', pretending to
3166 # support xhtml+xml but choking when it gets what it asked for.
Jakub Narebski952c65f2006-08-22 16:52:50 +02003167 if (defined $cgi->http('HTTP_ACCEPT') &&
3168 $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
3169 $cgi->Accept('application/xhtml+xml') != 0) {
Alp Tokerf6801d62006-07-11 11:19:34 +01003170 $content_type = 'application/xhtml+xml';
3171 } else {
3172 $content_type = 'text/html';
3173 }
Jakub Narebski952c65f2006-08-22 16:52:50 +02003174 print $cgi->header(-type=>$content_type, -charset => 'utf-8',
3175 -status=> $status, -expires => $expires);
Jakub Narebski45c9a752006-12-27 23:59:51 +01003176 my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
Kay Sieversa59d4af2005-08-07 20:15:44 +02003177 print <<EOF;
Kay Sievers6191f8e2005-08-07 20:19:56 +02003178<?xml version="1.0" encoding="utf-8"?>
Kay Sievers161332a2005-08-07 19:49:46 +02003179<!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 +02003180<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
Jakub Narebskid4baf9e2006-08-17 11:21:28 +02003181<!-- git web interface version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
Jakub Narebskiae20de52006-06-21 09:48:03 +02003182<!-- git core binaries version $git_version -->
Kay Sievers161332a2005-08-07 19:49:46 +02003183<head>
Alp Tokerf6801d62006-07-11 11:19:34 +01003184<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
Jakub Narebski45c9a752006-12-27 23:59:51 +01003185<meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/>
Kay Sieversc994d622005-08-07 20:27:18 +02003186<meta name="robots" content="index, nofollow"/>
Kay Sieversb87d78d2005-08-07 20:21:04 +02003187<title>$title</title>
Kay Sievers161332a2005-08-07 19:49:46 +02003188EOF
Giuseppe Bilotta41a4d162009-01-31 02:31:52 +01003189 # the stylesheet, favicon etc urls won't work correctly with path_info
3190 # unless we set the appropriate base URL
Giuseppe Bilottac3254ae2009-01-31 02:31:50 +01003191 if ($ENV{'PATH_INFO'}) {
Giuseppe Bilotta81d3fe92009-02-15 10:18:36 +01003192 print "<base href=\"".esc_url($base_url)."\" />\n";
Giuseppe Bilottac3254ae2009-01-31 02:31:50 +01003193 }
Giuseppe Bilotta41a4d162009-01-31 02:31:52 +01003194 # print out each stylesheet that exist, providing backwards capability
3195 # for those people who defined $stylesheet in a config file
Alan Chandlerb2d34762006-10-03 13:49:03 +01003196 if (defined $stylesheet) {
Alan Chandlerb2d34762006-10-03 13:49:03 +01003197 print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
3198 } else {
3199 foreach my $stylesheet (@stylesheets) {
3200 next unless $stylesheet;
3201 print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
3202 }
3203 }
Matthias Lederhoferdd04c422006-08-06 13:25:41 +02003204 if (defined $project) {
Jakub Narebski35621982008-04-20 22:09:48 +02003205 my %href_params = get_feed_info();
3206 if (!exists $href_params{'-title'}) {
3207 $href_params{'-title'} = 'log';
3208 }
3209
3210 foreach my $format qw(RSS Atom) {
3211 my $type = lc($format);
3212 my %link_attr = (
3213 '-rel' => 'alternate',
3214 '-title' => "$project - $href_params{'-title'} - $format feed",
3215 '-type' => "application/$type+xml"
3216 );
3217
3218 $href_params{'action'} = $type;
3219 $link_attr{'-href'} = href(%href_params);
3220 print "<link ".
3221 "rel=\"$link_attr{'-rel'}\" ".
3222 "title=\"$link_attr{'-title'}\" ".
3223 "href=\"$link_attr{'-href'}\" ".
3224 "type=\"$link_attr{'-type'}\" ".
3225 "/>\n";
3226
3227 $href_params{'extra_options'} = '--no-merges';
3228 $link_attr{'-href'} = href(%href_params);
3229 $link_attr{'-title'} .= ' (no merges)';
3230 print "<link ".
3231 "rel=\"$link_attr{'-rel'}\" ".
3232 "title=\"$link_attr{'-title'}\" ".
3233 "href=\"$link_attr{'-href'}\" ".
3234 "type=\"$link_attr{'-type'}\" ".
3235 "/>\n";
3236 }
3237
Jakub Narebski9d0734a2006-09-15 11:11:33 +02003238 } else {
3239 printf('<link rel="alternate" title="%s projects list" '.
Jakub Narebski35621982008-04-20 22:09:48 +02003240 'href="%s" type="text/plain; charset=utf-8" />'."\n",
Jakub Narebski9d0734a2006-09-15 11:11:33 +02003241 $site_name, href(project=>undef, action=>"project_index"));
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01003242 printf('<link rel="alternate" title="%s projects feeds" '.
Jakub Narebski35621982008-04-20 22:09:48 +02003243 'href="%s" type="text/x-opml" />'."\n",
Jakub Narebski9d0734a2006-09-15 11:11:33 +02003244 $site_name, href(project=>undef, action=>"opml"));
Matthias Lederhoferdd04c422006-08-06 13:25:41 +02003245 }
Jakub Narebski0b5deba2006-09-04 20:32:13 +02003246 if (defined $favicon) {
Jakub Narebski35621982008-04-20 22:09:48 +02003247 print qq(<link rel="shortcut icon" href="$favicon" type="image/png" />\n);
Jakub Narebski0b5deba2006-09-04 20:32:13 +02003248 }
Jakub Narebski10161352006-08-05 13:18:58 +02003249
Matthias Lederhoferdd04c422006-08-06 13:25:41 +02003250 print "</head>\n" .
Alan Chandlerb2d34762006-10-03 13:49:03 +01003251 "<body>\n";
3252
John 'Warthog9' Hawley24d4afc2010-01-30 23:30:41 +01003253 if (defined $site_header && -f $site_header) {
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01003254 insert_file($site_header);
Alan Chandlerb2d34762006-10-03 13:49:03 +01003255 }
3256
3257 print "<div class=\"page_header\">\n" .
Jakub Narebski9a7a62f2006-10-06 12:31:05 +02003258 $cgi->a({-href => esc_url($logo_url),
3259 -title => $logo_label},
3260 qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
Jakub Narebskif93bff82006-09-26 01:58:41 +02003261 print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
Kay Sieversb87d78d2005-08-07 20:21:04 +02003262 if (defined $project) {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003263 print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
Kay Sieversb87d78d2005-08-07 20:21:04 +02003264 if (defined $action) {
3265 print " / $action";
3266 }
Kay Sievers19806692005-08-07 20:26:27 +02003267 print "\n";
Robert Fitzsimons6be93512006-12-23 03:35:16 +00003268 }
Petr Baudisd77b5672007-05-17 04:24:19 +02003269 print "</div>\n";
3270
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08003271 my $have_search = gitweb_check_feature('search');
Jakub Narebskif70dda22008-06-02 11:54:41 +02003272 if (defined $project && $have_search) {
Kay Sievers19806692005-08-07 20:26:27 +02003273 if (!defined $searchtext) {
3274 $searchtext = "";
3275 }
Kay Sieversc39e47d2005-09-17 03:00:21 +02003276 my $search_hash;
Timo Hirvonen4c5c2022006-06-20 16:41:05 +03003277 if (defined $hash_base) {
3278 $search_hash = $hash_base;
3279 } elsif (defined $hash) {
Kay Sieversc39e47d2005-09-17 03:00:21 +02003280 $search_hash = $hash;
3281 } else {
Jakub Narebski8adc4bd2006-06-22 08:52:57 +02003282 $search_hash = "HEAD";
Kay Sieversc39e47d2005-09-17 03:00:21 +02003283 }
Matt McCutchen40375a82007-06-28 14:57:07 -04003284 my $action = $my_uri;
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08003285 my $use_pathinfo = gitweb_check_feature('pathinfo');
Matt McCutchen40375a82007-06-28 14:57:07 -04003286 if ($use_pathinfo) {
martin f. krafft85d17a12008-04-20 23:23:38 +02003287 $action .= "/".esc_url($project);
Matt McCutchen40375a82007-06-28 14:57:07 -04003288 }
Matt McCutchen40375a82007-06-28 14:57:07 -04003289 print $cgi->startform(-method => "get", -action => $action) .
Kay Sieversc994d622005-08-07 20:27:18 +02003290 "<div class=\"search\">\n" .
Jakub Narebskif70dda22008-06-02 11:54:41 +02003291 (!$use_pathinfo &&
3292 $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
3293 $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
3294 $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
Petr Baudis88ad7292006-10-24 05:15:46 +02003295 $cgi->popup_menu(-name => 'st', -default => 'commit',
Petr Baudise7738552007-05-17 04:31:12 +02003296 -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
Petr Baudis88ad7292006-10-24 05:15:46 +02003297 $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
3298 " search:\n",
Kay Sieversc994d622005-08-07 20:27:18 +02003299 $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
Petr Baudis0e559912008-02-26 13:22:08 +01003300 "<span title=\"Extended regular expression\">" .
3301 $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
3302 -checked => $search_use_regexp) .
3303 "</span>" .
Kay Sieversc994d622005-08-07 20:27:18 +02003304 "</div>" .
3305 $cgi->end_form() . "\n";
Kay Sievers4c02e3c2005-08-07 19:52:52 +02003306 }
Kay Sievers161332a2005-08-07 19:49:46 +02003307}
3308
Kay Sievers12a88f22005-08-07 20:02:47 +02003309sub git_footer_html {
Jakub Narebski35621982008-04-20 22:09:48 +02003310 my $feed_class = 'rss_logo';
3311
Kay Sievers6191f8e2005-08-07 20:19:56 +02003312 print "<div class=\"page_footer\">\n";
Kay Sieversb87d78d2005-08-07 20:21:04 +02003313 if (defined $project) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02003314 my $descr = git_get_project_description($project);
Kay Sieversb87d78d2005-08-07 20:21:04 +02003315 if (defined $descr) {
Kay Sievers40c13812005-11-19 17:41:29 +01003316 print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
Kay Sievers6191f8e2005-08-07 20:19:56 +02003317 }
Jakub Narebski35621982008-04-20 22:09:48 +02003318
3319 my %href_params = get_feed_info();
3320 if (!%href_params) {
3321 $feed_class .= ' generic';
3322 }
3323 $href_params{'-title'} ||= 'log';
3324
3325 foreach my $format qw(RSS Atom) {
3326 $href_params{'action'} = lc($format);
3327 print $cgi->a({-href => href(%href_params),
3328 -title => "$href_params{'-title'} $format feed",
3329 -class => $feed_class}, $format)."\n";
3330 }
3331
Kay Sieversc994d622005-08-07 20:27:18 +02003332 } else {
Jakub Narebskia1565c42006-09-15 19:30:34 +02003333 print $cgi->a({-href => href(project=>undef, action=>"opml"),
Jakub Narebski35621982008-04-20 22:09:48 +02003334 -class => $feed_class}, "OPML") . " ";
Jakub Narebski9d0734a2006-09-15 11:11:33 +02003335 print $cgi->a({-href => href(project=>undef, action=>"project_index"),
Jakub Narebski35621982008-04-20 22:09:48 +02003336 -class => $feed_class}, "TXT") . "\n";
Kay Sieversff7669a2005-08-07 20:13:02 +02003337 }
Jakub Narebski35621982008-04-20 22:09:48 +02003338 print "</div>\n"; # class="page_footer"
Alan Chandlerb2d34762006-10-03 13:49:03 +01003339
Jakub Narebskiaa7dd052009-09-01 13:39:16 +02003340 if (defined $t0 && gitweb_check_feature('timed')) {
3341 print "<div id=\"generating_info\">\n";
3342 print 'This page took '.
3343 '<span id="generating_time" class="time_span">'.
3344 Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
3345 ' seconds </span>'.
3346 ' and '.
3347 '<span id="generating_cmd">'.
3348 $number_of_git_cmds.
3349 '</span> git commands '.
3350 " to generate.\n";
3351 print "</div>\n"; # class="page_footer"
3352 }
3353
John 'Warthog9' Hawley24d4afc2010-01-30 23:30:41 +01003354 if (defined $site_footer && -f $site_footer) {
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01003355 insert_file($site_footer);
Alan Chandlerb2d34762006-10-03 13:49:03 +01003356 }
3357
Jakub Narebskic4ccf612009-09-01 13:39:19 +02003358 print qq!<script type="text/javascript" src="$javascript"></script>\n!;
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +01003359 if (defined $action &&
3360 $action eq 'blame_incremental') {
Jakub Narebskic4ccf612009-09-01 13:39:19 +02003361 print qq!<script type="text/javascript">\n!.
3362 qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
3363 qq! "!. href() .qq!");\n!.
3364 qq!</script>\n!;
Jakub Narebskie627e502009-11-26 21:12:15 +01003365 } elsif (gitweb_check_feature('javascript-actions')) {
Jakub Narebskic4ccf612009-09-01 13:39:19 +02003366 print qq!<script type="text/javascript">\n!.
3367 qq!window.onload = fixLinks;\n!.
3368 qq!</script>\n!;
3369 }
3370
Alan Chandlerb2d34762006-10-03 13:49:03 +01003371 print "</body>\n" .
Kay Sievers9cd3d982005-08-07 20:17:42 +02003372 "</html>";
Kay Sievers161332a2005-08-07 19:49:46 +02003373}
3374
Jakub Narebski453541f2010-02-07 21:51:18 +01003375# die_error(<http_status_code>, <error_message>[, <detailed_html_description>])
Lea Wiemann074afaa2008-06-19 22:03:21 +02003376# Example: die_error(404, 'Hash not found')
3377# By convention, use the following status codes (as defined in RFC 2616):
3378# 400: Invalid or missing CGI parameters, or
3379# requested object exists but has wrong type.
3380# 403: Requested feature (like "pickaxe" or "snapshot") not enabled on
3381# this server or project.
3382# 404: Requested object/revision/project doesn't exist.
3383# 500: The server isn't configured properly, or
3384# an internal error occurred (e.g. failed assertions caused by bugs), or
3385# an unknown error occurred (e.g. the git binary died unexpectedly).
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +01003386# 503: The server is currently unavailable (because it is overloaded,
3387# or down for maintenance). Generally, this is a temporary state.
Kay Sievers061cc7c2005-08-07 20:15:57 +02003388sub die_error {
Lea Wiemann074afaa2008-06-19 22:03:21 +02003389 my $status = shift || 500;
Jakub Narebski453541f2010-02-07 21:51:18 +01003390 my $error = esc_html(shift || "Internal Server Error");
John 'Warthog9' Hawleyaa140132010-01-30 23:30:44 +01003391 my $extra = shift;
Kay Sievers664f4cc2005-08-07 20:17:19 +02003392
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +01003393 my %http_responses = (
3394 400 => '400 Bad Request',
3395 403 => '403 Forbidden',
3396 404 => '404 Not Found',
3397 500 => '500 Internal Server Error',
3398 503 => '503 Service Unavailable',
3399 );
Lea Wiemann074afaa2008-06-19 22:03:21 +02003400 git_header_html($http_responses{$status});
Jakub Narebski59b9f612006-08-22 23:42:53 +02003401 print <<EOF;
3402<div class="page_body">
3403<br /><br />
3404$status - $error
3405<br />
Jakub Narebski59b9f612006-08-22 23:42:53 +02003406EOF
John 'Warthog9' Hawleyaa140132010-01-30 23:30:44 +01003407 if (defined $extra) {
3408 print "<hr />\n" .
3409 "$extra\n";
3410 }
3411 print "</div>\n";
3412
Kay Sieversa59d4af2005-08-07 20:15:44 +02003413 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02003414 exit;
Kay Sieversa59d4af2005-08-07 20:15:44 +02003415}
3416
Jakub Narebski717b8312006-07-31 21:22:15 +02003417## ----------------------------------------------------------------------
3418## functions printing or outputting HTML: navigation
3419
Jakub Narebski847e01f2006-08-14 02:05:47 +02003420sub git_print_page_nav {
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003421 my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_;
3422 $extra = '' if !defined $extra; # pager or formats
3423
3424 my @navs = qw(summary shortlog log commit commitdiff tree);
3425 if ($suppress) {
3426 @navs = grep { $_ ne $suppress } @navs;
3427 }
3428
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003429 my %arg = map { $_ => {action=>$_} } @navs;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003430 if (defined $head) {
3431 for (qw(commit commitdiff)) {
Jakub Narebski3be8e722007-04-01 22:22:21 +02003432 $arg{$_}{'hash'} = $head;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003433 }
3434 if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
3435 for (qw(shortlog log)) {
Jakub Narebski3be8e722007-04-01 22:22:21 +02003436 $arg{$_}{'hash'} = $head;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003437 }
3438 }
3439 }
Petr Baudisd627f682008-10-02 16:36:52 +02003440
Jakub Narebski3be8e722007-04-01 22:22:21 +02003441 $arg{'tree'}{'hash'} = $treehead if defined $treehead;
3442 $arg{'tree'}{'hash_base'} = $treebase if defined $treebase;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003443
Junio C Hamanoa7c5a282008-11-29 13:02:08 -08003444 my @actions = gitweb_get_feature('actions');
Jakub Narebski2b11e052008-10-12 00:02:32 +02003445 my %repl = (
3446 '%' => '%',
3447 'n' => $project, # project name
3448 'f' => $git_dir, # project path within filesystem
3449 'h' => $treehead || '', # current hash ('h' parameter)
3450 'b' => $treebase || '', # hash base ('hb' parameter)
3451 );
Petr Baudisd627f682008-10-02 16:36:52 +02003452 while (@actions) {
Jakub Narebski2b11e052008-10-12 00:02:32 +02003453 my ($label, $link, $pos) = splice(@actions,0,3);
3454 # insert
Petr Baudisd627f682008-10-02 16:36:52 +02003455 @navs = map { $_ eq $pos ? ($_, $label) : $_ } @navs;
3456 # munch munch
Jakub Narebski2b11e052008-10-12 00:02:32 +02003457 $link =~ s/%([%nfhb])/$repl{$1}/g;
Petr Baudisd627f682008-10-02 16:36:52 +02003458 $arg{$label}{'_href'} = $link;
3459 }
3460
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003461 print "<div class=\"page_nav\">\n" .
3462 (join " | ",
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003463 map { $_ eq $current ?
Petr Baudisd627f682008-10-02 16:36:52 +02003464 $_ : $cgi->a({-href => ($arg{$_}{_href} ? $arg{$_}{_href} : href(%{$arg{$_}}))}, "$_")
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003465 } @navs);
Jakub Narebski898a8932006-07-30 16:14:43 +02003466 print "<br/>\n$extra<br/>\n" .
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003467 "</div>\n";
3468}
3469
Jakub Narebski847e01f2006-08-14 02:05:47 +02003470sub format_paging_nav {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01003471 my ($action, $page, $has_next_link) = @_;
Jakub Narebski43ffc062006-07-30 17:49:00 +02003472 my $paging_nav;
3473
3474
Jakub Narebski43ffc062006-07-30 17:49:00 +02003475 if ($page > 0) {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01003476 $paging_nav .=
3477 $cgi->a({-href => href(-replay=>1, page=>undef)}, "first") .
3478 " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01003479 $cgi->a({-href => href(-replay=>1, page=>$page-1),
Jakub Narebski26298b52006-08-10 12:38:47 +02003480 -accesskey => "p", -title => "Alt-p"}, "prev");
Jakub Narebski43ffc062006-07-30 17:49:00 +02003481 } else {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01003482 $paging_nav .= "first &sdot; prev";
Jakub Narebski43ffc062006-07-30 17:49:00 +02003483 }
3484
Lea Wiemann1f684dc2008-05-28 01:25:42 +02003485 if ($has_next_link) {
Jakub Narebski43ffc062006-07-30 17:49:00 +02003486 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01003487 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Jakub Narebski26298b52006-08-10 12:38:47 +02003488 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski43ffc062006-07-30 17:49:00 +02003489 } else {
3490 $paging_nav .= " &sdot; next";
3491 }
3492
3493 return $paging_nav;
3494}
3495
Jakub Narebski717b8312006-07-31 21:22:15 +02003496## ......................................................................
3497## functions printing or outputting HTML: div
Kay Sievers42f7eb92005-08-07 20:21:46 +02003498
Jakub Narebski847e01f2006-08-14 02:05:47 +02003499sub git_print_header_div {
Jakub Narebski717b8312006-07-31 21:22:15 +02003500 my ($action, $title, $hash, $hash_base) = @_;
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003501 my %args = ();
Jakub Narebski717b8312006-07-31 21:22:15 +02003502
Jakub Narebski3be8e722007-04-01 22:22:21 +02003503 $args{'action'} = $action;
3504 $args{'hash'} = $hash if $hash;
3505 $args{'hash_base'} = $hash_base if $hash_base;
Jakub Narebski717b8312006-07-31 21:22:15 +02003506
3507 print "<div class=\"header\">\n" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003508 $cgi->a({-href => href(%args), -class => "title"},
3509 $title ? $title : $action) .
3510 "\n</div>\n";
Kay Sievers42f7eb92005-08-07 20:21:46 +02003511}
3512
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003513sub print_local_time {
John 'Warthog9' Hawley0cf207f2010-01-30 23:30:42 +01003514 print format_local_time(@_);
3515}
3516
3517sub format_local_time {
3518 my $localtime = '';
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003519 my %date = @_;
3520 if ($date{'hour_local'} < 6) {
John 'Warthog9' Hawley0cf207f2010-01-30 23:30:42 +01003521 $localtime .= sprintf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003522 $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
Jakub Narebskia44465c2006-08-28 23:17:31 +02003523 } else {
John 'Warthog9' Hawley0cf207f2010-01-30 23:30:42 +01003524 $localtime .= sprintf(" (%02d:%02d %s)",
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003525 $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
Jakub Narebskia44465c2006-08-28 23:17:31 +02003526 }
John 'Warthog9' Hawley0cf207f2010-01-30 23:30:42 +01003527
3528 return $localtime;
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003529}
3530
3531# Outputs the author name and date in long form
3532sub git_print_authorship {
3533 my $co = shift;
3534 my %opts = @_;
3535 my $tag = $opts{-tag} || 'div';
Stephen Boyde133d652009-10-15 21:14:59 -07003536 my $author = $co->{'author_name'};
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003537
3538 my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
3539 print "<$tag class=\"author_date\">" .
Stephen Boyde133d652009-10-15 21:14:59 -07003540 format_search_author($author, "author", esc_html($author)) .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003541 " [$ad{'rfc2822'}";
3542 print_local_time(%ad) if ($opts{-localtime});
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02003543 print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1)
3544 . "</$tag>\n";
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003545}
3546
3547# Outputs table rows containing the full author or committer information,
3548# in the format expected for 'commit' view (& similia).
3549# Parameters are a commit hash reference, followed by the list of people
3550# to output information for. If the list is empty it defalts to both
3551# author and committer.
3552sub git_print_authorship_rows {
3553 my $co = shift;
3554 # too bad we can't use @people = @_ || ('author', 'committer')
3555 my @people = @_;
3556 @people = ('author', 'committer') unless @people;
3557 foreach my $who (@people) {
3558 my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"});
Stephen Boyde133d652009-10-15 21:14:59 -07003559 print "<tr><td>$who</td><td>" .
3560 format_search_author($co->{"${who}_name"}, $who,
3561 esc_html($co->{"${who}_name"})) . " " .
3562 format_search_author($co->{"${who}_email"}, $who,
3563 esc_html("<" . $co->{"${who}_email"} . ">")) .
3564 "</td><td rowspan=\"2\">" .
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02003565 git_get_avatar($co->{"${who}_email"}, -size => 'double') .
3566 "</td></tr>\n" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003567 "<tr>" .
3568 "<td></td><td> $wd{'rfc2822'}";
3569 print_local_time(%wd);
3570 print "</td>" .
3571 "</tr>\n";
3572 }
Jakub Narebski6fd92a22006-08-28 14:48:12 +02003573}
3574
Jakub Narebski717b8312006-07-31 21:22:15 +02003575sub git_print_page_path {
3576 my $name = shift;
3577 my $type = shift;
Luben Tuikov59fb1c92006-08-17 10:39:29 -07003578 my $hb = shift;
Junio C Hamanodf2c37a2006-01-09 13:13:39 +01003579
Jakub Narebski4df118e2006-10-21 17:53:55 +02003580
3581 print "<div class=\"page_path\">";
3582 print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
Martin Koegler00f429a2007-06-03 17:42:44 +02003583 -title => 'tree root'}, to_utf8("[$project]"));
Jakub Narebski4df118e2006-10-21 17:53:55 +02003584 print " / ";
3585 if (defined $name) {
Jakub Narebski762c7202006-09-04 18:17:58 +02003586 my @dirname = split '/', $name;
3587 my $basename = pop @dirname;
3588 my $fullname = '';
3589
Jakub Narebski762c7202006-09-04 18:17:58 +02003590 foreach my $dir (@dirname) {
Petr Baudis16fdb482006-09-21 02:05:50 +02003591 $fullname .= ($fullname ? '/' : '') . $dir;
Jakub Narebski762c7202006-09-04 18:17:58 +02003592 print $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
3593 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01003594 -title => $fullname}, esc_path($dir));
Petr Baudis26d0a972006-09-23 01:00:12 +02003595 print " / ";
Jakub Narebski762c7202006-09-04 18:17:58 +02003596 }
3597 if (defined $type && $type eq 'blob') {
Jakub Narebski952c65f2006-08-22 16:52:50 +02003598 print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name,
Jakub Narebski762c7202006-09-04 18:17:58 +02003599 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01003600 -title => $name}, esc_path($basename));
Jakub Narebski762c7202006-09-04 18:17:58 +02003601 } elsif (defined $type && $type eq 'tree') {
3602 print $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
3603 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01003604 -title => $name}, esc_path($basename));
Jakub Narebski4df118e2006-10-21 17:53:55 +02003605 print " / ";
Luben Tuikov59fb1c92006-08-17 10:39:29 -07003606 } else {
Jakub Narebski403d0902006-11-08 11:48:56 +01003607 print esc_path($basename);
Luben Tuikov59fb1c92006-08-17 10:39:29 -07003608 }
Kay Sievers8ed23e12005-08-07 20:05:44 +02003609 }
Jakub Narebski4df118e2006-10-21 17:53:55 +02003610 print "<br/></div>\n";
Kay Sievers4c02e3c2005-08-07 19:52:52 +02003611}
3612
Jakub Narebski74fd8722009-05-07 19:11:29 +02003613sub git_print_log {
Jakub Narebskid16d0932006-08-17 11:21:23 +02003614 my $log = shift;
Jakub Narebskib7f92532006-08-28 14:48:10 +02003615 my %opts = @_;
Jakub Narebskid16d0932006-08-17 11:21:23 +02003616
Jakub Narebskib7f92532006-08-28 14:48:10 +02003617 if ($opts{'-remove_title'}) {
3618 # remove title, i.e. first line of log
3619 shift @$log;
3620 }
Jakub Narebskid16d0932006-08-17 11:21:23 +02003621 # remove leading empty lines
3622 while (defined $log->[0] && $log->[0] eq "") {
3623 shift @$log;
3624 }
3625
3626 # print log
3627 my $signoff = 0;
3628 my $empty = 0;
3629 foreach my $line (@$log) {
Jakub Narebskib7f92532006-08-28 14:48:10 +02003630 if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
3631 $signoff = 1;
Jakub Narebskifba20b42006-08-28 14:48:13 +02003632 $empty = 0;
Jakub Narebskib7f92532006-08-28 14:48:10 +02003633 if (! $opts{'-remove_signoff'}) {
3634 print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n";
3635 next;
3636 } else {
3637 # remove signoff lines
3638 next;
3639 }
3640 } else {
3641 $signoff = 0;
3642 }
3643
Jakub Narebskid16d0932006-08-17 11:21:23 +02003644 # print only one empty line
3645 # do not print empty line after signoff
3646 if ($line eq "") {
3647 next if ($empty || $signoff);
3648 $empty = 1;
3649 } else {
3650 $empty = 0;
3651 }
Jakub Narebskib7f92532006-08-28 14:48:10 +02003652
3653 print format_log_line_html($line) . "<br/>\n";
3654 }
3655
3656 if ($opts{'-final_empty_line'}) {
3657 # end with single empty line
3658 print "<br/>\n" unless $empty;
Jakub Narebskid16d0932006-08-17 11:21:23 +02003659 }
3660}
3661
Jakub Narebskie33fba42006-12-10 13:25:46 +01003662# return link target (what link points to)
3663sub git_get_link_target {
3664 my $hash = shift;
3665 my $link_target;
3666
3667 # read link
3668 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
3669 or return;
3670 {
Jakub Narebski34122b52009-05-11 03:29:40 +02003671 local $/ = undef;
Jakub Narebskie33fba42006-12-10 13:25:46 +01003672 $link_target = <$fd>;
3673 }
3674 close $fd
3675 or return;
3676
3677 return $link_target;
3678}
3679
Jakub Narebski3bf9d572006-12-10 13:25:48 +01003680# given link target, and the directory (basedir) the link is in,
3681# return target of link relative to top directory (top tree);
3682# return undef if it is not possible (including absolute links).
3683sub normalize_link_target {
Jakub Narebski15c54fe2009-05-11 19:45:11 +02003684 my ($link_target, $basedir) = @_;
Jakub Narebski3bf9d572006-12-10 13:25:48 +01003685
3686 # absolute symlinks (beginning with '/') cannot be normalized
3687 return if (substr($link_target, 0, 1) eq '/');
3688
3689 # normalize link target to path from top (root) tree (dir)
3690 my $path;
3691 if ($basedir) {
3692 $path = $basedir . '/' . $link_target;
3693 } else {
3694 # we are in top (root) tree (dir)
3695 $path = $link_target;
3696 }
3697
3698 # remove //, /./, and /../
3699 my @path_parts;
3700 foreach my $part (split('/', $path)) {
3701 # discard '.' and ''
3702 next if (!$part || $part eq '.');
3703 # handle '..'
3704 if ($part eq '..') {
3705 if (@path_parts) {
3706 pop @path_parts;
3707 } else {
3708 # link leads outside repository (outside top dir)
3709 return;
3710 }
3711 } else {
3712 push @path_parts, $part;
3713 }
3714 }
3715 $path = join('/', @path_parts);
3716
3717 return $path;
3718}
Jakub Narebskie33fba42006-12-10 13:25:46 +01003719
Jakub Narebskifa702002006-08-31 00:35:07 +02003720# print tree entry (row of git_tree), but without encompassing <tr> element
3721sub git_print_tree_entry {
3722 my ($t, $basedir, $hash_base, $have_blame) = @_;
3723
3724 my %base_key = ();
Jakub Narebskie33fba42006-12-10 13:25:46 +01003725 $base_key{'hash_base'} = $hash_base if defined $hash_base;
Jakub Narebskifa702002006-08-31 00:35:07 +02003726
Luben Tuikov4de741b2006-09-25 22:38:16 -07003727 # The format of a table row is: mode list link. Where mode is
3728 # the mode of the entry, list is the name of the entry, an href,
3729 # and link is the action links of the entry.
3730
Jakub Narebskifa702002006-08-31 00:35:07 +02003731 print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n";
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02003732 if (exists $t->{'size'}) {
3733 print "<td class=\"size\">$t->{'size'}</td>\n";
3734 }
Jakub Narebskifa702002006-08-31 00:35:07 +02003735 if ($t->{'type'} eq "blob") {
3736 print "<td class=\"list\">" .
Luben Tuikov4de741b2006-09-25 22:38:16 -07003737 $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02003738 file_name=>"$basedir$t->{'name'}", %base_key),
Jakub Narebskie33fba42006-12-10 13:25:46 +01003739 -class => "list"}, esc_path($t->{'name'}));
3740 if (S_ISLNK(oct $t->{'mode'})) {
3741 my $link_target = git_get_link_target($t->{'hash'});
3742 if ($link_target) {
Jakub Narebski15c54fe2009-05-11 19:45:11 +02003743 my $norm_target = normalize_link_target($link_target, $basedir);
Jakub Narebski3bf9d572006-12-10 13:25:48 +01003744 if (defined $norm_target) {
3745 print " -> " .
3746 $cgi->a({-href => href(action=>"object", hash_base=>$hash_base,
3747 file_name=>$norm_target),
3748 -title => $norm_target}, esc_path($link_target));
3749 } else {
3750 print " -> " . esc_path($link_target);
3751 }
Jakub Narebskie33fba42006-12-10 13:25:46 +01003752 }
3753 }
3754 print "</td>\n";
Luben Tuikov4de741b2006-09-25 22:38:16 -07003755 print "<td class=\"link\">";
Petr Baudis4777b012006-10-24 05:36:10 +02003756 print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
Jakub Narebskie33fba42006-12-10 13:25:46 +01003757 file_name=>"$basedir$t->{'name'}", %base_key)},
3758 "blob");
Jakub Narebskifa702002006-08-31 00:35:07 +02003759 if ($have_blame) {
Petr Baudis4777b012006-10-24 05:36:10 +02003760 print " | " .
3761 $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
Jakub Narebskie33fba42006-12-10 13:25:46 +01003762 file_name=>"$basedir$t->{'name'}", %base_key)},
3763 "blame");
Jakub Narebskifa702002006-08-31 00:35:07 +02003764 }
3765 if (defined $hash_base) {
Petr Baudis4777b012006-10-24 05:36:10 +02003766 print " | " .
3767 $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
Jakub Narebskifa702002006-08-31 00:35:07 +02003768 hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
3769 "history");
3770 }
3771 print " | " .
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07003772 $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base,
Jakub Narebskie7fb0222006-10-21 17:52:19 +02003773 file_name=>"$basedir$t->{'name'}")},
3774 "raw");
Luben Tuikov4de741b2006-09-25 22:38:16 -07003775 print "</td>\n";
Jakub Narebskifa702002006-08-31 00:35:07 +02003776
3777 } elsif ($t->{'type'} eq "tree") {
Luben Tuikov0fa105e2006-09-26 12:45:37 -07003778 print "<td class=\"list\">";
3779 print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02003780 file_name=>"$basedir$t->{'name'}",
3781 %base_key)},
Jakub Narebski403d0902006-11-08 11:48:56 +01003782 esc_path($t->{'name'}));
Luben Tuikov0fa105e2006-09-26 12:45:37 -07003783 print "</td>\n";
3784 print "<td class=\"link\">";
Petr Baudis4777b012006-10-24 05:36:10 +02003785 print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02003786 file_name=>"$basedir$t->{'name'}",
3787 %base_key)},
Jakub Narebskie33fba42006-12-10 13:25:46 +01003788 "tree");
Jakub Narebskifa702002006-08-31 00:35:07 +02003789 if (defined $hash_base) {
Petr Baudis4777b012006-10-24 05:36:10 +02003790 print " | " .
3791 $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
Jakub Narebskifa702002006-08-31 00:35:07 +02003792 file_name=>"$basedir$t->{'name'}")},
3793 "history");
3794 }
3795 print "</td>\n";
Jakub Narebski01ac1e32007-07-28 16:27:31 +02003796 } else {
3797 # unknown object: we can only present history for it
3798 # (this includes 'commit' object, i.e. submodule support)
3799 print "<td class=\"list\">" .
3800 esc_path($t->{'name'}) .
3801 "</td>\n";
3802 print "<td class=\"link\">";
3803 if (defined $hash_base) {
3804 print $cgi->a({-href => href(action=>"history",
3805 hash_base=>$hash_base,
3806 file_name=>"$basedir$t->{'name'}")},
3807 "history");
3808 }
3809 print "</td>\n";
Jakub Narebskifa702002006-08-31 00:35:07 +02003810 }
3811}
3812
Jakub Narebski717b8312006-07-31 21:22:15 +02003813## ......................................................................
3814## functions printing large fragments of HTML
Kay Sieversede5e102005-08-07 20:23:12 +02003815
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003816# get pre-image filenames for merge (combined) diff
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02003817sub fill_from_file_info {
3818 my ($diff, @parents) = @_;
3819
3820 $diff->{'from_file'} = [ ];
3821 $diff->{'from_file'}[$diff->{'nparents'} - 1] = undef;
3822 for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
3823 if ($diff->{'status'}[$i] eq 'R' ||
3824 $diff->{'status'}[$i] eq 'C') {
3825 $diff->{'from_file'}[$i] =
3826 git_get_path_by_hash($parents[$i], $diff->{'from_id'}[$i]);
3827 }
3828 }
3829
3830 return $diff;
3831}
3832
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003833# is current raw difftree line of file deletion
Jakub Narebski90921742007-06-08 13:27:42 +02003834sub is_deleted {
3835 my $diffinfo = shift;
3836
Jakub Narebski4ed4a342008-04-05 21:13:24 +01003837 return $diffinfo->{'to_id'} eq ('0' x 40);
Jakub Narebski90921742007-06-08 13:27:42 +02003838}
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02003839
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003840# does patch correspond to [previous] difftree raw line
3841# $diffinfo - hashref of parsed raw diff format
3842# $patchinfo - hashref of parsed patch diff format
3843# (the same keys as in $diffinfo)
3844sub is_patch_split {
3845 my ($diffinfo, $patchinfo) = @_;
3846
3847 return defined $diffinfo && defined $patchinfo
Jakub Narebski9d301452007-11-01 12:38:08 +01003848 && $diffinfo->{'to_file'} eq $patchinfo->{'to_file'};
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003849}
3850
3851
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003852sub git_difftree_body {
Jakub Narebskied224de2007-05-07 01:10:04 +02003853 my ($difftree, $hash, @parents) = @_;
3854 my ($parent) = $parents[0];
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08003855 my $have_blame = gitweb_check_feature('blame');
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003856 print "<div class=\"list_head\">\n";
3857 if ($#{$difftree} > 10) {
3858 print(($#{$difftree} + 1) . " files changed:\n");
3859 }
3860 print "</div>\n";
3861
Jakub Narebskied224de2007-05-07 01:10:04 +02003862 print "<table class=\"" .
3863 (@parents > 1 ? "combined " : "") .
3864 "diff_tree\">\n";
Jakub Narebski47598d72007-06-08 13:24:56 +02003865
3866 # header only for combined diff in 'commitdiff' view
Jakub Narebski3ef408a2007-09-08 21:54:28 +02003867 my $has_header = @$difftree && @parents > 1 && $action eq 'commitdiff';
Jakub Narebski47598d72007-06-08 13:24:56 +02003868 if ($has_header) {
3869 # table header
3870 print "<thead><tr>\n" .
3871 "<th></th><th></th>\n"; # filename, patchN link
3872 for (my $i = 0; $i < @parents; $i++) {
3873 my $par = $parents[$i];
3874 print "<th>" .
3875 $cgi->a({-href => href(action=>"commitdiff",
3876 hash=>$hash, hash_parent=>$par),
3877 -title => 'commitdiff to parent number ' .
3878 ($i+1) . ': ' . substr($par,0,7)},
3879 $i+1) .
3880 "&nbsp;</th>\n";
3881 }
3882 print "</tr></thead>\n<tbody>\n";
3883 }
3884
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07003885 my $alternate = 1;
Jakub Narebskib4657e72006-08-28 14:48:14 +02003886 my $patchno = 0;
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003887 foreach my $line (@{$difftree}) {
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003888 my $diff = parsed_difftree_line($line);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003889
3890 if ($alternate) {
3891 print "<tr class=\"dark\">\n";
3892 } else {
3893 print "<tr class=\"light\">\n";
3894 }
3895 $alternate ^= 1;
3896
Jakub Narebski493e01d2007-05-07 01:10:06 +02003897 if (exists $diff->{'nparents'}) { # combined diff
Jakub Narebskied224de2007-05-07 01:10:04 +02003898
Jakub Narebski493e01d2007-05-07 01:10:06 +02003899 fill_from_file_info($diff, @parents)
3900 unless exists $diff->{'from_file'};
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02003901
Jakub Narebski90921742007-06-08 13:27:42 +02003902 if (!is_deleted($diff)) {
Jakub Narebskied224de2007-05-07 01:10:04 +02003903 # file exists in the result (child) commit
3904 print "<td>" .
Jakub Narebski493e01d2007-05-07 01:10:06 +02003905 $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3906 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003907 hash_base=>$hash),
Jakub Narebski493e01d2007-05-07 01:10:06 +02003908 -class => "list"}, esc_path($diff->{'to_file'})) .
Jakub Narebskied224de2007-05-07 01:10:04 +02003909 "</td>\n";
3910 } else {
3911 print "<td>" .
Jakub Narebski493e01d2007-05-07 01:10:06 +02003912 esc_path($diff->{'to_file'}) .
Jakub Narebskied224de2007-05-07 01:10:04 +02003913 "</td>\n";
3914 }
3915
3916 if ($action eq 'commitdiff') {
3917 # link to patch
3918 $patchno++;
3919 print "<td class=\"link\">" .
3920 $cgi->a({-href => "#patch$patchno"}, "patch") .
3921 " | " .
3922 "</td>\n";
3923 }
3924
3925 my $has_history = 0;
3926 my $not_deleted = 0;
Jakub Narebski493e01d2007-05-07 01:10:06 +02003927 for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
Jakub Narebskied224de2007-05-07 01:10:04 +02003928 my $hash_parent = $parents[$i];
Jakub Narebski493e01d2007-05-07 01:10:06 +02003929 my $from_hash = $diff->{'from_id'}[$i];
3930 my $from_path = $diff->{'from_file'}[$i];
3931 my $status = $diff->{'status'}[$i];
Jakub Narebskied224de2007-05-07 01:10:04 +02003932
3933 $has_history ||= ($status ne 'A');
3934 $not_deleted ||= ($status ne 'D');
3935
Jakub Narebskied224de2007-05-07 01:10:04 +02003936 if ($status eq 'A') {
3937 print "<td class=\"link\" align=\"right\"> | </td>\n";
3938 } elsif ($status eq 'D') {
3939 print "<td class=\"link\">" .
3940 $cgi->a({-href => href(action=>"blob",
3941 hash_base=>$hash,
3942 hash=>$from_hash,
3943 file_name=>$from_path)},
3944 "blob" . ($i+1)) .
3945 " | </td>\n";
3946 } else {
Jakub Narebski493e01d2007-05-07 01:10:06 +02003947 if ($diff->{'to_id'} eq $from_hash) {
Jakub Narebskied224de2007-05-07 01:10:04 +02003948 print "<td class=\"link nochange\">";
3949 } else {
3950 print "<td class=\"link\">";
3951 }
3952 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003953 hash=>$diff->{'to_id'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003954 hash_parent=>$from_hash,
3955 hash_base=>$hash,
3956 hash_parent_base=>$hash_parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003957 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003958 file_parent=>$from_path)},
3959 "diff" . ($i+1)) .
3960 " | </td>\n";
3961 }
3962 }
3963
3964 print "<td class=\"link\">";
3965 if ($not_deleted) {
3966 print $cgi->a({-href => href(action=>"blob",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003967 hash=>$diff->{'to_id'},
3968 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003969 hash_base=>$hash)},
3970 "blob");
3971 print " | " if ($has_history);
3972 }
3973 if ($has_history) {
3974 print $cgi->a({-href => href(action=>"history",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003975 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003976 hash_base=>$hash)},
3977 "history");
3978 }
3979 print "</td>\n";
3980
3981 print "</tr>\n";
3982 next; # instead of 'else' clause, to avoid extra indent
3983 }
3984 # else ordinary diff
3985
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003986 my ($to_mode_oct, $to_mode_str, $to_file_type);
3987 my ($from_mode_oct, $from_mode_str, $from_file_type);
Jakub Narebski493e01d2007-05-07 01:10:06 +02003988 if ($diff->{'to_mode'} ne ('0' x 6)) {
3989 $to_mode_oct = oct $diff->{'to_mode'};
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003990 if (S_ISREG($to_mode_oct)) { # only for regular file
3991 $to_mode_str = sprintf("%04o", $to_mode_oct & 0777); # permission bits
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003992 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003993 $to_file_type = file_type($diff->{'to_mode'});
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003994 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003995 if ($diff->{'from_mode'} ne ('0' x 6)) {
3996 $from_mode_oct = oct $diff->{'from_mode'};
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003997 if (S_ISREG($to_mode_oct)) { # only for regular file
3998 $from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits
3999 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004000 $from_file_type = file_type($diff->{'from_mode'});
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004001 }
4002
Jakub Narebski493e01d2007-05-07 01:10:06 +02004003 if ($diff->{'status'} eq "A") { # created
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004004 my $mode_chng = "<span class=\"file_status new\">[new $to_file_type";
4005 $mode_chng .= " with mode: $to_mode_str" if $to_mode_str;
4006 $mode_chng .= "]</span>";
Luben Tuikov499faed2006-09-27 17:23:25 -07004007 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004008 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4009 hash_base=>$hash, file_name=>$diff->{'file'}),
4010 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07004011 print "</td>\n";
4012 print "<td>$mode_chng</td>\n";
4013 print "<td class=\"link\">";
Jakub Narebski72dbafa2006-09-04 18:19:58 +02004014 if ($action eq 'commitdiff') {
Jakub Narebskib4657e72006-08-28 14:48:14 +02004015 # link to patch
4016 $patchno++;
Luben Tuikov499faed2006-09-27 17:23:25 -07004017 print $cgi->a({-href => "#patch$patchno"}, "patch");
Jakub Narebski897d1d22006-11-19 22:51:39 +01004018 print " | ";
Jakub Narebskib4657e72006-08-28 14:48:14 +02004019 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004020 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4021 hash_base=>$hash, file_name=>$diff->{'file'})},
Jakub Narebski3faa5412007-01-08 02:10:42 +01004022 "blob");
Jakub Narebskib4657e72006-08-28 14:48:14 +02004023 print "</td>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004024
Jakub Narebski493e01d2007-05-07 01:10:06 +02004025 } elsif ($diff->{'status'} eq "D") { # deleted
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004026 my $mode_chng = "<span class=\"file_status deleted\">[deleted $from_file_type]</span>";
Luben Tuikov499faed2006-09-27 17:23:25 -07004027 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004028 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
4029 hash_base=>$parent, file_name=>$diff->{'file'}),
4030 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07004031 print "</td>\n";
4032 print "<td>$mode_chng</td>\n";
4033 print "<td class=\"link\">";
Jakub Narebski72dbafa2006-09-04 18:19:58 +02004034 if ($action eq 'commitdiff') {
Jakub Narebskib4657e72006-08-28 14:48:14 +02004035 # link to patch
4036 $patchno++;
Luben Tuikov499faed2006-09-27 17:23:25 -07004037 print $cgi->a({-href => "#patch$patchno"}, "patch");
4038 print " | ";
Jakub Narebskib4657e72006-08-28 14:48:14 +02004039 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004040 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
4041 hash_base=>$parent, file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004042 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004043 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01004044 print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004045 file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004046 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004047 }
Jakub Narebskib4657e72006-08-28 14:48:14 +02004048 print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004049 file_name=>$diff->{'file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02004050 "history");
Luben Tuikov499faed2006-09-27 17:23:25 -07004051 print "</td>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004052
Jakub Narebski493e01d2007-05-07 01:10:06 +02004053 } elsif ($diff->{'status'} eq "M" || $diff->{'status'} eq "T") { # modified, or type changed
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004054 my $mode_chnge = "";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004055 if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004056 $mode_chnge = "<span class=\"file_status mode_chnge\">[changed";
Jakub Narebski6e72cf42007-01-03 20:47:24 +01004057 if ($from_file_type ne $to_file_type) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004058 $mode_chnge .= " from $from_file_type to $to_file_type";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004059 }
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004060 if (($from_mode_oct & 0777) != ($to_mode_oct & 0777)) {
4061 if ($from_mode_str && $to_mode_str) {
4062 $mode_chnge .= " mode: $from_mode_str->$to_mode_str";
4063 } elsif ($to_mode_str) {
4064 $mode_chnge .= " mode: $to_mode_str";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004065 }
4066 }
4067 $mode_chnge .= "]</span>\n";
4068 }
4069 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004070 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4071 hash_base=>$hash, file_name=>$diff->{'file'}),
4072 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07004073 print "</td>\n";
4074 print "<td>$mode_chnge</td>\n";
4075 print "<td class=\"link\">";
Jakub Narebski241cc592006-10-31 17:36:27 +01004076 if ($action eq 'commitdiff') {
4077 # link to patch
4078 $patchno++;
4079 print $cgi->a({-href => "#patch$patchno"}, "patch") .
4080 " | ";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004081 } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
Jakub Narebski241cc592006-10-31 17:36:27 +01004082 # "commit" view and modified file (not onlu mode changed)
4083 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02004084 hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
Jakub Narebski241cc592006-10-31 17:36:27 +01004085 hash_base=>$hash, hash_parent_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004086 file_name=>$diff->{'file'})},
Jakub Narebski241cc592006-10-31 17:36:27 +01004087 "diff") .
4088 " | ";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004089 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004090 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4091 hash_base=>$hash, file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004092 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004093 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01004094 print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004095 file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004096 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004097 }
Luben Tuikoveb51ec92006-09-27 17:24:49 -07004098 print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004099 file_name=>$diff->{'file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02004100 "history");
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004101 print "</td>\n";
4102
Jakub Narebski493e01d2007-05-07 01:10:06 +02004103 } elsif ($diff->{'status'} eq "R" || $diff->{'status'} eq "C") { # renamed or copied
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004104 my %status_name = ('R' => 'moved', 'C' => 'copied');
Jakub Narebski493e01d2007-05-07 01:10:06 +02004105 my $nstatus = $status_name{$diff->{'status'}};
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004106 my $mode_chng = "";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004107 if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004108 # mode also for directories, so we cannot use $to_mode_str
4109 $mode_chng = sprintf(", mode: %04o", $to_mode_oct & 0777);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004110 }
4111 print "<td>" .
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004112 $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004113 hash=>$diff->{'to_id'}, file_name=>$diff->{'to_file'}),
4114 -class => "list"}, esc_path($diff->{'to_file'})) . "</td>\n" .
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004115 "<td><span class=\"file_status $nstatus\">[$nstatus from " .
4116 $cgi->a({-href => href(action=>"blob", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004117 hash=>$diff->{'from_id'}, file_name=>$diff->{'from_file'}),
4118 -class => "list"}, esc_path($diff->{'from_file'})) .
4119 " with " . (int $diff->{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
Luben Tuikov499faed2006-09-27 17:23:25 -07004120 "<td class=\"link\">";
Jakub Narebski241cc592006-10-31 17:36:27 +01004121 if ($action eq 'commitdiff') {
4122 # link to patch
4123 $patchno++;
4124 print $cgi->a({-href => "#patch$patchno"}, "patch") .
4125 " | ";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004126 } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
Jakub Narebski241cc592006-10-31 17:36:27 +01004127 # "commit" view and modified file (not only pure rename or copy)
4128 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02004129 hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
Jakub Narebski241cc592006-10-31 17:36:27 +01004130 hash_base=>$hash, hash_parent_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004131 file_name=>$diff->{'to_file'}, file_parent=>$diff->{'from_file'})},
Jakub Narebski241cc592006-10-31 17:36:27 +01004132 "diff") .
4133 " | ";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004134 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004135 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4136 hash_base=>$parent, file_name=>$diff->{'to_file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004137 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004138 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01004139 print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004140 file_name=>$diff->{'to_file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004141 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004142 }
Jakub Narebski897d1d22006-11-19 22:51:39 +01004143 print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004144 file_name=>$diff->{'to_file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02004145 "history");
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004146 print "</td>\n";
4147
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004148 } # we should not encounter Unmerged (U) or Unknown (X) status
4149 print "</tr>\n";
4150 }
Jakub Narebski47598d72007-06-08 13:24:56 +02004151 print "</tbody>" if $has_header;
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004152 print "</table>\n";
4153}
4154
Jakub Narebskieee08902006-08-24 00:15:14 +02004155sub git_patchset_body {
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02004156 my ($fd, $difftree, $hash, @hash_parents) = @_;
4157 my ($hash_parent) = $hash_parents[0];
Jakub Narebskieee08902006-08-24 00:15:14 +02004158
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004159 my $is_combined = (@hash_parents > 1);
Jakub Narebskieee08902006-08-24 00:15:14 +02004160 my $patch_idx = 0;
Martin Koegler4280cde2007-04-22 22:49:25 -07004161 my $patch_number = 0;
Jakub Narebski6d55f052006-11-18 23:35:39 +01004162 my $patch_line;
Jakub Narebskife875852006-08-25 20:59:39 +02004163 my $diffinfo;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004164 my $to_name;
Jakub Narebski744d0ac2006-11-08 17:59:41 +01004165 my (%from, %to);
Jakub Narebskieee08902006-08-24 00:15:14 +02004166
4167 print "<div class=\"patchset\">\n";
4168
Jakub Narebski6d55f052006-11-18 23:35:39 +01004169 # skip to first patch
4170 while ($patch_line = <$fd>) {
Jakub Narebski157e43b2006-08-24 19:34:36 +02004171 chomp $patch_line;
Jakub Narebskieee08902006-08-24 00:15:14 +02004172
Jakub Narebski6d55f052006-11-18 23:35:39 +01004173 last if ($patch_line =~ m/^diff /);
4174 }
4175
4176 PATCH:
4177 while ($patch_line) {
Jakub Narebski6d55f052006-11-18 23:35:39 +01004178
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004179 # parse "git diff" header line
4180 if ($patch_line =~ m/^diff --git (\"(?:[^\\\"]*(?:\\.[^\\\"]*)*)\"|[^ "]*) (.*)$/) {
4181 # $1 is from_name, which we do not use
4182 $to_name = unquote($2);
4183 $to_name =~ s!^b/!!;
4184 } elsif ($patch_line =~ m/^diff --(cc|combined) ("?.*"?)$/) {
4185 # $1 is 'cc' or 'combined', which we do not use
4186 $to_name = unquote($2);
4187 } else {
4188 $to_name = undef;
Jakub Narebski6d55f052006-11-18 23:35:39 +01004189 }
Jakub Narebski6d55f052006-11-18 23:35:39 +01004190
4191 # check if current patch belong to current raw line
4192 # and parse raw git-diff line if needed
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004193 if (is_patch_split($diffinfo, { 'to_file' => $to_name })) {
Jakub Narebski22065372007-05-12 12:42:32 +02004194 # this is continuation of a split patch
Jakub Narebski6d55f052006-11-18 23:35:39 +01004195 print "<div class=\"patch cont\">\n";
4196 } else {
4197 # advance raw git-diff output if needed
4198 $patch_idx++ if defined $diffinfo;
Jakub Narebskieee08902006-08-24 00:15:14 +02004199
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004200 # read and prepare patch information
4201 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
4202
Jakub Narebskicd030c32007-06-08 13:33:28 +02004203 # compact combined diff output can have some patches skipped
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004204 # find which patch (using pathname of result) we are at now;
4205 if ($is_combined) {
4206 while ($to_name ne $diffinfo->{'to_file'}) {
Jakub Narebskicd030c32007-06-08 13:33:28 +02004207 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
4208 format_diff_cc_simplified($diffinfo, @hash_parents) .
4209 "</div>\n"; # class="patch"
4210
4211 $patch_idx++;
4212 $patch_number++;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004213
4214 last if $patch_idx > $#$difftree;
4215 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
Jakub Narebskicd030c32007-06-08 13:33:28 +02004216 }
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004217 }
Jakub Narebski711fa742007-09-08 21:49:11 +02004218
Jakub Narebski90921742007-06-08 13:27:42 +02004219 # modifies %from, %to hashes
4220 parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents);
Jakub Narebski5f855052007-05-17 22:54:28 +02004221
Jakub Narebski6d55f052006-11-18 23:35:39 +01004222 # this is first patch for raw difftree line with $patch_idx index
4223 # we index @$difftree array from 0, but number patches from 1
4224 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
Jakub Narebski744d0ac2006-11-08 17:59:41 +01004225 }
Jakub Narebskieee08902006-08-24 00:15:14 +02004226
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004227 # git diff header
4228 #assert($patch_line =~ m/^diff /) if DEBUG;
4229 #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
4230 $patch_number++;
Jakub Narebski6d55f052006-11-18 23:35:39 +01004231 # print "git diff" header
Jakub Narebski90921742007-06-08 13:27:42 +02004232 print format_git_diff_header_line($patch_line, $diffinfo,
4233 \%from, \%to);
Jakub Narebskieee08902006-08-24 00:15:14 +02004234
Jakub Narebski6d55f052006-11-18 23:35:39 +01004235 # print extended diff header
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004236 print "<div class=\"diff extended_header\">\n";
Jakub Narebski6d55f052006-11-18 23:35:39 +01004237 EXTENDED_HEADER:
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004238 while ($patch_line = <$fd>) {
4239 chomp $patch_line;
4240
4241 last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /);
4242
Jakub Narebski90921742007-06-08 13:27:42 +02004243 print format_extended_diff_header_line($patch_line, $diffinfo,
4244 \%from, \%to);
Jakub Narebski6d55f052006-11-18 23:35:39 +01004245 }
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004246 print "</div>\n"; # class="diff extended_header"
Jakub Narebskie4e4f822006-08-25 21:04:13 +02004247
Jakub Narebski6d55f052006-11-18 23:35:39 +01004248 # from-file/to-file diff header
Jakub Narebski0bdb28c2007-01-10 00:07:43 +01004249 if (! $patch_line) {
4250 print "</div>\n"; # class="patch"
4251 last PATCH;
4252 }
Jakub Narebski66399ef2007-01-07 02:52:25 +01004253 next PATCH if ($patch_line =~ m/^diff /);
Jakub Narebski6d55f052006-11-18 23:35:39 +01004254 #assert($patch_line =~ m/^---/) if DEBUG;
Jakub Narebski6d55f052006-11-18 23:35:39 +01004255
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004256 my $last_patch_line = $patch_line;
Jakub Narebski6d55f052006-11-18 23:35:39 +01004257 $patch_line = <$fd>;
Jakub Narebski6d55f052006-11-18 23:35:39 +01004258 chomp $patch_line;
Jakub Narebski90921742007-06-08 13:27:42 +02004259 #assert($patch_line =~ m/^\+\+\+/) if DEBUG;
Jakub Narebski6d55f052006-11-18 23:35:39 +01004260
Jakub Narebski90921742007-06-08 13:27:42 +02004261 print format_diff_from_to_header($last_patch_line, $patch_line,
Jakub Narebski91af4ce2007-06-08 13:32:44 +02004262 $diffinfo, \%from, \%to,
4263 @hash_parents);
Jakub Narebski6d55f052006-11-18 23:35:39 +01004264
4265 # the patch itself
4266 LINE:
4267 while ($patch_line = <$fd>) {
4268 chomp $patch_line;
4269
4270 next PATCH if ($patch_line =~ m/^diff /);
4271
Jakub Narebski59e3b142006-11-18 23:35:40 +01004272 print format_diff_line($patch_line, \%from, \%to);
Jakub Narebskieee08902006-08-24 00:15:14 +02004273 }
Jakub Narebskieee08902006-08-24 00:15:14 +02004274
Jakub Narebski6d55f052006-11-18 23:35:39 +01004275 } continue {
4276 print "</div>\n"; # class="patch"
Jakub Narebskieee08902006-08-24 00:15:14 +02004277 }
Jakub Narebskid26c4262007-05-17 00:05:55 +02004278
Jakub Narebskicd030c32007-06-08 13:33:28 +02004279 # for compact combined (--cc) format, with chunk and patch simpliciaction
4280 # patchset might be empty, but there might be unprocessed raw lines
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004281 for (++$patch_idx if $patch_number > 0;
Jakub Narebskicd030c32007-06-08 13:33:28 +02004282 $patch_idx < @$difftree;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004283 ++$patch_idx) {
Jakub Narebskicd030c32007-06-08 13:33:28 +02004284 # read and prepare patch information
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004285 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
Jakub Narebskicd030c32007-06-08 13:33:28 +02004286
4287 # generate anchor for "patch" links in difftree / whatchanged part
4288 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
4289 format_diff_cc_simplified($diffinfo, @hash_parents) .
4290 "</div>\n"; # class="patch"
4291
4292 $patch_number++;
4293 }
4294
Jakub Narebskid26c4262007-05-17 00:05:55 +02004295 if ($patch_number == 0) {
4296 if (@hash_parents > 1) {
4297 print "<div class=\"diff nodifferences\">Trivial merge</div>\n";
4298 } else {
4299 print "<div class=\"diff nodifferences\">No differences found</div>\n";
4300 }
4301 }
Jakub Narebskieee08902006-08-24 00:15:14 +02004302
4303 print "</div>\n"; # class="patchset"
4304}
4305
4306# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4307
Jakub Narebski69913412008-06-10 19:21:01 +02004308# fills project list info (age, description, owner, forks) for each
4309# project in the list, removing invalid projects from returned list
4310# NOTE: modifies $projlist, but does not remove entries from it
4311sub fill_project_list_info {
4312 my ($projlist, $check_forks) = @_;
Petr Baudise30496d2006-10-24 05:33:17 +02004313 my @projects;
Jakub Narebski69913412008-06-10 19:21:01 +02004314
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004315 my $show_ctags = gitweb_check_feature('ctags');
Jakub Narebski69913412008-06-10 19:21:01 +02004316 PROJECT:
Petr Baudise30496d2006-10-24 05:33:17 +02004317 foreach my $pr (@$projlist) {
Jakub Narebski69913412008-06-10 19:21:01 +02004318 my (@activity) = git_get_last_activity($pr->{'path'});
4319 unless (@activity) {
4320 next PROJECT;
Petr Baudise30496d2006-10-24 05:33:17 +02004321 }
Jakub Narebski69913412008-06-10 19:21:01 +02004322 ($pr->{'age'}, $pr->{'age_string'}) = @activity;
Petr Baudise30496d2006-10-24 05:33:17 +02004323 if (!defined $pr->{'descr'}) {
4324 my $descr = git_get_project_description($pr->{'path'}) || "";
Jakub Narebski69913412008-06-10 19:21:01 +02004325 $descr = to_utf8($descr);
4326 $pr->{'descr_long'} = $descr;
Michael Hendricks55feb122007-07-04 18:36:48 -06004327 $pr->{'descr'} = chop_str($descr, $projects_list_description_width, 5);
Petr Baudise30496d2006-10-24 05:33:17 +02004328 }
4329 if (!defined $pr->{'owner'}) {
Miklos Vajna76e4f5d2007-07-04 00:11:23 +02004330 $pr->{'owner'} = git_get_project_owner("$pr->{'path'}") || "";
Petr Baudise30496d2006-10-24 05:33:17 +02004331 }
4332 if ($check_forks) {
4333 my $pname = $pr->{'path'};
Junio C Hamano83ee94c2006-11-07 22:37:17 -08004334 if (($pname =~ s/\.git$//) &&
4335 ($pname !~ /\/$/) &&
4336 (-d "$projectroot/$pname")) {
4337 $pr->{'forks'} = "-d $projectroot/$pname";
Jakub Narebski3278fbc2009-05-11 19:37:28 +02004338 } else {
Junio C Hamano83ee94c2006-11-07 22:37:17 -08004339 $pr->{'forks'} = 0;
4340 }
Petr Baudise30496d2006-10-24 05:33:17 +02004341 }
Petr Baudisaed93de2008-10-02 17:13:02 +02004342 $show_ctags and $pr->{'ctags'} = git_get_project_ctags($pr->{'path'});
Petr Baudise30496d2006-10-24 05:33:17 +02004343 push @projects, $pr;
4344 }
4345
Jakub Narebski69913412008-06-10 19:21:01 +02004346 return @projects;
4347}
4348
Petr Baudis6b28da62008-09-25 18:48:37 +02004349# print 'sort by' <th> element, generating 'sort by $name' replay link
4350# if that order is not selected
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02004351sub print_sort_th {
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01004352 print format_sort_th(@_);
4353}
4354
4355sub format_sort_th {
Petr Baudis6b28da62008-09-25 18:48:37 +02004356 my ($name, $order, $header) = @_;
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01004357 my $sort_th = "";
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02004358 $header ||= ucfirst($name);
4359
4360 if ($order eq $name) {
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01004361 $sort_th .= "<th>$header</th>\n";
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02004362 } else {
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01004363 $sort_th .= "<th>" .
4364 $cgi->a({-href => href(-replay=>1, order=>$name),
4365 -class => "header"}, $header) .
4366 "</th>\n";
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02004367 }
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01004368
4369 return $sort_th;
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02004370}
4371
Jakub Narebski69913412008-06-10 19:21:01 +02004372sub git_project_list_body {
Petr Baudis42326112008-10-02 17:17:01 +02004373 # actually uses global variable $project
Jakub Narebski69913412008-06-10 19:21:01 +02004374 my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
4375
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004376 my $check_forks = gitweb_check_feature('forks');
Jakub Narebski69913412008-06-10 19:21:01 +02004377 my @projects = fill_project_list_info($projlist, $check_forks);
4378
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02004379 $order ||= $default_projects_order;
Petr Baudise30496d2006-10-24 05:33:17 +02004380 $from = 0 unless defined $from;
4381 $to = $#projects if (!defined $to || $#projects < $to);
4382
Petr Baudis6b28da62008-09-25 18:48:37 +02004383 my %order_info = (
4384 project => { key => 'path', type => 'str' },
4385 descr => { key => 'descr_long', type => 'str' },
4386 owner => { key => 'owner', type => 'str' },
4387 age => { key => 'age', type => 'num' }
4388 );
4389 my $oi = $order_info{$order};
4390 if ($oi->{'type'} eq 'str') {
4391 @projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @projects;
4392 } else {
4393 @projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects;
4394 }
4395
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004396 my $show_ctags = gitweb_check_feature('ctags');
Petr Baudisaed93de2008-10-02 17:13:02 +02004397 if ($show_ctags) {
4398 my %ctags;
4399 foreach my $p (@projects) {
4400 foreach my $ct (keys %{$p->{'ctags'}}) {
4401 $ctags{$ct} += $p->{'ctags'}->{$ct};
4402 }
4403 }
4404 my $cloud = git_populate_project_tagcloud(\%ctags);
4405 print git_show_project_tagcloud($cloud, 64);
4406 }
4407
Petr Baudise30496d2006-10-24 05:33:17 +02004408 print "<table class=\"project_list\">\n";
4409 unless ($no_header) {
4410 print "<tr>\n";
4411 if ($check_forks) {
4412 print "<th></th>\n";
4413 }
Petr Baudis6b28da62008-09-25 18:48:37 +02004414 print_sort_th('project', $order, 'Project');
4415 print_sort_th('descr', $order, 'Description');
4416 print_sort_th('owner', $order, 'Owner');
4417 print_sort_th('age', $order, 'Last Change');
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02004418 print "<th></th>\n" . # for links
Petr Baudise30496d2006-10-24 05:33:17 +02004419 "</tr>\n";
4420 }
4421 my $alternate = 1;
Petr Baudisaed93de2008-10-02 17:13:02 +02004422 my $tagfilter = $cgi->param('by_tag');
Petr Baudise30496d2006-10-24 05:33:17 +02004423 for (my $i = $from; $i <= $to; $i++) {
4424 my $pr = $projects[$i];
Petr Baudis42326112008-10-02 17:17:01 +02004425
Petr Baudisaed93de2008-10-02 17:13:02 +02004426 next if $tagfilter and $show_ctags and not grep { lc $_ eq lc $tagfilter } keys %{$pr->{'ctags'}};
Petr Baudis0d1d1542008-10-03 09:29:45 +02004427 next if $searchtext and not $pr->{'path'} =~ /$searchtext/
4428 and not $pr->{'descr_long'} =~ /$searchtext/;
4429 # Weed out forks or non-matching entries of search
Petr Baudis42326112008-10-02 17:17:01 +02004430 if ($check_forks) {
4431 my $forkbase = $project; $forkbase ||= ''; $forkbase =~ s#\.git$#/#;
4432 $forkbase="^$forkbase" if $forkbase;
Petr Baudis0d1d1542008-10-03 09:29:45 +02004433 next if not $searchtext and not $tagfilter and $show_ctags
4434 and $pr->{'path'} =~ m#$forkbase.*/.*#; # regexp-safe
Petr Baudis42326112008-10-02 17:17:01 +02004435 }
4436
Petr Baudise30496d2006-10-24 05:33:17 +02004437 if ($alternate) {
4438 print "<tr class=\"dark\">\n";
4439 } else {
4440 print "<tr class=\"light\">\n";
4441 }
4442 $alternate ^= 1;
4443 if ($check_forks) {
4444 print "<td>";
4445 if ($pr->{'forks'}) {
Junio C Hamano83ee94c2006-11-07 22:37:17 -08004446 print "<!-- $pr->{'forks'} -->\n";
Petr Baudise30496d2006-10-24 05:33:17 +02004447 print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+");
4448 }
4449 print "</td>\n";
4450 }
4451 print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
4452 -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
Jakub Narebskie88ce8a2006-11-26 02:18:26 +01004453 "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
4454 -class => "list", -title => $pr->{'descr_long'}},
4455 esc_html($pr->{'descr'})) . "</td>\n" .
David Symondsd3cd2492007-10-23 11:31:23 +10004456 "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
Petr Baudise30496d2006-10-24 05:33:17 +02004457 print "<td class=\"". age_class($pr->{'age'}) . "\">" .
Jakub Narebski785cdea2007-05-13 12:39:22 +02004458 (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" .
Petr Baudise30496d2006-10-24 05:33:17 +02004459 "<td class=\"link\">" .
4460 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " .
Alexandre Julliardfaa1bbf2006-11-15 21:37:50 +01004461 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
Petr Baudise30496d2006-10-24 05:33:17 +02004462 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
4463 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
4464 ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') .
4465 "</td>\n" .
4466 "</tr>\n";
4467 }
4468 if (defined $extra) {
4469 print "<tr>\n";
4470 if ($check_forks) {
4471 print "<td></td>\n";
4472 }
4473 print "<td colspan=\"5\">$extra</td>\n" .
4474 "</tr>\n";
4475 }
4476 print "</table>\n";
4477}
4478
Jakub Narebski42671ca2009-11-13 02:02:12 +01004479sub git_log_body {
4480 # uses global variable $project
4481 my ($commitlist, $from, $to, $refs, $extra) = @_;
4482
4483 $from = 0 unless defined $from;
4484 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
4485
4486 for (my $i = 0; $i <= $to; $i++) {
4487 my %co = %{$commitlist->[$i]};
4488 next if !%co;
4489 my $commit = $co{'id'};
4490 my $ref = format_ref_marker($refs, $commit);
4491 my %ad = parse_date($co{'author_epoch'});
4492 git_print_header_div('commit',
4493 "<span class=\"age\">$co{'age_string'}</span>" .
4494 esc_html($co{'title'}) . $ref,
4495 $commit);
4496 print "<div class=\"title_text\">\n" .
4497 "<div class=\"log_link\">\n" .
4498 $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") .
4499 " | " .
4500 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") .
4501 " | " .
4502 $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
4503 "<br/>\n" .
4504 "</div>\n";
4505 git_print_authorship(\%co, -tag => 'span');
4506 print "<br/>\n</div>\n";
4507
4508 print "<div class=\"log_body\">\n";
4509 git_print_log($co{'comment'}, -final_empty_line=> 1);
4510 print "</div>\n";
4511 }
4512 if ($extra) {
4513 print "<div class=\"page_nav\">\n";
4514 print "$extra\n";
4515 print "</div>\n";
4516 }
4517}
4518
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004519sub git_shortlog_body {
4520 # uses global variable $project
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00004521 my ($commitlist, $from, $to, $refs, $extra) = @_;
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05304522
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004523 $from = 0 unless defined $from;
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00004524 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004525
Jakub Narebski591ebf62007-11-19 14:16:11 +01004526 print "<table class=\"shortlog\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004527 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004528 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00004529 my %co = %{$commitlist->[$i]};
4530 my $commit = $co{'id'};
Jakub Narebski847e01f2006-08-14 02:05:47 +02004531 my $ref = format_ref_marker($refs, $commit);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004532 if ($alternate) {
4533 print "<tr class=\"dark\">\n";
4534 } else {
4535 print "<tr class=\"light\">\n";
4536 }
4537 $alternate ^= 1;
4538 # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
4539 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004540 format_author_html('td', \%co, 10) . "<td>";
Jakub Narebski952c65f2006-08-22 16:52:50 +02004541 print format_subject_html($co{'title'}, $co{'title_short'},
4542 href(action=>"commit", hash=>$commit), $ref);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004543 print "</td>\n" .
4544 "<td class=\"link\">" .
Petr Baudis4777b012006-10-24 05:36:10 +02004545 $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
Petr Baudis35749ae2006-09-22 03:19:44 +02004546 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
Petr Baudis55ff35c2006-10-06 15:57:52 +02004547 $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004548 my $snapshot_links = format_snapshot_links($commit);
4549 if (defined $snapshot_links) {
4550 print " | " . $snapshot_links;
Petr Baudis55ff35c2006-10-06 15:57:52 +02004551 }
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304552 print "</td>\n" .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004553 "</tr>\n";
4554 }
4555 if (defined $extra) {
4556 print "<tr>\n" .
4557 "<td colspan=\"4\">$extra</td>\n" .
4558 "</tr>\n";
4559 }
4560 print "</table>\n";
4561}
4562
Jakub Narebski581860e2006-08-14 02:09:08 +02004563sub git_history_body {
4564 # Warning: assumes constant type (blob or tree) during history
Jakub Narebski69ca37d2009-11-13 02:02:14 +01004565 my ($commitlist, $from, $to, $refs, $extra,
4566 $file_name, $file_hash, $ftype) = @_;
Jakub Narebski8be68352006-09-11 00:36:04 +02004567
4568 $from = 0 unless defined $from;
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00004569 $to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist});
Jakub Narebski581860e2006-08-14 02:09:08 +02004570
Jakub Narebski591ebf62007-11-19 14:16:11 +01004571 print "<table class=\"history\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004572 my $alternate = 1;
Jakub Narebski8be68352006-09-11 00:36:04 +02004573 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00004574 my %co = %{$commitlist->[$i]};
Jakub Narebski581860e2006-08-14 02:09:08 +02004575 if (!%co) {
4576 next;
4577 }
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00004578 my $commit = $co{'id'};
Jakub Narebski581860e2006-08-14 02:09:08 +02004579
4580 my $ref = format_ref_marker($refs, $commit);
4581
4582 if ($alternate) {
4583 print "<tr class=\"dark\">\n";
4584 } else {
4585 print "<tr class=\"light\">\n";
4586 }
4587 $alternate ^= 1;
4588 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004589 # shortlog: format_author_html('td', \%co, 10)
4590 format_author_html('td', \%co, 15, 3) . "<td>";
Jakub Narebski581860e2006-08-14 02:09:08 +02004591 # originally git_history used chop_str($co{'title'}, 50)
Jakub Narebski952c65f2006-08-22 16:52:50 +02004592 print format_subject_html($co{'title'}, $co{'title_short'},
4593 href(action=>"commit", hash=>$commit), $ref);
Jakub Narebski581860e2006-08-14 02:09:08 +02004594 print "</td>\n" .
4595 "<td class=\"link\">" .
Luben Tuikov6d81c5a2006-09-28 17:21:07 -07004596 $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype) . " | " .
4597 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
Jakub Narebski581860e2006-08-14 02:09:08 +02004598
4599 if ($ftype eq 'blob') {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01004600 my $blob_current = $file_hash;
Jakub Narebski581860e2006-08-14 02:09:08 +02004601 my $blob_parent = git_get_hash_by_path($commit, $file_name);
4602 if (defined $blob_current && defined $blob_parent &&
4603 $blob_current ne $blob_parent) {
4604 print " | " .
Jakub Narebski420e92f2006-08-24 23:53:54 +02004605 $cgi->a({-href => href(action=>"blobdiff",
4606 hash=>$blob_current, hash_parent=>$blob_parent,
4607 hash_base=>$hash_base, hash_parent_base=>$commit,
4608 file_name=>$file_name)},
Jakub Narebski581860e2006-08-14 02:09:08 +02004609 "diff to current");
4610 }
4611 }
4612 print "</td>\n" .
4613 "</tr>\n";
4614 }
4615 if (defined $extra) {
4616 print "<tr>\n" .
4617 "<td colspan=\"4\">$extra</td>\n" .
4618 "</tr>\n";
4619 }
4620 print "</table>\n";
4621}
4622
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004623sub git_tags_body {
4624 # uses global variable $project
4625 my ($taglist, $from, $to, $extra) = @_;
4626 $from = 0 unless defined $from;
4627 $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
4628
Jakub Narebski591ebf62007-11-19 14:16:11 +01004629 print "<table class=\"tags\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004630 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004631 for (my $i = $from; $i <= $to; $i++) {
4632 my $entry = $taglist->[$i];
4633 my %tag = %$entry;
Jakub Narebskicd146402006-11-02 20:23:11 +01004634 my $comment = $tag{'subject'};
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004635 my $comment_short;
4636 if (defined $comment) {
4637 $comment_short = chop_str($comment, 30, 5);
4638 }
4639 if ($alternate) {
4640 print "<tr class=\"dark\">\n";
4641 } else {
4642 print "<tr class=\"light\">\n";
4643 }
4644 $alternate ^= 1;
Jakub Narebski27dd1a82007-01-03 22:54:29 +01004645 if (defined $tag{'age'}) {
4646 print "<td><i>$tag{'age'}</i></td>\n";
4647 } else {
4648 print "<td></td>\n";
4649 }
4650 print "<td>" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004651 $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'}),
Jakub Narebski63e42202006-08-22 12:38:59 +02004652 -class => "list name"}, esc_html($tag{'name'})) .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004653 "</td>\n" .
4654 "<td>";
4655 if (defined $comment) {
Jakub Narebski952c65f2006-08-22 16:52:50 +02004656 print format_subject_html($comment, $comment_short,
4657 href(action=>"tag", hash=>$tag{'id'}));
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004658 }
4659 print "</td>\n" .
4660 "<td class=\"selflink\">";
4661 if ($tag{'type'} eq "tag") {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004662 print $cgi->a({-href => href(action=>"tag", hash=>$tag{'id'})}, "tag");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004663 } else {
4664 print "&nbsp;";
4665 }
4666 print "</td>\n" .
4667 "<td class=\"link\">" . " | " .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004668 $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'});
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004669 if ($tag{'reftype'} eq "commit") {
Jakub Narebskibf901f82007-12-15 15:40:28 +01004670 print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'fullname'})}, "shortlog") .
4671 " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'fullname'})}, "log");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004672 } elsif ($tag{'reftype'} eq "blob") {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004673 print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004674 }
4675 print "</td>\n" .
4676 "</tr>";
4677 }
4678 if (defined $extra) {
4679 print "<tr>\n" .
4680 "<td colspan=\"5\">$extra</td>\n" .
4681 "</tr>\n";
4682 }
4683 print "</table>\n";
4684}
4685
4686sub git_heads_body {
4687 # uses global variable $project
Jakub Narebski120ddde2006-09-19 14:33:22 +02004688 my ($headlist, $head, $from, $to, $extra) = @_;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004689 $from = 0 unless defined $from;
Jakub Narebski120ddde2006-09-19 14:33:22 +02004690 $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004691
Jakub Narebski591ebf62007-11-19 14:16:11 +01004692 print "<table class=\"heads\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004693 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004694 for (my $i = $from; $i <= $to; $i++) {
Jakub Narebski120ddde2006-09-19 14:33:22 +02004695 my $entry = $headlist->[$i];
Jakub Narebskicd146402006-11-02 20:23:11 +01004696 my %ref = %$entry;
4697 my $curr = $ref{'id'} eq $head;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004698 if ($alternate) {
4699 print "<tr class=\"dark\">\n";
4700 } else {
4701 print "<tr class=\"light\">\n";
4702 }
4703 $alternate ^= 1;
Jakub Narebskicd146402006-11-02 20:23:11 +01004704 print "<td><i>$ref{'age'}</i></td>\n" .
4705 ($curr ? "<td class=\"current_head\">" : "<td>") .
Jakub Narebskibf901f82007-12-15 15:40:28 +01004706 $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'}),
Jakub Narebskicd146402006-11-02 20:23:11 +01004707 -class => "list name"},esc_html($ref{'name'})) .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004708 "</td>\n" .
4709 "<td class=\"link\">" .
Jakub Narebskibf901f82007-12-15 15:40:28 +01004710 $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'})}, "shortlog") . " | " .
4711 $cgi->a({-href => href(action=>"log", hash=>$ref{'fullname'})}, "log") . " | " .
4712 $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'name'})}, "tree") .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004713 "</td>\n" .
4714 "</tr>";
4715 }
4716 if (defined $extra) {
4717 print "<tr>\n" .
4718 "<td colspan=\"3\">$extra</td>\n" .
4719 "</tr>\n";
4720 }
4721 print "</table>\n";
4722}
4723
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004724sub git_search_grep_body {
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00004725 my ($commitlist, $from, $to, $extra) = @_;
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004726 $from = 0 unless defined $from;
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00004727 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004728
Jakub Narebski591ebf62007-11-19 14:16:11 +01004729 print "<table class=\"commit_search\">\n";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004730 my $alternate = 1;
4731 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00004732 my %co = %{$commitlist->[$i]};
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004733 if (!%co) {
4734 next;
4735 }
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00004736 my $commit = $co{'id'};
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004737 if ($alternate) {
4738 print "<tr class=\"dark\">\n";
4739 } else {
4740 print "<tr class=\"light\">\n";
4741 }
4742 $alternate ^= 1;
4743 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004744 format_author_html('td', \%co, 15, 5) .
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004745 "<td>" .
Junio C Hamanobe8b9062008-02-22 17:33:47 +01004746 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
4747 -class => "list subject"},
4748 chop_and_escape_str($co{'title'}, 50) . "<br/>");
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004749 my $comment = $co{'comment'};
4750 foreach my $line (@$comment) {
Jakub Narebski6dfbb302008-03-02 16:57:14 +01004751 if ($line =~ m/^(.*?)($search_regexp)(.*)$/i) {
Junio C Hamanobe8b9062008-02-22 17:33:47 +01004752 my ($lead, $match, $trail) = ($1, $2, $3);
Jakub Narebskib8d97d02008-02-25 21:07:57 +01004753 $match = chop_str($match, 70, 5, 'center');
4754 my $contextlen = int((80 - length($match))/2);
4755 $contextlen = 30 if ($contextlen > 30);
4756 $lead = chop_str($lead, $contextlen, 10, 'left');
4757 $trail = chop_str($trail, $contextlen, 10, 'right');
Junio C Hamanobe8b9062008-02-22 17:33:47 +01004758
4759 $lead = esc_html($lead);
4760 $match = esc_html($match);
4761 $trail = esc_html($trail);
4762
4763 print "$lead<span class=\"match\">$match</span>$trail<br />";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004764 }
4765 }
4766 print "</td>\n" .
4767 "<td class=\"link\">" .
4768 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
4769 " | " .
Denis Chengf1fe8f52007-11-26 20:42:06 +08004770 $cgi->a({-href => href(action=>"commitdiff", hash=>$co{'id'})}, "commitdiff") .
4771 " | " .
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004772 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
4773 print "</td>\n" .
4774 "</tr>\n";
4775 }
4776 if (defined $extra) {
4777 print "<tr>\n" .
4778 "<td colspan=\"3\">$extra</td>\n" .
4779 "</tr>\n";
4780 }
4781 print "</table>\n";
4782}
4783
Jakub Narebski717b8312006-07-31 21:22:15 +02004784## ======================================================================
4785## ======================================================================
4786## actions
4787
Jakub Narebski717b8312006-07-31 21:22:15 +02004788sub git_project_list {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02004789 my $order = $input_params{'order'};
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02004790 if (defined $order && $order !~ m/none|project|descr|owner|age/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004791 die_error(400, "Unknown order parameter");
Jakub Narebski6326b602006-08-01 02:59:12 +02004792 }
4793
Jakub Narebski847e01f2006-08-14 02:05:47 +02004794 my @list = git_get_projects_list();
Jakub Narebski717b8312006-07-31 21:22:15 +02004795 if (!@list) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004796 die_error(404, "No projects found");
Jakub Narebski717b8312006-07-31 21:22:15 +02004797 }
Jakub Narebski6326b602006-08-01 02:59:12 +02004798
Jakub Narebski717b8312006-07-31 21:22:15 +02004799 git_header_html();
John 'Warthog9' Hawley24d4afc2010-01-30 23:30:41 +01004800 if (defined $home_text && -f $home_text) {
Jakub Narebski717b8312006-07-31 21:22:15 +02004801 print "<div class=\"index_include\">\n";
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01004802 insert_file($home_text);
Jakub Narebski717b8312006-07-31 21:22:15 +02004803 print "</div>\n";
4804 }
Petr Baudis0d1d1542008-10-03 09:29:45 +02004805 print $cgi->startform(-method => "get") .
4806 "<p class=\"projsearch\">Search:\n" .
4807 $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
4808 "</p>" .
4809 $cgi->end_form() . "\n";
Petr Baudise30496d2006-10-24 05:33:17 +02004810 git_project_list_body(\@list, $order);
4811 git_footer_html();
4812}
4813
4814sub git_forks {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02004815 my $order = $input_params{'order'};
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02004816 if (defined $order && $order !~ m/none|project|descr|owner|age/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004817 die_error(400, "Unknown order parameter");
Jakub Narebski717b8312006-07-31 21:22:15 +02004818 }
Petr Baudise30496d2006-10-24 05:33:17 +02004819
4820 my @list = git_get_projects_list($project);
4821 if (!@list) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004822 die_error(404, "No forks found");
Jakub Narebski717b8312006-07-31 21:22:15 +02004823 }
Petr Baudise30496d2006-10-24 05:33:17 +02004824
4825 git_header_html();
4826 git_print_page_nav('','');
4827 git_print_header_div('summary', "$project forks");
4828 git_project_list_body(\@list, $order);
Jakub Narebski717b8312006-07-31 21:22:15 +02004829 git_footer_html();
4830}
4831
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02004832sub git_project_index {
Petr Baudise30496d2006-10-24 05:33:17 +02004833 my @projects = git_get_projects_list($project);
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02004834
4835 print $cgi->header(
4836 -type => 'text/plain',
4837 -charset => 'utf-8',
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02004838 -content_disposition => 'inline; filename="index.aux"');
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02004839
4840 foreach my $pr (@projects) {
4841 if (!exists $pr->{'owner'}) {
Miklos Vajna76e4f5d2007-07-04 00:11:23 +02004842 $pr->{'owner'} = git_get_project_owner("$pr->{'path'}");
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02004843 }
4844
4845 my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'});
4846 # quote as in CGI::Util::encode, but keep the slash, and use '+' for ' '
4847 $path =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
4848 $owner =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
4849 $path =~ s/ /\+/g;
4850 $owner =~ s/ /\+/g;
4851
4852 print "$path $owner\n";
4853 }
4854}
4855
Kay Sieversede5e102005-08-07 20:23:12 +02004856sub git_summary {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004857 my $descr = git_get_project_description($project) || "none";
Robert Fitzsimonsa979d122006-12-22 19:38:15 +00004858 my %co = parse_commit("HEAD");
Jakub Narebski785cdea2007-05-13 12:39:22 +02004859 my %cd = %co ? parse_date($co{'committer_epoch'}, $co{'committer_tz'}) : ();
Robert Fitzsimonsa979d122006-12-22 19:38:15 +00004860 my $head = $co{'id'};
Kay Sieversede5e102005-08-07 20:23:12 +02004861
Jakub Narebski1e0cf032006-08-14 02:10:06 +02004862 my $owner = git_get_project_owner($project);
Kay Sieversede5e102005-08-07 20:23:12 +02004863
Jakub Narebskicd146402006-11-02 20:23:11 +01004864 my $refs = git_get_references();
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00004865 # These get_*_list functions return one more to allow us to see if
4866 # there are more ...
4867 my @taglist = git_get_tags_list(16);
4868 my @headlist = git_get_heads_list(16);
Petr Baudise30496d2006-10-24 05:33:17 +02004869 my @forklist;
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004870 my $check_forks = gitweb_check_feature('forks');
Junio C Hamano5dd5ed02006-11-07 22:00:45 -08004871
4872 if ($check_forks) {
Petr Baudise30496d2006-10-24 05:33:17 +02004873 @forklist = git_get_projects_list($project);
4874 }
Jakub Narebski120ddde2006-09-19 14:33:22 +02004875
Kay Sieversede5e102005-08-07 20:23:12 +02004876 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02004877 git_print_page_nav('summary','', $head);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004878
Kay Sievers19806692005-08-07 20:26:27 +02004879 print "<div class=\"title\">&nbsp;</div>\n";
Jakub Narebski591ebf62007-11-19 14:16:11 +01004880 print "<table class=\"projects_list\">\n" .
Petr Baudisa4761422008-10-02 16:25:05 +02004881 "<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
4882 "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
Jakub Narebski785cdea2007-05-13 12:39:22 +02004883 if (defined $cd{'rfc2822'}) {
Petr Baudisa4761422008-10-02 16:25:05 +02004884 print "<tr id=\"metadata_lchange\"><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n";
Jakub Narebski785cdea2007-05-13 12:39:22 +02004885 }
4886
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02004887 # use per project git URL list in $projectroot/$project/cloneurl
4888 # or make project git URL from git base URL and project name
Jakub Narebski19a87212006-08-15 23:03:17 +02004889 my $url_tag = "URL";
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02004890 my @url_list = git_get_project_url_list($project);
4891 @url_list = map { "$_/$project" } @git_base_url_list unless @url_list;
4892 foreach my $git_url (@url_list) {
4893 next unless $git_url;
Petr Baudisa4761422008-10-02 16:25:05 +02004894 print "<tr class=\"metadata_url\"><td>$url_tag</td><td>$git_url</td></tr>\n";
Jakub Narebski19a87212006-08-15 23:03:17 +02004895 $url_tag = "";
4896 }
Petr Baudisaed93de2008-10-02 17:13:02 +02004897
4898 # Tag cloud
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004899 my $show_ctags = gitweb_check_feature('ctags');
Petr Baudisaed93de2008-10-02 17:13:02 +02004900 if ($show_ctags) {
4901 my $ctags = git_get_project_ctags($project);
4902 my $cloud = git_populate_project_tagcloud($ctags);
4903 print "<tr id=\"metadata_ctags\"><td>Content tags:<br />";
4904 print "</td>\n<td>" unless %$ctags;
4905 print "<form action=\"$show_ctags\" method=\"post\"><input type=\"hidden\" name=\"p\" value=\"$project\" />Add: <input type=\"text\" name=\"t\" size=\"8\" /></form>";
4906 print "</td>\n<td>" if %$ctags;
4907 print git_show_project_tagcloud($cloud, 48);
4908 print "</td></tr>";
4909 }
4910
Jakub Narebski19a87212006-08-15 23:03:17 +02004911 print "</table>\n";
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004912
Matt McCutchen7e1100e2009-02-07 19:00:09 -05004913 # If XSS prevention is on, we don't include README.html.
4914 # TODO: Allow a readme in some safe format.
4915 if (!$prevent_xss && -s "$projectroot/$project/README.html") {
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01004916 print "<div class=\"title\">readme</div>\n" .
4917 "<div class=\"readme\">\n";
4918 insert_file("$projectroot/$project/README.html");
4919 print "\n</div>\n"; # class="readme"
Petr Baudis447ef092006-10-24 05:23:46 +02004920 }
4921
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00004922 # we need to request one more than 16 (0..15) to check if
4923 # those 16 are all
Jakub Narebski785cdea2007-05-13 12:39:22 +02004924 my @commitlist = $head ? parse_commits($head, 17) : ();
4925 if (@commitlist) {
4926 git_print_header_div('shortlog');
4927 git_shortlog_body(\@commitlist, 0, 15, $refs,
4928 $#commitlist <= 15 ? undef :
4929 $cgi->a({-href => href(action=>"shortlog")}, "..."));
4930 }
Kay Sieversede5e102005-08-07 20:23:12 +02004931
Jakub Narebski120ddde2006-09-19 14:33:22 +02004932 if (@taglist) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004933 git_print_header_div('tags');
Jakub Narebski120ddde2006-09-19 14:33:22 +02004934 git_tags_body(\@taglist, 0, 15,
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00004935 $#taglist <= 15 ? undef :
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004936 $cgi->a({-href => href(action=>"tags")}, "..."));
Kay Sieversede5e102005-08-07 20:23:12 +02004937 }
Kay Sievers0db37972005-08-07 20:24:35 +02004938
Jakub Narebski120ddde2006-09-19 14:33:22 +02004939 if (@headlist) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004940 git_print_header_div('heads');
Jakub Narebski120ddde2006-09-19 14:33:22 +02004941 git_heads_body(\@headlist, $head, 0, 15,
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00004942 $#headlist <= 15 ? undef :
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004943 $cgi->a({-href => href(action=>"heads")}, "..."));
Kay Sievers0db37972005-08-07 20:24:35 +02004944 }
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004945
Petr Baudise30496d2006-10-24 05:33:17 +02004946 if (@forklist) {
4947 git_print_header_div('forks');
Mike Ralphsonf04f27e2008-09-25 18:48:48 +02004948 git_project_list_body(\@forklist, 'age', 0, 15,
Robert Fitzsimonsaaca9672006-12-22 19:38:12 +00004949 $#forklist <= 15 ? undef :
Petr Baudise30496d2006-10-24 05:33:17 +02004950 $cgi->a({-href => href(action=>"forks")}, "..."),
Mike Ralphsonf04f27e2008-09-25 18:48:48 +02004951 'no_header');
Petr Baudise30496d2006-10-24 05:33:17 +02004952 }
4953
Kay Sieversede5e102005-08-07 20:23:12 +02004954 git_footer_html();
4955}
4956
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004957sub git_tag {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004958 my $head = git_get_head_hash($project);
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004959 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02004960 git_print_page_nav('','', $head,undef,$head);
4961 my %tag = parse_tag($hash);
Jakub Narebski198a2a82007-05-12 21:16:34 +02004962
4963 if (! %tag) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004964 die_error(404, "Unknown tag object");
Jakub Narebski198a2a82007-05-12 21:16:34 +02004965 }
4966
Jakub Narebski847e01f2006-08-14 02:05:47 +02004967 git_print_header_div('commit', esc_html($tag{'name'}), $hash);
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004968 print "<div class=\"title_text\">\n" .
Jakub Narebski591ebf62007-11-19 14:16:11 +01004969 "<table class=\"object_header\">\n" .
Kay Sieverse4669df2005-08-08 00:02:39 +02004970 "<tr>\n" .
4971 "<td>object</td>\n" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004972 "<td>" . $cgi->a({-class => "list", -href => href(action=>$tag{'type'}, hash=>$tag{'object'})},
4973 $tag{'object'}) . "</td>\n" .
4974 "<td class=\"link\">" . $cgi->a({-href => href(action=>$tag{'type'}, hash=>$tag{'object'})},
4975 $tag{'type'}) . "</td>\n" .
Kay Sieverse4669df2005-08-08 00:02:39 +02004976 "</tr>\n";
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004977 if (defined($tag{'author'})) {
Giuseppe Bilottaba924732009-06-30 00:00:50 +02004978 git_print_authorship_rows(\%tag, 'author');
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004979 }
4980 print "</table>\n\n" .
4981 "</div>\n";
4982 print "<div class=\"page_body\">";
4983 my $comment = $tag{'comment'};
4984 foreach my $line (@$comment) {
Junio C Hamano70022432006-11-24 14:04:01 -08004985 chomp $line;
Jakub Narebski793c4002006-11-24 22:25:50 +01004986 print esc_html($line, -nbsp=>1) . "<br/>\n";
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004987 }
4988 print "</div>\n";
4989 git_footer_html();
4990}
4991
Jakub Narebski4af819d2009-09-01 13:39:17 +02004992sub git_blame_common {
4993 my $format = shift || 'porcelain';
Jakub Narebskic4ccf612009-09-01 13:39:19 +02004994 if ($format eq 'porcelain' && $cgi->param('js')) {
4995 $format = 'incremental';
4996 $action = 'blame_incremental'; # for page title etc
4997 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02004998
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01004999 # permissions
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08005000 gitweb_check_feature('blame')
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005001 or die_error(403, "Blame view not allowed");
Lea Wiemann074afaa2008-06-19 22:03:21 +02005002
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005003 # error checking
Lea Wiemann074afaa2008-06-19 22:03:21 +02005004 die_error(400, "No file name given") unless $file_name;
Jakub Narebski847e01f2006-08-14 02:05:47 +02005005 $hash_base ||= git_get_head_hash($project);
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005006 die_error(404, "Couldn't find base commit") unless $hash_base;
Jakub Narebski847e01f2006-08-14 02:05:47 +02005007 my %co = parse_commit($hash_base)
Lea Wiemann074afaa2008-06-19 22:03:21 +02005008 or die_error(404, "Commit not found");
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005009 my $ftype = "blob";
Luben Tuikov1f2857e2006-07-23 13:34:55 -07005010 if (!defined $hash) {
5011 $hash = git_get_hash_by_path($hash_base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02005012 or die_error(404, "Error looking up file");
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005013 } else {
5014 $ftype = git_get_type($hash);
5015 if ($ftype !~ "blob") {
5016 die_error(400, "Object is not a blob");
5017 }
Luben Tuikov1f2857e2006-07-23 13:34:55 -07005018 }
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005019
Jakub Narebski4af819d2009-09-01 13:39:17 +02005020 my $fd;
5021 if ($format eq 'incremental') {
5022 # get file contents (as base)
5023 open $fd, "-|", git_cmd(), 'cat-file', 'blob', $hash
5024 or die_error(500, "Open git-cat-file failed");
5025 } elsif ($format eq 'data') {
5026 # run git-blame --incremental
5027 open $fd, "-|", git_cmd(), "blame", "--incremental",
5028 $hash_base, "--", $file_name
5029 or die_error(500, "Open git-blame --incremental failed");
5030 } else {
5031 # run git-blame --porcelain
5032 open $fd, "-|", git_cmd(), "blame", '-p',
5033 $hash_base, '--', $file_name
5034 or die_error(500, "Open git-blame --porcelain failed");
5035 }
5036
5037 # incremental blame data returns early
5038 if ($format eq 'data') {
5039 print $cgi->header(
5040 -type=>"text/plain", -charset => "utf-8",
5041 -status=> "200 OK");
5042 local $| = 1; # output autoflush
5043 print while <$fd>;
5044 close $fd
5045 or print "ERROR $!\n";
5046
5047 print 'END';
5048 if (defined $t0 && gitweb_check_feature('timed')) {
5049 print ' '.
5050 Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
5051 ' '.$number_of_git_cmds;
5052 }
5053 print "\n";
5054
5055 return;
5056 }
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005057
5058 # page header
Luben Tuikov1f2857e2006-07-23 13:34:55 -07005059 git_header_html();
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02005060 my $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01005061 $cgi->a({-href => href(action=>"blob", -replay=>1)},
Jakub Narebski952c65f2006-08-22 16:52:50 +02005062 "blob") .
Jakub Narebski87e573f2009-12-01 17:54:26 +01005063 " | ";
5064 if ($format eq 'incremental') {
5065 $formats_nav .=
5066 $cgi->a({-href => href(action=>"blame", javascript=>0, -replay=>1)},
5067 "blame") . " (non-incremental)";
5068 } else {
5069 $formats_nav .=
5070 $cgi->a({-href => href(action=>"blame_incremental", -replay=>1)},
5071 "blame") . " (incremental)";
5072 }
5073 $formats_nav .=
Jakub Narebski952c65f2006-08-22 16:52:50 +02005074 " | " .
Jakub Narebskia3823e52007-11-01 13:06:29 +01005075 $cgi->a({-href => href(action=>"history", -replay=>1)},
5076 "history") .
Petr Baudiscae18622006-09-22 03:19:41 +02005077 " | " .
Jakub Narebski4af819d2009-09-01 13:39:17 +02005078 $cgi->a({-href => href(action=>$action, file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02005079 "HEAD");
Jakub Narebski847e01f2006-08-14 02:05:47 +02005080 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
5081 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
Luben Tuikov59fb1c92006-08-17 10:39:29 -07005082 git_print_page_path($file_name, $ftype, $hash_base);
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005083
5084 # page body
Jakub Narebski4af819d2009-09-01 13:39:17 +02005085 if ($format eq 'incremental') {
5086 print "<noscript>\n<div class=\"error\"><center><b>\n".
5087 "This page requires JavaScript to run.\n Use ".
Jakub Narebskic4ccf612009-09-01 13:39:19 +02005088 $cgi->a({-href => href(action=>'blame',javascript=>0,-replay=>1)},
Jakub Narebski4af819d2009-09-01 13:39:17 +02005089 'this page').
5090 " instead.\n".
5091 "</b></center></div>\n</noscript>\n";
5092
5093 print qq!<div id="progress_bar" style="width: 100%; background-color: yellow"></div>\n!;
5094 }
5095
5096 print qq!<div class="page_body">\n!;
5097 print qq!<div id="progress_info">... / ...</div>\n!
5098 if ($format eq 'incremental');
5099 print qq!<table id="blame_table" class="blame" width="100%">\n!.
5100 #qq!<col width="5.5em" /><col width="2.5em" /><col width="*" />\n!.
5101 qq!<thead>\n!.
5102 qq!<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n!.
5103 qq!</thead>\n!.
5104 qq!<tbody>\n!;
5105
Jakub Narebskiaef37682009-07-25 00:44:06 +02005106 my @rev_color = qw(light dark);
Luben Tuikovcc1bf972006-07-23 13:37:53 -07005107 my $num_colors = scalar(@rev_color);
5108 my $current_color = 0;
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005109
Jakub Narebski4af819d2009-09-01 13:39:17 +02005110 if ($format eq 'incremental') {
5111 my $color_class = $rev_color[$current_color];
5112
5113 #contents of a file
5114 my $linenr = 0;
5115 LINE:
5116 while (my $line = <$fd>) {
5117 chomp $line;
5118 $linenr++;
5119
5120 print qq!<tr id="l$linenr" class="$color_class">!.
5121 qq!<td class="sha1"><a href=""> </a></td>!.
5122 qq!<td class="linenr">!.
5123 qq!<a class="linenr" href="">$linenr</a></td>!;
5124 print qq!<td class="pre">! . esc_html($line) . "</td>\n";
5125 print qq!</tr>\n!;
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07005126 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02005127
5128 } else { # porcelain, i.e. ordinary blame
5129 my %metainfo = (); # saves information about commits
5130
5131 # blame data
5132 LINE:
5133 while (my $line = <$fd>) {
5134 chomp $line;
5135 # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
5136 # no <lines in group> for subsequent lines in group of lines
5137 my ($full_rev, $orig_lineno, $lineno, $group_size) =
5138 ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
5139 if (!exists $metainfo{$full_rev}) {
5140 $metainfo{$full_rev} = { 'nprevious' => 0 };
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07005141 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02005142 my $meta = $metainfo{$full_rev};
5143 my $data;
5144 while ($data = <$fd>) {
5145 chomp $data;
5146 last if ($data =~ s/^\t//); # contents of line
5147 if ($data =~ /^(\S+)(?: (.*))?$/) {
5148 $meta->{$1} = $2 unless exists $meta->{$1};
5149 }
5150 if ($data =~ /^previous /) {
5151 $meta->{'nprevious'}++;
Jakub Narebskia36817b2009-07-25 00:44:05 +02005152 }
5153 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02005154 my $short_rev = substr($full_rev, 0, 8);
5155 my $author = $meta->{'author'};
5156 my %date =
5157 parse_date($meta->{'author-time'}, $meta->{'author-tz'});
5158 my $date = $date{'iso-tz'};
5159 if ($group_size) {
5160 $current_color = ($current_color + 1) % $num_colors;
5161 }
5162 my $tr_class = $rev_color[$current_color];
5163 $tr_class .= ' boundary' if (exists $meta->{'boundary'});
5164 $tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0);
5165 $tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1);
5166 print "<tr id=\"l$lineno\" class=\"$tr_class\">\n";
5167 if ($group_size) {
5168 print "<td class=\"sha1\"";
5169 print " title=\"". esc_html($author) . ", $date\"";
5170 print " rowspan=\"$group_size\"" if ($group_size > 1);
5171 print ">";
5172 print $cgi->a({-href => href(action=>"commit",
5173 hash=>$full_rev,
5174 file_name=>$file_name)},
5175 esc_html($short_rev));
5176 if ($group_size >= 2) {
5177 my @author_initials = ($author =~ /\b([[:upper:]])\B/g);
5178 if (@author_initials) {
5179 print "<br />" .
5180 esc_html(join('', @author_initials));
5181 # or join('.', ...)
5182 }
5183 }
5184 print "</td>\n";
5185 }
5186 # 'previous' <sha1 of parent commit> <filename at commit>
5187 if (exists $meta->{'previous'} &&
5188 $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) {
5189 $meta->{'parent'} = $1;
5190 $meta->{'file_parent'} = unquote($2);
5191 }
5192 my $linenr_commit =
5193 exists($meta->{'parent'}) ?
5194 $meta->{'parent'} : $full_rev;
5195 my $linenr_filename =
5196 exists($meta->{'file_parent'}) ?
5197 $meta->{'file_parent'} : unquote($meta->{'filename'});
5198 my $blamed = href(action => 'blame',
5199 file_name => $linenr_filename,
5200 hash_base => $linenr_commit);
5201 print "<td class=\"linenr\">";
5202 print $cgi->a({ -href => "$blamed#l$orig_lineno",
5203 -class => "linenr" },
5204 esc_html($lineno));
5205 print "</td>";
5206 print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
5207 print "</tr>\n";
5208 } # end while
5209
Luben Tuikov1f2857e2006-07-23 13:34:55 -07005210 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02005211
5212 # footer
5213 print "</tbody>\n".
5214 "</table>\n"; # class="blame"
5215 print "</div>\n"; # class="blame_body"
Jakub Narebski952c65f2006-08-22 16:52:50 +02005216 close $fd
5217 or print "Reading blob failed\n";
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005218
Luben Tuikov1f2857e2006-07-23 13:34:55 -07005219 git_footer_html();
5220}
5221
Jakub Narebski4af819d2009-09-01 13:39:17 +02005222sub git_blame {
5223 git_blame_common();
5224}
5225
5226sub git_blame_incremental {
5227 git_blame_common('incremental');
5228}
5229
5230sub git_blame_data {
5231 git_blame_common('data');
5232}
5233
Kay Sieversede5e102005-08-07 20:23:12 +02005234sub git_tags {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005235 my $head = git_get_head_hash($project);
Kay Sieversede5e102005-08-07 20:23:12 +02005236 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02005237 git_print_page_nav('','', $head,undef,$head);
5238 git_print_header_div('summary', $project);
Jakub Narebski27fb8c42006-07-30 20:32:01 +02005239
Jakub Narebskicd146402006-11-02 20:23:11 +01005240 my @tagslist = git_get_tags_list();
5241 if (@tagslist) {
5242 git_tags_body(\@tagslist);
Kay Sieversede5e102005-08-07 20:23:12 +02005243 }
Kay Sieversede5e102005-08-07 20:23:12 +02005244 git_footer_html();
5245}
5246
Kay Sieversd8f1c5c2005-10-04 01:12:47 +02005247sub git_heads {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005248 my $head = git_get_head_hash($project);
Kay Sievers0db37972005-08-07 20:24:35 +02005249 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02005250 git_print_page_nav('','', $head,undef,$head);
5251 git_print_header_div('summary', $project);
Jakub Narebski27fb8c42006-07-30 20:32:01 +02005252
Jakub Narebskicd146402006-11-02 20:23:11 +01005253 my @headslist = git_get_heads_list();
5254 if (@headslist) {
5255 git_heads_body(\@headslist, $head);
Kay Sievers0db37972005-08-07 20:24:35 +02005256 }
Kay Sievers0db37972005-08-07 20:24:35 +02005257 git_footer_html();
5258}
5259
Luben Tuikov930cf7d2006-07-09 20:18:57 -07005260sub git_blob_plain {
Jakub Narebski7f718e82008-06-03 16:47:10 +02005261 my $type = shift;
Jakub Narebskif2e73302006-08-26 19:14:25 +02005262 my $expires;
Jakub Narebskif2e73302006-08-26 19:14:25 +02005263
Luben Tuikovcff07712006-07-23 13:28:55 -07005264 if (!defined $hash) {
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005265 if (defined $file_name) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005266 my $base = $hash_base || git_get_head_hash($project);
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005267 $hash = git_get_hash_by_path($base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02005268 or die_error(404, "Cannot find file");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005269 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005270 die_error(400, "No file name defined");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005271 }
Martin Waitz800764c2006-09-16 23:09:02 +02005272 } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
5273 # blobs defined by non-textual hash id's can be cached
5274 $expires = "+1d";
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005275 }
Martin Waitz800764c2006-09-16 23:09:02 +02005276
Dennis Stosberg25691fb2006-08-28 17:49:58 +02005277 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
Lea Wiemann074afaa2008-06-19 22:03:21 +02005278 or die_error(500, "Open git-cat-file blob '$hash' failed");
Luben Tuikov930cf7d2006-07-09 20:18:57 -07005279
Jakub Narebski7f718e82008-06-03 16:47:10 +02005280 # content-type (can include charset)
5281 $type = blob_contenttype($fd, $file_name, $type);
Luben Tuikov930cf7d2006-07-09 20:18:57 -07005282
Jakub Narebski7f718e82008-06-03 16:47:10 +02005283 # "save as" filename, even when no $file_name is given
Luben Tuikov930cf7d2006-07-09 20:18:57 -07005284 my $save_as = "$hash";
5285 if (defined $file_name) {
5286 $save_as = $file_name;
5287 } elsif ($type =~ m/^text\//) {
5288 $save_as .= '.txt';
5289 }
5290
Matt McCutchen7e1100e2009-02-07 19:00:09 -05005291 # With XSS prevention on, blobs of all types except a few known safe
5292 # ones are served with "Content-Disposition: attachment" to make sure
5293 # they don't run in our security domain. For certain image types,
5294 # blob view writes an <img> tag referring to blob_plain view, and we
5295 # want to be sure not to break that by serving the image as an
5296 # attachment (though Firefox 3 doesn't seem to care).
5297 my $sandbox = $prevent_xss &&
5298 $type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))$!;
5299
Jakub Narebskif2e73302006-08-26 19:14:25 +02005300 print $cgi->header(
Jakub Narebski7f718e82008-06-03 16:47:10 +02005301 -type => $type,
5302 -expires => $expires,
Matt McCutchen7e1100e2009-02-07 19:00:09 -05005303 -content_disposition =>
5304 ($sandbox ? 'attachment' : 'inline')
5305 . '; filename="' . $save_as . '"');
Jakub Narebski34122b52009-05-11 03:29:40 +02005306 local $/ = undef;
Luben Tuikov930cf7d2006-07-09 20:18:57 -07005307 binmode STDOUT, ':raw';
5308 print <$fd>;
5309 binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
Luben Tuikov930cf7d2006-07-09 20:18:57 -07005310 close $fd;
5311}
5312
Kay Sievers09bd7892005-08-07 20:21:23 +02005313sub git_blob {
Jakub Narebskif2e73302006-08-26 19:14:25 +02005314 my $expires;
Jakub Narebskif2e73302006-08-26 19:14:25 +02005315
Luben Tuikovcff07712006-07-23 13:28:55 -07005316 if (!defined $hash) {
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005317 if (defined $file_name) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005318 my $base = $hash_base || git_get_head_hash($project);
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005319 $hash = git_get_hash_by_path($base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02005320 or die_error(404, "Cannot find file");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005321 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005322 die_error(400, "No file name defined");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005323 }
Martin Waitz800764c2006-09-16 23:09:02 +02005324 } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
5325 # blobs defined by non-textual hash id's can be cached
5326 $expires = "+1d";
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005327 }
Martin Waitz800764c2006-09-16 23:09:02 +02005328
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08005329 my $have_blame = gitweb_check_feature('blame');
Dennis Stosberg25691fb2006-08-28 17:49:58 +02005330 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
Lea Wiemann074afaa2008-06-19 22:03:21 +02005331 or die_error(500, "Couldn't cat $file_name, $hash");
Jakub Narebski847e01f2006-08-14 02:05:47 +02005332 my $mimetype = blob_mimetype($fd, $file_name);
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01005333 if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)! && -B $fd) {
Luben Tuikov930cf7d2006-07-09 20:18:57 -07005334 close $fd;
5335 return git_blob_plain($mimetype);
5336 }
Jakub Narebski5a4cf332006-12-04 23:47:22 +01005337 # we can have blame only for text/* mimetype
5338 $have_blame &&= ($mimetype =~ m!^text/!);
5339
Jakub Narebskif2e73302006-08-26 19:14:25 +02005340 git_header_html(undef, $expires);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02005341 my $formats_nav = '';
Jakub Narebski847e01f2006-08-14 02:05:47 +02005342 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
Kay Sievers93129442005-10-17 03:27:54 +02005343 if (defined $file_name) {
Florian Forster5996ca02006-06-12 10:31:57 +02005344 if ($have_blame) {
Jakub Narebski952c65f2006-08-22 16:52:50 +02005345 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01005346 $cgi->a({-href => href(action=>"blame", -replay=>1)},
Jakub Narebski952c65f2006-08-22 16:52:50 +02005347 "blame") .
5348 " | ";
Florian Forster5996ca02006-06-12 10:31:57 +02005349 }
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02005350 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01005351 $cgi->a({-href => href(action=>"history", -replay=>1)},
Petr Baudiscae18622006-09-22 03:19:41 +02005352 "history") .
5353 " | " .
Jakub Narebskia3823e52007-11-01 13:06:29 +01005354 $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
Petr Baudis35329cc2006-09-22 03:19:50 +02005355 "raw") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02005356 " | " .
5357 $cgi->a({-href => href(action=>"blob",
5358 hash_base=>"HEAD", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02005359 "HEAD");
Kay Sievers93129442005-10-17 03:27:54 +02005360 } else {
Jakub Narebski952c65f2006-08-22 16:52:50 +02005361 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01005362 $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
5363 "raw");
Kay Sievers93129442005-10-17 03:27:54 +02005364 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02005365 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
5366 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
Kay Sievers09bd7892005-08-07 20:21:23 +02005367 } else {
5368 print "<div class=\"page_nav\">\n" .
5369 "<br/><br/></div>\n" .
5370 "<div class=\"title\">$hash</div>\n";
5371 }
Luben Tuikov59fb1c92006-08-17 10:39:29 -07005372 git_print_page_path($file_name, "blob", $hash_base);
Kay Sieversc07ad4b2005-08-07 20:22:44 +02005373 print "<div class=\"page_body\">\n";
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01005374 if ($mimetype =~ m!^image/!) {
Jakub Narebski5a4cf332006-12-04 23:47:22 +01005375 print qq!<img type="$mimetype"!;
5376 if ($file_name) {
5377 print qq! alt="$file_name" title="$file_name"!;
5378 }
5379 print qq! src="! .
5380 href(action=>"blob_plain", hash=>$hash,
5381 hash_base=>$hash_base, file_name=>$file_name) .
5382 qq!" />\n!;
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01005383 } else {
5384 my $nr;
5385 while (my $line = <$fd>) {
5386 chomp $line;
5387 $nr++;
5388 $line = untabify($line);
Petr Baudisb9759f02009-11-06 16:08:41 +01005389 printf "<div class=\"pre\"><a id=\"l%i\" href=\"" . href(-replay => 1)
5390 . "#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01005391 $nr, $nr, $nr, esc_html($line, -nbsp=>1);
5392 }
Kay Sievers161332a2005-08-07 19:49:46 +02005393 }
Jakub Narebski952c65f2006-08-22 16:52:50 +02005394 close $fd
5395 or print "Reading blob failed.\n";
Kay Sieversfbb592a2005-08-07 20:12:11 +02005396 print "</div>";
Kay Sievers12a88f22005-08-07 20:02:47 +02005397 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02005398}
5399
5400sub git_tree {
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07005401 if (!defined $hash_base) {
5402 $hash_base = "HEAD";
5403 }
Kay Sieversb87d78d2005-08-07 20:21:04 +02005404 if (!defined $hash) {
Kay Sievers09bd7892005-08-07 20:21:23 +02005405 if (defined $file_name) {
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07005406 $hash = git_get_hash_by_path($hash_base, $file_name, "tree");
5407 } else {
5408 $hash = $hash_base;
Kay Sievers10dba282005-08-07 20:25:27 +02005409 }
Kay Sieverse925f382005-08-07 20:23:35 +02005410 }
Jakub Narebski2d7a3532008-10-02 16:50:04 +02005411 die_error(404, "No such tree") unless defined($hash);
Jakub Narebski34122b52009-05-11 03:29:40 +02005412
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02005413 my $show_sizes = gitweb_check_feature('show-sizes');
5414 my $have_blame = gitweb_check_feature('blame');
5415
Jakub Narebski34122b52009-05-11 03:29:40 +02005416 my @entries = ();
5417 {
5418 local $/ = "\0";
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02005419 open my $fd, "-|", git_cmd(), "ls-tree", '-z',
5420 ($show_sizes ? '-l' : ()), @extra_options, $hash
Jakub Narebski34122b52009-05-11 03:29:40 +02005421 or die_error(500, "Open git-ls-tree failed");
5422 @entries = map { chomp; $_ } <$fd>;
5423 close $fd
5424 or die_error(404, "Reading tree failed");
5425 }
Kay Sieversd63577d2005-08-07 20:18:13 +02005426
Jakub Narebski847e01f2006-08-14 02:05:47 +02005427 my $refs = git_get_references();
5428 my $ref = format_ref_marker($refs, $hash_base);
Kay Sievers12a88f22005-08-07 20:02:47 +02005429 git_header_html();
Jakub Narebski300454f2006-10-21 17:53:09 +02005430 my $basedir = '';
Jakub Narebski847e01f2006-08-14 02:05:47 +02005431 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
Petr Baudiscae18622006-09-22 03:19:41 +02005432 my @views_nav = ();
5433 if (defined $file_name) {
5434 push @views_nav,
Jakub Narebskia3823e52007-11-01 13:06:29 +01005435 $cgi->a({-href => href(action=>"history", -replay=>1)},
Petr Baudiscae18622006-09-22 03:19:41 +02005436 "history"),
5437 $cgi->a({-href => href(action=>"tree",
5438 hash_base=>"HEAD", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02005439 "HEAD"),
Petr Baudiscae18622006-09-22 03:19:41 +02005440 }
Matt McCutchena3c8ab32007-07-22 01:30:27 +02005441 my $snapshot_links = format_snapshot_links($hash);
5442 if (defined $snapshot_links) {
Petr Baudiscae18622006-09-22 03:19:41 +02005443 # FIXME: Should be available when we have no hash base as well.
Matt McCutchena3c8ab32007-07-22 01:30:27 +02005444 push @views_nav, $snapshot_links;
Petr Baudiscae18622006-09-22 03:19:41 +02005445 }
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02005446 git_print_page_nav('tree','', $hash_base, undef, undef,
5447 join(' | ', @views_nav));
Jakub Narebski847e01f2006-08-14 02:05:47 +02005448 git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
Kay Sieversd63577d2005-08-07 20:18:13 +02005449 } else {
Jakub Narebskifa702002006-08-31 00:35:07 +02005450 undef $hash_base;
Kay Sieversd63577d2005-08-07 20:18:13 +02005451 print "<div class=\"page_nav\">\n";
5452 print "<br/><br/></div>\n";
5453 print "<div class=\"title\">$hash</div>\n";
5454 }
Kay Sievers09bd7892005-08-07 20:21:23 +02005455 if (defined $file_name) {
Jakub Narebski300454f2006-10-21 17:53:09 +02005456 $basedir = $file_name;
5457 if ($basedir ne '' && substr($basedir, -1) ne '/') {
5458 $basedir .= '/';
5459 }
Jakub Narebski2d7a3532008-10-02 16:50:04 +02005460 git_print_page_path($file_name, 'tree', $hash_base);
Kay Sievers09bd7892005-08-07 20:21:23 +02005461 }
Kay Sieversfbb592a2005-08-07 20:12:11 +02005462 print "<div class=\"page_body\">\n";
Jakub Narebski591ebf62007-11-19 14:16:11 +01005463 print "<table class=\"tree\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07005464 my $alternate = 1;
Jakub Narebskib6b7fc72006-10-21 17:54:44 +02005465 # '..' (top directory) link if possible
5466 if (defined $hash_base &&
5467 defined $file_name && $file_name =~ m![^/]+$!) {
5468 if ($alternate) {
5469 print "<tr class=\"dark\">\n";
5470 } else {
5471 print "<tr class=\"light\">\n";
5472 }
5473 $alternate ^= 1;
5474
5475 my $up = $file_name;
5476 $up =~ s!/?[^/]+$!!;
5477 undef $up unless $up;
5478 # based on git_print_tree_entry
5479 print '<td class="mode">' . mode_str('040000') . "</td>\n";
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02005480 print '<td class="size">&nbsp;</td>'."\n" if $show_sizes;
Jakub Narebskib6b7fc72006-10-21 17:54:44 +02005481 print '<td class="list">';
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02005482 print $cgi->a({-href => href(action=>"tree",
5483 hash_base=>$hash_base,
Jakub Narebskib6b7fc72006-10-21 17:54:44 +02005484 file_name=>$up)},
5485 "..");
5486 print "</td>\n";
5487 print "<td class=\"link\"></td>\n";
5488
5489 print "</tr>\n";
5490 }
Kay Sievers161332a2005-08-07 19:49:46 +02005491 foreach my $line (@entries) {
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02005492 my %t = parse_ls_tree_line($line, -z => 1, -l => $show_sizes);
Jakub Narebskicb849b42006-08-31 00:32:15 +02005493
Kay Sieversbddec012005-08-07 20:25:42 +02005494 if ($alternate) {
Kay Sieversc994d622005-08-07 20:27:18 +02005495 print "<tr class=\"dark\">\n";
Kay Sieversbddec012005-08-07 20:25:42 +02005496 } else {
Kay Sieversc994d622005-08-07 20:27:18 +02005497 print "<tr class=\"light\">\n";
Kay Sieversbddec012005-08-07 20:25:42 +02005498 }
5499 $alternate ^= 1;
Jakub Narebskicb849b42006-08-31 00:32:15 +02005500
Jakub Narebski300454f2006-10-21 17:53:09 +02005501 git_print_tree_entry(\%t, $basedir, $hash_base, $have_blame);
Jakub Narebskifa702002006-08-31 00:35:07 +02005502
Kay Sievers42f7eb92005-08-07 20:21:46 +02005503 print "</tr>\n";
Kay Sievers161332a2005-08-07 19:49:46 +02005504 }
Kay Sievers42f7eb92005-08-07 20:21:46 +02005505 print "</table>\n" .
5506 "</div>";
Kay Sievers12a88f22005-08-07 20:02:47 +02005507 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02005508}
5509
Mark Radab6292752009-11-07 16:13:29 +01005510sub snapshot_name {
5511 my ($project, $hash) = @_;
5512
5513 # path/to/project.git -> project
5514 # path/to/project/.git -> project
5515 my $name = to_utf8($project);
5516 $name =~ s,([^/])/*\.git$,$1,;
5517 $name = basename($name);
5518 # sanitize name
5519 $name =~ s/[[:cntrl:]]/?/g;
5520
5521 my $ver = $hash;
5522 if ($hash =~ /^[0-9a-fA-F]+$/) {
5523 # shorten SHA-1 hash
5524 my $full_hash = git_get_full_hash($project, $hash);
5525 if ($full_hash =~ /^$hash/ && length($hash) > 7) {
5526 $ver = git_get_short_hash($project, $hash);
5527 }
5528 } elsif ($hash =~ m!^refs/tags/(.*)$!) {
5529 # tags don't need shortened SHA-1 hash
5530 $ver = $1;
5531 } else {
5532 # branches and other need shortened SHA-1 hash
5533 if ($hash =~ m!^refs/(?:heads|remotes)/(.*)$!) {
5534 $ver = $1;
5535 }
5536 $ver .= '-' . git_get_short_hash($project, $hash);
5537 }
5538 # in case of hierarchical branch names
5539 $ver =~ s!/!.!g;
5540
5541 # name = project-version_string
5542 $name = "$name-$ver";
5543
5544 return wantarray ? ($name, $name) : $name;
5545}
5546
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305547sub git_snapshot {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02005548 my $format = $input_params{'snapshot_format'};
Giuseppe Bilotta5e166842008-11-02 10:21:37 +01005549 if (!@snapshot_fmts) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005550 die_error(403, "Snapshots not allowed");
Jakub Narebski3473e7d2007-07-25 01:19:58 +02005551 }
5552 # default to first supported snapshot format
Giuseppe Bilotta5e166842008-11-02 10:21:37 +01005553 $format ||= $snapshot_fmts[0];
Jakub Narebski3473e7d2007-07-25 01:19:58 +02005554 if ($format !~ m/^[a-z0-9]+$/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005555 die_error(400, "Invalid snapshot format parameter");
Jakub Narebski3473e7d2007-07-25 01:19:58 +02005556 } elsif (!exists($known_snapshot_formats{$format})) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005557 die_error(400, "Unknown snapshot format");
Mark A Rada1bfd3632009-08-06 10:25:39 -04005558 } elsif ($known_snapshot_formats{$format}{'disabled'}) {
5559 die_error(403, "Snapshot format not allowed");
Mark Rada34b31a82009-08-25 00:59:48 -04005560 } elsif (!grep($_ eq $format, @snapshot_fmts)) {
5561 die_error(403, "Unsupported snapshot format");
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05305562 }
5563
Mark Radafdb0c362009-09-26 13:46:08 -04005564 my $type = git_get_type("$hash^{}");
5565 if (!$type) {
5566 die_error(404, 'Object does not exist');
5567 } elsif ($type eq 'blob') {
5568 die_error(400, 'Object is not a tree-ish');
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305569 }
5570
Mark Radab6292752009-11-07 16:13:29 +01005571 my ($name, $prefix) = snapshot_name($project, $hash);
5572 my $filename = "$name$known_snapshot_formats{$format}{'suffix'}";
5573 my $cmd = quote_command(
Lea Wiemann516381d2008-06-17 23:46:35 +02005574 git_cmd(), 'archive',
5575 "--format=$known_snapshot_formats{$format}{'format'}",
Mark Radab6292752009-11-07 16:13:29 +01005576 "--prefix=$prefix/", $hash);
Matt McCutchena3c8ab32007-07-22 01:30:27 +02005577 if (exists $known_snapshot_formats{$format}{'compressor'}) {
Lea Wiemann516381d2008-06-17 23:46:35 +02005578 $cmd .= ' | ' . quote_command(@{$known_snapshot_formats{$format}{'compressor'}});
Mark Levedahl072570e2007-05-20 11:46:46 -04005579 }
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305580
Mark Radab6292752009-11-07 16:13:29 +01005581 $filename =~ s/(["\\])/\\$1/g;
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02005582 print $cgi->header(
Matt McCutchena3c8ab32007-07-22 01:30:27 +02005583 -type => $known_snapshot_formats{$format}{'type'},
Mark Radab6292752009-11-07 16:13:29 +01005584 -content_disposition => 'inline; filename="' . $filename . '"',
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02005585 -status => '200 OK');
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305586
Mark Levedahl072570e2007-05-20 11:46:46 -04005587 open my $fd, "-|", $cmd
Lea Wiemann074afaa2008-06-19 22:03:21 +02005588 or die_error(500, "Execute git-archive failed");
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305589 binmode STDOUT, ':raw';
5590 print <$fd>;
5591 binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
5592 close $fd;
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305593}
5594
Jakub Narebski15f0b112009-11-13 02:02:13 +01005595sub git_log_generic {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005596 my ($fmt_name, $body_subr, $base, $parent, $file_name, $file_hash) = @_;
Jakub Narebski15f0b112009-11-13 02:02:13 +01005597
Jakub Narebski847e01f2006-08-14 02:05:47 +02005598 my $head = git_get_head_hash($project);
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005599 if (!defined $base) {
5600 $base = $head;
Kay Sievers0db37972005-08-07 20:24:35 +02005601 }
Kay Sieversea4a6df2005-08-07 20:26:49 +02005602 if (!defined $page) {
5603 $page = 0;
Kay Sieversb87d78d2005-08-07 20:21:04 +02005604 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02005605 my $refs = git_get_references();
Kay Sieversea4a6df2005-08-07 20:26:49 +02005606
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005607 my $commit_hash = $base;
5608 if (defined $parent) {
5609 $commit_hash = "$parent..$base";
Jakub Narebski15f0b112009-11-13 02:02:13 +01005610 }
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005611 my @commitlist =
5612 parse_commits($commit_hash, 101, (100 * $page),
5613 defined $file_name ? ($file_name, "--full-history") : ());
Kay Sieversea4a6df2005-08-07 20:26:49 +02005614
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005615 my $ftype;
5616 if (!defined $file_hash && defined $file_name) {
5617 # some commits could have deleted file in question,
5618 # and not have it in tree, but one of them has to have it
5619 for (my $i = 0; $i < @commitlist; $i++) {
5620 $file_hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name);
5621 last if defined $file_hash;
5622 }
5623 }
5624 if (defined $file_hash) {
5625 $ftype = git_get_type($file_hash);
5626 }
5627 if (defined $file_name && !defined $ftype) {
5628 die_error(500, "Unknown type of object");
5629 }
5630 my %co;
5631 if (defined $file_name) {
5632 %co = parse_commit($base)
5633 or die_error(404, "Unknown commit object");
5634 }
5635
5636
5637 my $paging_nav = format_paging_nav($fmt_name, $page, $#commitlist >= 100);
Jakub Narebski15f0b112009-11-13 02:02:13 +01005638 my $next_link = '';
Jakub Narebski42671ca2009-11-13 02:02:12 +01005639 if ($#commitlist >= 100) {
5640 $next_link =
5641 $cgi->a({-href => href(-replay=>1, page=>$page+1),
5642 -accesskey => "n", -title => "Alt-n"}, "next");
5643 }
Jakub Narebski15f0b112009-11-13 02:02:13 +01005644 my $patch_max = gitweb_get_feature('patches');
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005645 if ($patch_max && !defined $file_name) {
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01005646 if ($patch_max < 0 || @commitlist <= $patch_max) {
5647 $paging_nav .= " &sdot; " .
5648 $cgi->a({-href => href(action=>"patches", -replay=>1)},
5649 "patches");
5650 }
5651 }
5652
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02005653 git_header_html();
Jakub Narebski15f0b112009-11-13 02:02:13 +01005654 git_print_page_nav($fmt_name,'', $hash,$hash,$hash, $paging_nav);
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005655 if (defined $file_name) {
5656 git_print_header_div('commit', esc_html($co{'title'}), $base);
5657 } else {
5658 git_print_header_div('summary', $project)
5659 }
5660 git_print_page_path($file_name, $ftype, $hash_base)
5661 if (defined $file_name);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02005662
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005663 $body_subr->(\@commitlist, 0, 99, $refs, $next_link,
5664 $file_name, $file_hash, $ftype);
Jakub Narebski42671ca2009-11-13 02:02:12 +01005665
Kay Sievers034df392005-08-07 20:20:07 +02005666 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02005667}
5668
Jakub Narebski15f0b112009-11-13 02:02:13 +01005669sub git_log {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005670 git_log_generic('log', \&git_log_body,
5671 $hash, $hash_parent);
Jakub Narebski15f0b112009-11-13 02:02:13 +01005672}
5673
Kay Sievers09bd7892005-08-07 20:21:23 +02005674sub git_commit {
Jakub Narebski9954f772006-11-18 23:35:41 +01005675 $hash ||= $hash_base || "HEAD";
Lea Wiemann074afaa2008-06-19 22:03:21 +02005676 my %co = parse_commit($hash)
5677 or die_error(404, "Unknown commit object");
Kay Sievers161332a2005-08-07 19:49:46 +02005678
Jakub Narebskic9d193d2006-12-15 21:57:16 +01005679 my $parent = $co{'parent'};
5680 my $parents = $co{'parents'}; # listref
5681
5682 # we need to prepare $formats_nav before any parameter munging
5683 my $formats_nav;
5684 if (!defined $parent) {
5685 # --root commitdiff
5686 $formats_nav .= '(initial)';
5687 } elsif (@$parents == 1) {
5688 # single parent commit
5689 $formats_nav .=
5690 '(parent: ' .
5691 $cgi->a({-href => href(action=>"commit",
5692 hash=>$parent)},
5693 esc_html(substr($parent, 0, 7))) .
5694 ')';
5695 } else {
5696 # merge commit
5697 $formats_nav .=
5698 '(merge: ' .
5699 join(' ', map {
Jakub Narebskif9308a12007-03-23 21:04:31 +01005700 $cgi->a({-href => href(action=>"commit",
Jakub Narebskic9d193d2006-12-15 21:57:16 +01005701 hash=>$_)},
5702 esc_html(substr($_, 0, 7)));
5703 } @$parents ) .
5704 ')';
5705 }
Jakub Narebski1655c982009-10-09 14:26:44 +02005706 if (gitweb_check_feature('patches') && @$parents <= 1) {
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01005707 $formats_nav .= " | " .
5708 $cgi->a({-href => href(action=>"patch", -replay=>1)},
5709 "patch");
5710 }
Jakub Narebskic9d193d2006-12-15 21:57:16 +01005711
Kay Sieversd8a20ba2005-08-07 20:28:53 +02005712 if (!defined $parent) {
Jakub Narebskib9182982006-07-30 18:28:34 -07005713 $parent = "--root";
Kay Sievers6191f8e2005-08-07 20:19:56 +02005714 }
Jakub Narebski549ab4a2006-12-15 17:53:45 +01005715 my @difftree;
Jakub Narebski208ecb22007-05-07 01:10:08 +02005716 open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
5717 @diff_opts,
5718 (@$parents <= 1 ? $parent : '-c'),
5719 $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02005720 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski208ecb22007-05-07 01:10:08 +02005721 @difftree = map { chomp; $_ } <$fd>;
Lea Wiemann074afaa2008-06-19 22:03:21 +02005722 close $fd or die_error(404, "Reading git-diff-tree failed");
Kay Sievers11044292005-10-19 03:18:45 +02005723
5724 # non-textual hash id's can be cached
5725 my $expires;
5726 if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
5727 $expires = "+1d";
5728 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02005729 my $refs = git_get_references();
5730 my $ref = format_ref_marker($refs, $co{'id'});
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05305731
Jakub Narebski594e2122006-07-31 02:21:52 +02005732 git_header_html(undef, $expires);
Petr Baudisa1441542006-10-06 18:59:33 +02005733 git_print_page_nav('commit', '',
Jakub Narebski952c65f2006-08-22 16:52:50 +02005734 $hash, $co{'tree'}, $hash,
Jakub Narebskic9d193d2006-12-15 21:57:16 +01005735 $formats_nav);
Luben Tuikov4f7b34c2006-07-23 13:36:32 -07005736
Kay Sieversb87d78d2005-08-07 20:21:04 +02005737 if (defined $co{'parent'}) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005738 git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);
Kay Sieversb87d78d2005-08-07 20:21:04 +02005739 } else {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005740 git_print_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash);
Kay Sieversb87d78d2005-08-07 20:21:04 +02005741 }
Kay Sievers6191f8e2005-08-07 20:19:56 +02005742 print "<div class=\"title_text\">\n" .
Jakub Narebski591ebf62007-11-19 14:16:11 +01005743 "<table class=\"object_header\">\n";
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02005744 git_print_authorship_rows(\%co);
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00005745 print "<tr><td>commit</td><td class=\"sha1\">$co{'id'}</td></tr>\n";
Kay Sieversbddec012005-08-07 20:25:42 +02005746 print "<tr>" .
5747 "<td>tree</td>" .
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00005748 "<td class=\"sha1\">" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02005749 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash),
5750 class => "list"}, $co{'tree'}) .
Kay Sievers19806692005-08-07 20:26:27 +02005751 "</td>" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02005752 "<td class=\"link\">" .
5753 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)},
5754 "tree");
Matt McCutchena3c8ab32007-07-22 01:30:27 +02005755 my $snapshot_links = format_snapshot_links($hash);
5756 if (defined $snapshot_links) {
5757 print " | " . $snapshot_links;
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305758 }
5759 print "</td>" .
Kay Sieversbddec012005-08-07 20:25:42 +02005760 "</tr>\n";
Jakub Narebski549ab4a2006-12-15 17:53:45 +01005761
Kay Sievers3e029292005-08-07 20:05:15 +02005762 foreach my $par (@$parents) {
Kay Sieversbddec012005-08-07 20:25:42 +02005763 print "<tr>" .
5764 "<td>parent</td>" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02005765 "<td class=\"sha1\">" .
5766 $cgi->a({-href => href(action=>"commit", hash=>$par),
5767 class => "list"}, $par) .
5768 "</td>" .
Kay Sieversbddec012005-08-07 20:25:42 +02005769 "<td class=\"link\">" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02005770 $cgi->a({-href => href(action=>"commit", hash=>$par)}, "commit") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02005771 " | " .
Jakub Narebskif2e60942006-09-03 23:43:03 +02005772 $cgi->a({-href => href(action=>"commitdiff", hash=>$hash, hash_parent=>$par)}, "diff") .
Kay Sieversbddec012005-08-07 20:25:42 +02005773 "</td>" .
5774 "</tr>\n";
Kay Sievers3e029292005-08-07 20:05:15 +02005775 }
Jakub Narebski7a9b4c52006-06-21 09:48:02 +02005776 print "</table>".
Kay Sieversb87d78d2005-08-07 20:21:04 +02005777 "</div>\n";
Jakub Narebskid16d0932006-08-17 11:21:23 +02005778
Kay Sieversfbb592a2005-08-07 20:12:11 +02005779 print "<div class=\"page_body\">\n";
Jakub Narebskid16d0932006-08-17 11:21:23 +02005780 git_print_log($co{'comment'});
Kay Sievers927dcec2005-08-07 20:18:44 +02005781 print "</div>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005782
Jakub Narebski208ecb22007-05-07 01:10:08 +02005783 git_difftree_body(\@difftree, $hash, @$parents);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02005784
Kay Sievers12a88f22005-08-07 20:02:47 +02005785 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02005786}
5787
Jakub Narebskica946012006-12-10 13:25:47 +01005788sub git_object {
5789 # object is defined by:
5790 # - hash or hash_base alone
5791 # - hash_base and file_name
5792 my $type;
5793
5794 # - hash or hash_base alone
5795 if ($hash || ($hash_base && !defined $file_name)) {
5796 my $object_id = $hash || $hash_base;
5797
Lea Wiemann516381d2008-06-17 23:46:35 +02005798 open my $fd, "-|", quote_command(
5799 git_cmd(), 'cat-file', '-t', $object_id) . ' 2> /dev/null'
Lea Wiemann074afaa2008-06-19 22:03:21 +02005800 or die_error(404, "Object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01005801 $type = <$fd>;
5802 chomp $type;
5803 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02005804 or die_error(404, "Object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01005805
5806 # - hash_base and file_name
5807 } elsif ($hash_base && defined $file_name) {
5808 $file_name =~ s,/+$,,;
5809
5810 system(git_cmd(), "cat-file", '-e', $hash_base) == 0
Lea Wiemann074afaa2008-06-19 22:03:21 +02005811 or die_error(404, "Base object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01005812
5813 # here errors should not hapen
5814 open my $fd, "-|", git_cmd(), "ls-tree", $hash_base, "--", $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02005815 or die_error(500, "Open git-ls-tree failed");
Jakub Narebskica946012006-12-10 13:25:47 +01005816 my $line = <$fd>;
5817 close $fd;
5818
5819 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
5820 unless ($line && $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005821 die_error(404, "File or directory for given base does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01005822 }
5823 $type = $2;
5824 $hash = $3;
5825 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005826 die_error(400, "Not enough information to find object");
Jakub Narebskica946012006-12-10 13:25:47 +01005827 }
5828
5829 print $cgi->redirect(-uri => href(action=>$type, -full=>1,
5830 hash=>$hash, hash_base=>$hash_base,
5831 file_name=>$file_name),
5832 -status => '302 Found');
5833}
5834
Kay Sievers09bd7892005-08-07 20:21:23 +02005835sub git_blobdiff {
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005836 my $format = shift || 'html';
5837
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005838 my $fd;
5839 my @difftree;
5840 my %diffinfo;
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005841 my $expires;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005842
5843 # preparing $fd and %diffinfo for git_patchset_body
5844 # new style URI
5845 if (defined $hash_base && defined $hash_parent_base) {
5846 if (defined $file_name) {
5847 # read raw output
Jakub Narebski45bd0c82006-10-30 22:29:06 +01005848 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
5849 $hash_parent_base, $hash_base,
Jakub Narebski5ae917a2007-03-30 23:41:26 +02005850 "--", (defined $file_parent ? $file_parent : ()), $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02005851 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005852 @difftree = map { chomp; $_ } <$fd>;
5853 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02005854 or die_error(404, "Reading git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005855 @difftree
Lea Wiemann074afaa2008-06-19 22:03:21 +02005856 or die_error(404, "Blob diff not found");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005857
Jakub Narebski0aea3372006-08-27 23:45:26 +02005858 } elsif (defined $hash &&
5859 $hash =~ /[0-9a-fA-F]{40}/) {
5860 # try to find filename from $hash
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005861
5862 # read filtered raw output
Jakub Narebski45bd0c82006-10-30 22:29:06 +01005863 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
5864 $hash_parent_base, $hash_base, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02005865 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005866 @difftree =
5867 # ':100644 100644 03b21826... 3b93d5e7... M ls-files.c'
5868 # $hash == to_id
5869 grep { /^:[0-7]{6} [0-7]{6} [0-9a-fA-F]{40} $hash/ }
5870 map { chomp; $_ } <$fd>;
5871 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02005872 or die_error(404, "Reading git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005873 @difftree
Lea Wiemann074afaa2008-06-19 22:03:21 +02005874 or die_error(404, "Blob diff not found");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005875
5876 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005877 die_error(400, "Missing one of the blob diff parameters");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005878 }
5879
5880 if (@difftree > 1) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005881 die_error(400, "Ambiguous blob diff specification");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005882 }
5883
5884 %diffinfo = parse_difftree_raw_line($difftree[0]);
Jakub Narebski9d301452007-11-01 12:38:08 +01005885 $file_parent ||= $diffinfo{'from_file'} || $file_name;
5886 $file_name ||= $diffinfo{'to_file'};
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005887
5888 $hash_parent ||= $diffinfo{'from_id'};
5889 $hash ||= $diffinfo{'to_id'};
5890
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005891 # non-textual hash id's can be cached
5892 if ($hash_base =~ m/^[0-9a-fA-F]{40}$/ &&
5893 $hash_parent_base =~ m/^[0-9a-fA-F]{40}$/) {
5894 $expires = '+1d';
5895 }
5896
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005897 # open patch output
Dennis Stosberg25691fb2006-08-28 17:49:58 +02005898 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebski957d6ea2007-04-05 13:45:41 +02005899 '-p', ($format eq 'html' ? "--full-index" : ()),
5900 $hash_parent_base, $hash_base,
Jakub Narebski5ae917a2007-03-30 23:41:26 +02005901 "--", (defined $file_parent ? $file_parent : ()), $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02005902 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005903 }
5904
Junio C Hamanob54dc9f2008-12-16 19:42:02 -08005905 # old/legacy style URI -- not generated anymore since 1.4.3.
5906 if (!%diffinfo) {
5907 die_error('404 Not Found', "Missing one of the blob diff parameters")
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005908 }
5909
5910 # header
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005911 if ($format eq 'html') {
5912 my $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01005913 $cgi->a({-href => href(action=>"blobdiff_plain", -replay=>1)},
Petr Baudis35329cc2006-09-22 03:19:50 +02005914 "raw");
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005915 git_header_html(undef, $expires);
5916 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
5917 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
5918 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
5919 } else {
5920 print "<div class=\"page_nav\"><br/>$formats_nav<br/></div>\n";
5921 print "<div class=\"title\">$hash vs $hash_parent</div>\n";
5922 }
5923 if (defined $file_name) {
5924 git_print_page_path($file_name, "blob", $hash_base);
5925 } else {
5926 print "<div class=\"page_path\"></div>\n";
5927 }
5928
5929 } elsif ($format eq 'plain') {
5930 print $cgi->header(
5931 -type => 'text/plain',
5932 -charset => 'utf-8',
5933 -expires => $expires,
Luben Tuikova2a3bf72006-09-28 16:51:43 -07005934 -content_disposition => 'inline; filename="' . "$file_name" . '.patch"');
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005935
5936 print "X-Git-Url: " . $cgi->self_url() . "\n\n";
5937
Kay Sievers09bd7892005-08-07 20:21:23 +02005938 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005939 die_error(400, "Unknown blobdiff format");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005940 }
5941
5942 # patch
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005943 if ($format eq 'html') {
5944 print "<div class=\"page_body\">\n";
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005945
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005946 git_patchset_body($fd, [ \%diffinfo ], $hash_base, $hash_parent_base);
5947 close $fd;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005948
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005949 print "</div>\n"; # class="page_body"
5950 git_footer_html();
5951
5952 } else {
5953 while (my $line = <$fd>) {
Jakub Narebski403d0902006-11-08 11:48:56 +01005954 $line =~ s!a/($hash|$hash_parent)!'a/'.esc_path($diffinfo{'from_file'})!eg;
5955 $line =~ s!b/($hash|$hash_parent)!'b/'.esc_path($diffinfo{'to_file'})!eg;
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005956
5957 print $line;
5958
5959 last if $line =~ m!^\+\+\+!;
5960 }
5961 local $/ = undef;
5962 print <$fd>;
5963 close $fd;
5964 }
Kay Sievers09bd7892005-08-07 20:21:23 +02005965}
5966
Kay Sievers19806692005-08-07 20:26:27 +02005967sub git_blobdiff_plain {
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005968 git_blobdiff('plain');
Kay Sievers19806692005-08-07 20:26:27 +02005969}
5970
Kay Sievers09bd7892005-08-07 20:21:23 +02005971sub git_commitdiff {
Giuseppe Bilotta20209852008-12-18 08:13:17 +01005972 my %params = @_;
5973 my $format = $params{-format} || 'html';
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01005974
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01005975 my ($patch_max) = gitweb_get_feature('patches');
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01005976 if ($format eq 'patch') {
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01005977 die_error(403, "Patch view not allowed") unless $patch_max;
5978 }
5979
Jakub Narebski9954f772006-11-18 23:35:41 +01005980 $hash ||= $hash_base || "HEAD";
Lea Wiemann074afaa2008-06-19 22:03:21 +02005981 my %co = parse_commit($hash)
5982 or die_error(404, "Unknown commit object");
Jakub Narebski151602d2006-10-23 00:37:56 +02005983
Jakub Narebskicd030c32007-06-08 13:33:28 +02005984 # choose format for commitdiff for merge
5985 if (! defined $hash_parent && @{$co{'parents'}} > 1) {
5986 $hash_parent = '--cc';
5987 }
5988 # we need to prepare $formats_nav before almost any parameter munging
Jakub Narebski151602d2006-10-23 00:37:56 +02005989 my $formats_nav;
5990 if ($format eq 'html') {
5991 $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01005992 $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
Jakub Narebski151602d2006-10-23 00:37:56 +02005993 "raw");
Jakub Narebski1655c982009-10-09 14:26:44 +02005994 if ($patch_max && @{$co{'parents'}} <= 1) {
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01005995 $formats_nav .= " | " .
5996 $cgi->a({-href => href(action=>"patch", -replay=>1)},
5997 "patch");
5998 }
Jakub Narebski151602d2006-10-23 00:37:56 +02005999
Jakub Narebskicd030c32007-06-08 13:33:28 +02006000 if (defined $hash_parent &&
6001 $hash_parent ne '-c' && $hash_parent ne '--cc') {
Jakub Narebski151602d2006-10-23 00:37:56 +02006002 # commitdiff with two commits given
6003 my $hash_parent_short = $hash_parent;
6004 if ($hash_parent =~ m/^[0-9a-fA-F]{40}$/) {
6005 $hash_parent_short = substr($hash_parent, 0, 7);
6006 }
6007 $formats_nav .=
Jakub Narebskiada3e1f2007-06-08 13:26:31 +02006008 ' (from';
6009 for (my $i = 0; $i < @{$co{'parents'}}; $i++) {
6010 if ($co{'parents'}[$i] eq $hash_parent) {
6011 $formats_nav .= ' parent ' . ($i+1);
6012 last;
6013 }
6014 }
6015 $formats_nav .= ': ' .
Jakub Narebski151602d2006-10-23 00:37:56 +02006016 $cgi->a({-href => href(action=>"commitdiff",
6017 hash=>$hash_parent)},
6018 esc_html($hash_parent_short)) .
6019 ')';
6020 } elsif (!$co{'parent'}) {
6021 # --root commitdiff
6022 $formats_nav .= ' (initial)';
6023 } elsif (scalar @{$co{'parents'}} == 1) {
6024 # single parent commit
6025 $formats_nav .=
6026 ' (parent: ' .
6027 $cgi->a({-href => href(action=>"commitdiff",
6028 hash=>$co{'parent'})},
6029 esc_html(substr($co{'parent'}, 0, 7))) .
6030 ')';
6031 } else {
6032 # merge commit
Jakub Narebskicd030c32007-06-08 13:33:28 +02006033 if ($hash_parent eq '--cc') {
6034 $formats_nav .= ' | ' .
6035 $cgi->a({-href => href(action=>"commitdiff",
6036 hash=>$hash, hash_parent=>'-c')},
6037 'combined');
6038 } else { # $hash_parent eq '-c'
6039 $formats_nav .= ' | ' .
6040 $cgi->a({-href => href(action=>"commitdiff",
6041 hash=>$hash, hash_parent=>'--cc')},
6042 'compact');
6043 }
Jakub Narebski151602d2006-10-23 00:37:56 +02006044 $formats_nav .=
6045 ' (merge: ' .
6046 join(' ', map {
6047 $cgi->a({-href => href(action=>"commitdiff",
6048 hash=>$_)},
6049 esc_html(substr($_, 0, 7)));
6050 } @{$co{'parents'}} ) .
6051 ')';
6052 }
6053 }
6054
Jakub Narebskifb1dde42007-05-07 01:10:07 +02006055 my $hash_parent_param = $hash_parent;
Jakub Narebskicd030c32007-06-08 13:33:28 +02006056 if (!defined $hash_parent_param) {
6057 # --cc for multiple parents, --root for parentless
Jakub Narebskifb1dde42007-05-07 01:10:07 +02006058 $hash_parent_param =
Jakub Narebskicd030c32007-06-08 13:33:28 +02006059 @{$co{'parents'}} > 1 ? '--cc' : $co{'parent'} || '--root';
Kay Sieversbddec012005-08-07 20:25:42 +02006060 }
Jakub Narebskieee08902006-08-24 00:15:14 +02006061
6062 # read commitdiff
6063 my $fd;
6064 my @difftree;
Jakub Narebskieee08902006-08-24 00:15:14 +02006065 if ($format eq 'html') {
Dennis Stosberg25691fb2006-08-28 17:49:58 +02006066 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebski45bd0c82006-10-30 22:29:06 +01006067 "--no-commit-id", "--patch-with-raw", "--full-index",
Jakub Narebskifb1dde42007-05-07 01:10:07 +02006068 $hash_parent_param, $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02006069 or die_error(500, "Open git-diff-tree failed");
Jakub Narebskieee08902006-08-24 00:15:14 +02006070
Jakub Narebski04408c32006-11-18 23:35:38 +01006071 while (my $line = <$fd>) {
6072 chomp $line;
Jakub Narebskieee08902006-08-24 00:15:14 +02006073 # empty line ends raw part of diff-tree output
6074 last unless $line;
Jakub Narebski493e01d2007-05-07 01:10:06 +02006075 push @difftree, scalar parse_difftree_raw_line($line);
Jakub Narebskieee08902006-08-24 00:15:14 +02006076 }
Jakub Narebskieee08902006-08-24 00:15:14 +02006077
Jakub Narebskieee08902006-08-24 00:15:14 +02006078 } elsif ($format eq 'plain') {
Dennis Stosberg25691fb2006-08-28 17:49:58 +02006079 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebskifb1dde42007-05-07 01:10:07 +02006080 '-p', $hash_parent_param, $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02006081 or die_error(500, "Open git-diff-tree failed");
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01006082 } elsif ($format eq 'patch') {
6083 # For commit ranges, we limit the output to the number of
6084 # patches specified in the 'patches' feature.
6085 # For single commits, we limit the output to a single patch,
6086 # diverging from the git-format-patch default.
6087 my @commit_spec = ();
6088 if ($hash_parent) {
6089 if ($patch_max > 0) {
6090 push @commit_spec, "-$patch_max";
6091 }
6092 push @commit_spec, '-n', "$hash_parent..$hash";
6093 } else {
Giuseppe Bilottaa3411f82008-12-18 08:13:18 +01006094 if ($params{-single}) {
6095 push @commit_spec, '-1';
6096 } else {
6097 if ($patch_max > 0) {
6098 push @commit_spec, "-$patch_max";
6099 }
6100 push @commit_spec, "-n";
6101 }
6102 push @commit_spec, '--root', $hash;
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01006103 }
6104 open $fd, "-|", git_cmd(), "format-patch", '--encoding=utf8',
6105 '--stdout', @commit_spec
6106 or die_error(500, "Open git-format-patch failed");
Jakub Narebskieee08902006-08-24 00:15:14 +02006107 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006108 die_error(400, "Unknown commitdiff format");
Jakub Narebskieee08902006-08-24 00:15:14 +02006109 }
Kay Sievers4c02e3c2005-08-07 19:52:52 +02006110
Kay Sievers11044292005-10-19 03:18:45 +02006111 # non-textual hash id's can be cached
6112 my $expires;
6113 if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
6114 $expires = "+1d";
6115 }
Kay Sievers09bd7892005-08-07 20:21:23 +02006116
Jakub Narebskieee08902006-08-24 00:15:14 +02006117 # write commit message
6118 if ($format eq 'html') {
6119 my $refs = git_get_references();
6120 my $ref = format_ref_marker($refs, $co{'id'});
Kay Sievers19806692005-08-07 20:26:27 +02006121
Jakub Narebskieee08902006-08-24 00:15:14 +02006122 git_header_html(undef, $expires);
6123 git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
6124 git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
Giuseppe Bilottaf88bafa2009-06-30 00:00:49 +02006125 print "<div class=\"title_text\">\n" .
6126 "<table class=\"object_header\">\n";
6127 git_print_authorship_rows(\%co);
6128 print "</table>".
6129 "</div>\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02006130 print "<div class=\"page_body\">\n";
Jakub Narebski82560982006-10-24 13:55:33 +02006131 if (@{$co{'comment'}} > 1) {
6132 print "<div class=\"log\">\n";
6133 git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1);
6134 print "</div>\n"; # class="log"
6135 }
Kay Sievers1b1cd422005-08-07 20:28:01 +02006136
Jakub Narebskieee08902006-08-24 00:15:14 +02006137 } elsif ($format eq 'plain') {
6138 my $refs = git_get_references("tags");
Jakub Narebskiedf735a2006-08-24 19:45:30 +02006139 my $tagname = git_get_rev_name_tags($hash);
Jakub Narebskieee08902006-08-24 00:15:14 +02006140 my $filename = basename($project) . "-$hash.patch";
6141
6142 print $cgi->header(
6143 -type => 'text/plain',
6144 -charset => 'utf-8',
6145 -expires => $expires,
Luben Tuikova2a3bf72006-09-28 16:51:43 -07006146 -content_disposition => 'inline; filename="' . "$filename" . '"');
Jakub Narebskieee08902006-08-24 00:15:14 +02006147 my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
Yasushi SHOJI77202242008-01-29 21:16:02 +09006148 print "From: " . to_utf8($co{'author'}) . "\n";
6149 print "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n";
6150 print "Subject: " . to_utf8($co{'title'}) . "\n";
6151
Jakub Narebskiedf735a2006-08-24 19:45:30 +02006152 print "X-Git-Tag: $tagname\n" if $tagname;
Jakub Narebskieee08902006-08-24 00:15:14 +02006153 print "X-Git-Url: " . $cgi->self_url() . "\n\n";
Jakub Narebskiedf735a2006-08-24 19:45:30 +02006154
Jakub Narebskieee08902006-08-24 00:15:14 +02006155 foreach my $line (@{$co{'comment'}}) {
Yasushi SHOJI77202242008-01-29 21:16:02 +09006156 print to_utf8($line) . "\n";
Kay Sievers19806692005-08-07 20:26:27 +02006157 }
Jakub Narebskieee08902006-08-24 00:15:14 +02006158 print "---\n\n";
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01006159 } elsif ($format eq 'patch') {
6160 my $filename = basename($project) . "-$hash.patch";
6161
6162 print $cgi->header(
6163 -type => 'text/plain',
6164 -charset => 'utf-8',
6165 -expires => $expires,
6166 -content_disposition => 'inline; filename="' . "$filename" . '"');
Kay Sievers19806692005-08-07 20:26:27 +02006167 }
Jakub Narebskieee08902006-08-24 00:15:14 +02006168
6169 # write patch
6170 if ($format eq 'html') {
Jakub Narebskicd030c32007-06-08 13:33:28 +02006171 my $use_parents = !defined $hash_parent ||
6172 $hash_parent eq '-c' || $hash_parent eq '--cc';
6173 git_difftree_body(\@difftree, $hash,
6174 $use_parents ? @{$co{'parents'}} : $hash_parent);
Jakub Narebskib4657e72006-08-28 14:48:14 +02006175 print "<br/>\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02006176
Jakub Narebskicd030c32007-06-08 13:33:28 +02006177 git_patchset_body($fd, \@difftree, $hash,
6178 $use_parents ? @{$co{'parents'}} : $hash_parent);
Jakub Narebski157e43b2006-08-24 19:34:36 +02006179 close $fd;
Jakub Narebskieee08902006-08-24 00:15:14 +02006180 print "</div>\n"; # class="page_body"
6181 git_footer_html();
6182
6183 } elsif ($format eq 'plain') {
6184 local $/ = undef;
6185 print <$fd>;
6186 close $fd
6187 or print "Reading git-diff-tree failed\n";
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01006188 } elsif ($format eq 'patch') {
6189 local $/ = undef;
6190 print <$fd>;
6191 close $fd
6192 or print "Reading git-format-patch failed\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02006193 }
6194}
6195
6196sub git_commitdiff_plain {
Giuseppe Bilotta20209852008-12-18 08:13:17 +01006197 git_commitdiff(-format => 'plain');
Kay Sievers19806692005-08-07 20:26:27 +02006198}
6199
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01006200# format-patch-style patches
6201sub git_patch {
Jakub Narebski1655c982009-10-09 14:26:44 +02006202 git_commitdiff(-format => 'patch', -single => 1);
Giuseppe Bilottaa3411f82008-12-18 08:13:18 +01006203}
6204
6205sub git_patches {
Giuseppe Bilotta20209852008-12-18 08:13:17 +01006206 git_commitdiff(-format => 'patch');
Kay Sievers09bd7892005-08-07 20:21:23 +02006207}
6208
6209sub git_history {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01006210 git_log_generic('history', \&git_history_body,
6211 $hash_base, $hash_parent_base,
6212 $file_name, $hash);
Kay Sievers161332a2005-08-07 19:49:46 +02006213}
Kay Sievers19806692005-08-07 20:26:27 +02006214
6215sub git_search {
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006216 gitweb_check_feature('search') or die_error(403, "Search is disabled");
Kay Sievers19806692005-08-07 20:26:27 +02006217 if (!defined $searchtext) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006218 die_error(400, "Text field is empty");
Kay Sievers19806692005-08-07 20:26:27 +02006219 }
6220 if (!defined $hash) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006221 $hash = git_get_head_hash($project);
Kay Sievers19806692005-08-07 20:26:27 +02006222 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02006223 my %co = parse_commit($hash);
Kay Sievers19806692005-08-07 20:26:27 +02006224 if (!%co) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006225 die_error(404, "Unknown commit object");
Kay Sievers19806692005-08-07 20:26:27 +02006226 }
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006227 if (!defined $page) {
6228 $page = 0;
6229 }
Jakub Narebski04f7a942006-09-11 00:29:27 +02006230
Petr Baudis88ad7292006-10-24 05:15:46 +02006231 $searchtype ||= 'commit';
6232 if ($searchtype eq 'pickaxe') {
Jakub Narebski04f7a942006-09-11 00:29:27 +02006233 # pickaxe may take all resources of your box and run for several minutes
6234 # with every query - so decide by yourself how public you make this feature
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006235 gitweb_check_feature('pickaxe')
Lea Wiemann074afaa2008-06-19 22:03:21 +02006236 or die_error(403, "Pickaxe is disabled");
Kay Sieversc994d622005-08-07 20:27:18 +02006237 }
Petr Baudise7738552007-05-17 04:31:12 +02006238 if ($searchtype eq 'grep') {
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006239 gitweb_check_feature('grep')
Lea Wiemann074afaa2008-06-19 22:03:21 +02006240 or die_error(403, "Grep is disabled");
Petr Baudise7738552007-05-17 04:31:12 +02006241 }
Petr Baudis88ad7292006-10-24 05:15:46 +02006242
Kay Sievers19806692005-08-07 20:26:27 +02006243 git_header_html();
Kay Sievers19806692005-08-07 20:26:27 +02006244
Petr Baudis88ad7292006-10-24 05:15:46 +02006245 if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') {
Robert Fitzsimons8e574fb2006-12-23 03:35:14 +00006246 my $greptype;
6247 if ($searchtype eq 'commit') {
6248 $greptype = "--grep=";
6249 } elsif ($searchtype eq 'author') {
6250 $greptype = "--author=";
6251 } elsif ($searchtype eq 'committer') {
6252 $greptype = "--committer=";
6253 }
Jakub Narebski0270cd02008-02-26 13:22:07 +01006254 $greptype .= $searchtext;
6255 my @commitlist = parse_commits($hash, 101, (100 * $page), undef,
Petr Baudis0e559912008-02-26 13:22:08 +01006256 $greptype, '--regexp-ignore-case',
6257 $search_use_regexp ? '--extended-regexp' : '--fixed-strings');
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006258
6259 my $paging_nav = '';
6260 if ($page > 0) {
6261 $paging_nav .=
6262 $cgi->a({-href => href(action=>"search", hash=>$hash,
Jakub Narebski0270cd02008-02-26 13:22:07 +01006263 searchtext=>$searchtext,
6264 searchtype=>$searchtype)},
Jakub Narebskia23f0a72007-04-01 22:21:38 +02006265 "first");
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006266 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01006267 $cgi->a({-href => href(-replay=>1, page=>$page-1),
Jakub Narebskia23f0a72007-04-01 22:21:38 +02006268 -accesskey => "p", -title => "Alt-p"}, "prev");
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006269 } else {
6270 $paging_nav .= "first";
6271 $paging_nav .= " &sdot; prev";
6272 }
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006273 my $next_link = '';
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00006274 if ($#commitlist >= 100) {
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006275 $next_link =
Jakub Narebski7afd77b2007-11-01 13:06:28 +01006276 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Jakub Narebskia23f0a72007-04-01 22:21:38 +02006277 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski7afd77b2007-11-01 13:06:28 +01006278 $paging_nav .= " &sdot; $next_link";
6279 } else {
6280 $paging_nav .= " &sdot; next";
6281 }
6282
6283 if ($#commitlist >= 100) {
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006284 }
6285
6286 git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
6287 git_print_header_div('commit', esc_html($co{'title'}), $hash);
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00006288 git_search_grep_body(\@commitlist, 0, 99, $next_link);
Kay Sieversc994d622005-08-07 20:27:18 +02006289 }
6290
Petr Baudis88ad7292006-10-24 05:15:46 +02006291 if ($searchtype eq 'pickaxe') {
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006292 git_print_page_nav('','', $hash,$co{'tree'},$hash);
6293 git_print_header_div('commit', esc_html($co{'title'}), $hash);
6294
Jakub Narebski591ebf62007-11-19 14:16:11 +01006295 print "<table class=\"pickaxe search\">\n";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006296 my $alternate = 1;
Jakub Narebski34122b52009-05-11 03:29:40 +02006297 local $/ = "\n";
Jakub Narebskic582aba2008-03-05 09:31:55 +01006298 open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts,
6299 '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext",
6300 ($search_use_regexp ? '--pickaxe-regex' : ());
Kay Sieversc994d622005-08-07 20:27:18 +02006301 undef %co;
6302 my @files;
6303 while (my $line = <$fd>) {
Jakub Narebskic582aba2008-03-05 09:31:55 +01006304 chomp $line;
6305 next unless $line;
6306
6307 my %set = parse_difftree_raw_line($line);
6308 if (defined $set{'commit'}) {
6309 # finish previous commit
Kay Sieversc994d622005-08-07 20:27:18 +02006310 if (%co) {
Kay Sieversc994d622005-08-07 20:27:18 +02006311 print "</td>\n" .
6312 "<td class=\"link\">" .
Martin Waitz756d2f02006-08-17 00:28:36 +02006313 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02006314 " | " .
6315 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
Kay Sieversc994d622005-08-07 20:27:18 +02006316 print "</td>\n" .
6317 "</tr>\n";
6318 }
Jakub Narebskic582aba2008-03-05 09:31:55 +01006319
6320 if ($alternate) {
6321 print "<tr class=\"dark\">\n";
6322 } else {
6323 print "<tr class=\"light\">\n";
6324 }
6325 $alternate ^= 1;
6326 %co = parse_commit($set{'commit'});
6327 my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
6328 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
6329 "<td><i>$author</i></td>\n" .
6330 "<td>" .
6331 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
6332 -class => "list subject"},
6333 chop_and_escape_str($co{'title'}, 50) . "<br/>");
6334 } elsif (defined $set{'to_id'}) {
6335 next if ($set{'to_id'} =~ m/^0{40}$/);
6336
6337 print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
6338 hash=>$set{'to_id'}, file_name=>$set{'to_file'}),
6339 -class => "list"},
6340 "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
6341 "<br/>\n";
Kay Sieversc994d622005-08-07 20:27:18 +02006342 }
6343 }
6344 close $fd;
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006345
Jakub Narebskic582aba2008-03-05 09:31:55 +01006346 # finish last commit (warning: repetition!)
6347 if (%co) {
6348 print "</td>\n" .
6349 "<td class=\"link\">" .
6350 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
6351 " | " .
6352 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
6353 print "</td>\n" .
6354 "</tr>\n";
6355 }
6356
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006357 print "</table>\n";
Kay Sievers19806692005-08-07 20:26:27 +02006358 }
Petr Baudise7738552007-05-17 04:31:12 +02006359
6360 if ($searchtype eq 'grep') {
6361 git_print_page_nav('','', $hash,$co{'tree'},$hash);
6362 git_print_header_div('commit', esc_html($co{'title'}), $hash);
6363
Jakub Narebski591ebf62007-11-19 14:16:11 +01006364 print "<table class=\"grep_search\">\n";
Petr Baudise7738552007-05-17 04:31:12 +02006365 my $alternate = 1;
6366 my $matches = 0;
Jakub Narebski34122b52009-05-11 03:29:40 +02006367 local $/ = "\n";
Petr Baudis0e559912008-02-26 13:22:08 +01006368 open my $fd, "-|", git_cmd(), 'grep', '-n',
6369 $search_use_regexp ? ('-E', '-i') : '-F',
6370 $searchtext, $co{'tree'};
Petr Baudise7738552007-05-17 04:31:12 +02006371 my $lastfile = '';
6372 while (my $line = <$fd>) {
6373 chomp $line;
6374 my ($file, $lno, $ltext, $binary);
6375 last if ($matches++ > 1000);
6376 if ($line =~ /^Binary file (.+) matches$/) {
6377 $file = $1;
6378 $binary = 1;
6379 } else {
6380 (undef, $file, $lno, $ltext) = split(/:/, $line, 4);
6381 }
6382 if ($file ne $lastfile) {
6383 $lastfile and print "</td></tr>\n";
6384 if ($alternate++) {
6385 print "<tr class=\"dark\">\n";
6386 } else {
6387 print "<tr class=\"light\">\n";
6388 }
6389 print "<td class=\"list\">".
6390 $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
6391 file_name=>"$file"),
6392 -class => "list"}, esc_path($file));
6393 print "</td><td>\n";
6394 $lastfile = $file;
6395 }
6396 if ($binary) {
6397 print "<div class=\"binary\">Binary file</div>\n";
6398 } else {
6399 $ltext = untabify($ltext);
Petr Baudis0e559912008-02-26 13:22:08 +01006400 if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) {
Petr Baudise7738552007-05-17 04:31:12 +02006401 $ltext = esc_html($1, -nbsp=>1);
6402 $ltext .= '<span class="match">';
6403 $ltext .= esc_html($2, -nbsp=>1);
6404 $ltext .= '</span>';
6405 $ltext .= esc_html($3, -nbsp=>1);
6406 } else {
6407 $ltext = esc_html($ltext, -nbsp=>1);
6408 }
6409 print "<div class=\"pre\">" .
6410 $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
6411 file_name=>"$file").'#l'.$lno,
6412 -class => "linenr"}, sprintf('%4i', $lno))
6413 . ' ' . $ltext . "</div>\n";
6414 }
6415 }
6416 if ($lastfile) {
6417 print "</td></tr>\n";
6418 if ($matches > 1000) {
6419 print "<div class=\"diff nodifferences\">Too many matches, listing trimmed</div>\n";
6420 }
6421 } else {
6422 print "<div class=\"diff nodifferences\">No matches found</div>\n";
6423 }
6424 close $fd;
6425
6426 print "</table>\n";
6427 }
Kay Sievers19806692005-08-07 20:26:27 +02006428 git_footer_html();
6429}
6430
Petr Baudis88ad7292006-10-24 05:15:46 +02006431sub git_search_help {
6432 git_header_html();
6433 git_print_page_nav('','', $hash,$hash,$hash);
6434 print <<EOT;
Petr Baudis0e559912008-02-26 13:22:08 +01006435<p><strong>Pattern</strong> is by default a normal string that is matched precisely (but without
6436regard to case, except in the case of pickaxe). However, when you check the <em>re</em> checkbox,
6437the pattern entered is recognized as the POSIX extended
6438<a href="http://en.wikipedia.org/wiki/Regular_expression">regular expression</a> (also case
6439insensitive).</p>
Petr Baudis88ad7292006-10-24 05:15:46 +02006440<dl>
6441<dt><b>commit</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01006442<dd>The commit messages and authorship information will be scanned for the given pattern.</dd>
Petr Baudise7738552007-05-17 04:31:12 +02006443EOT
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006444 my $have_grep = gitweb_check_feature('grep');
Petr Baudise7738552007-05-17 04:31:12 +02006445 if ($have_grep) {
6446 print <<EOT;
6447<dt><b>grep</b></dt>
6448<dd>All files in the currently selected tree (HEAD unless you are explicitly browsing
Petr Baudis0e559912008-02-26 13:22:08 +01006449 a different one) are searched for the given pattern. On large trees, this search can take
6450a while and put some strain on the server, so please use it with some consideration. Note that
6451due to git-grep peculiarity, currently if regexp mode is turned off, the matches are
6452case-sensitive.</dd>
Petr Baudise7738552007-05-17 04:31:12 +02006453EOT
6454 }
6455 print <<EOT;
Petr Baudis88ad7292006-10-24 05:15:46 +02006456<dt><b>author</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01006457<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 +02006458<dt><b>committer</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01006459<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 +02006460EOT
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006461 my $have_pickaxe = gitweb_check_feature('pickaxe');
Petr Baudis88ad7292006-10-24 05:15:46 +02006462 if ($have_pickaxe) {
6463 print <<EOT;
6464<dt><b>pickaxe</b></dt>
6465<dd>All commits that caused the string to appear or disappear from any file (changes that
6466added, removed or "modified" the string) will be listed. This search can take a while and
Petr Baudis0e559912008-02-26 13:22:08 +01006467takes a lot of strain on the server, so please use it wisely. Note that since you may be
6468interested even in changes just changing the case as well, this search is case sensitive.</dd>
Petr Baudis88ad7292006-10-24 05:15:46 +02006469EOT
6470 }
6471 print "</dl>\n";
6472 git_footer_html();
6473}
6474
Kay Sievers19806692005-08-07 20:26:27 +02006475sub git_shortlog {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01006476 git_log_generic('shortlog', \&git_shortlog_body,
6477 $hash, $hash_parent);
Kay Sievers19806692005-08-07 20:26:27 +02006478}
Jakub Narebski717b8312006-07-31 21:22:15 +02006479
6480## ......................................................................
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006481## feeds (RSS, Atom; OPML)
Jakub Narebski717b8312006-07-31 21:22:15 +02006482
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006483sub git_feed {
6484 my $format = shift || 'atom';
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006485 my $have_blame = gitweb_check_feature('blame');
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006486
6487 # Atom: http://www.atomenabled.org/developers/syndication/
6488 # RSS: http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
6489 if ($format ne 'rss' && $format ne 'atom') {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006490 die_error(400, "Unknown web feed format");
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006491 }
6492
6493 # log/feed of current (HEAD) branch, log of given branch, history of file/directory
6494 my $head = $hash || 'HEAD';
Jakub Narebski311e5522008-02-26 13:22:06 +01006495 my @commitlist = parse_commits($head, 150, 0, $file_name);
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006496
6497 my %latest_commit;
6498 my %latest_date;
6499 my $content_type = "application/$format+xml";
6500 if (defined $cgi->http('HTTP_ACCEPT') &&
6501 $cgi->Accept('text/xml') > $cgi->Accept($content_type)) {
6502 # browser (feed reader) prefers text/xml
6503 $content_type = 'text/xml';
6504 }
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00006505 if (defined($commitlist[0])) {
6506 %latest_commit = %{$commitlist[0]};
Giuseppe Bilottacd956c72009-01-26 12:50:16 +01006507 my $latest_epoch = $latest_commit{'committer_epoch'};
6508 %latest_date = parse_date($latest_epoch);
6509 my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
6510 if (defined $if_modified) {
6511 my $since;
6512 if (eval { require HTTP::Date; 1; }) {
6513 $since = HTTP::Date::str2time($if_modified);
6514 } elsif (eval { require Time::ParseDate; 1; }) {
6515 $since = Time::ParseDate::parsedate($if_modified, GMT => 1);
6516 }
6517 if (defined $since && $latest_epoch <= $since) {
6518 print $cgi->header(
6519 -type => $content_type,
6520 -charset => 'utf-8',
6521 -last_modified => $latest_date{'rfc2822'},
6522 -status => '304 Not Modified');
6523 return;
6524 }
6525 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006526 print $cgi->header(
6527 -type => $content_type,
6528 -charset => 'utf-8',
6529 -last_modified => $latest_date{'rfc2822'});
6530 } else {
6531 print $cgi->header(
6532 -type => $content_type,
6533 -charset => 'utf-8');
6534 }
6535
6536 # Optimization: skip generating the body if client asks only
6537 # for Last-Modified date.
6538 return if ($cgi->request_method() eq 'HEAD');
6539
6540 # header variables
6541 my $title = "$site_name - $project/$action";
6542 my $feed_type = 'log';
6543 if (defined $hash) {
6544 $title .= " - '$hash'";
6545 $feed_type = 'branch log';
6546 if (defined $file_name) {
6547 $title .= " :: $file_name";
6548 $feed_type = 'history';
6549 }
6550 } elsif (defined $file_name) {
6551 $title .= " - $file_name";
6552 $feed_type = 'history';
6553 }
6554 $title .= " $feed_type";
6555 my $descr = git_get_project_description($project);
6556 if (defined $descr) {
6557 $descr = esc_html($descr);
6558 } else {
6559 $descr = "$project " .
6560 ($format eq 'rss' ? 'RSS' : 'Atom') .
6561 " feed";
6562 }
6563 my $owner = git_get_project_owner($project);
6564 $owner = esc_html($owner);
6565
6566 #header
6567 my $alt_url;
6568 if (defined $file_name) {
6569 $alt_url = href(-full=>1, action=>"history", hash=>$hash, file_name=>$file_name);
6570 } elsif (defined $hash) {
6571 $alt_url = href(-full=>1, action=>"log", hash=>$hash);
6572 } else {
6573 $alt_url = href(-full=>1, action=>"summary");
6574 }
6575 print qq!<?xml version="1.0" encoding="utf-8"?>\n!;
6576 if ($format eq 'rss') {
6577 print <<XML;
Jakub Narebski59b9f612006-08-22 23:42:53 +02006578<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
6579<channel>
Jakub Narebski59b9f612006-08-22 23:42:53 +02006580XML
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006581 print "<title>$title</title>\n" .
6582 "<link>$alt_url</link>\n" .
6583 "<description>$descr</description>\n" .
Giuseppe Bilotta3ac109a2009-01-26 12:50:13 +01006584 "<language>en</language>\n" .
6585 # project owner is responsible for 'editorial' content
6586 "<managingEditor>$owner</managingEditor>\n";
Giuseppe Bilotta1ba68ce2009-01-26 12:50:11 +01006587 if (defined $logo || defined $favicon) {
6588 # prefer the logo to the favicon, since RSS
6589 # doesn't allow both
6590 my $img = esc_url($logo || $favicon);
6591 print "<image>\n" .
6592 "<url>$img</url>\n" .
6593 "<title>$title</title>\n" .
6594 "<link>$alt_url</link>\n" .
6595 "</image>\n";
6596 }
Giuseppe Bilotta0cf31282009-01-26 12:50:14 +01006597 if (%latest_date) {
6598 print "<pubDate>$latest_date{'rfc2822'}</pubDate>\n";
6599 print "<lastBuildDate>$latest_date{'rfc2822'}</lastBuildDate>\n";
6600 }
Giuseppe Bilottaad59a7a2009-01-26 12:50:12 +01006601 print "<generator>gitweb v.$version/$git_version</generator>\n";
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006602 } elsif ($format eq 'atom') {
6603 print <<XML;
6604<feed xmlns="http://www.w3.org/2005/Atom">
6605XML
6606 print "<title>$title</title>\n" .
6607 "<subtitle>$descr</subtitle>\n" .
6608 '<link rel="alternate" type="text/html" href="' .
6609 $alt_url . '" />' . "\n" .
6610 '<link rel="self" type="' . $content_type . '" href="' .
6611 $cgi->self_url() . '" />' . "\n" .
6612 "<id>" . href(-full=>1) . "</id>\n" .
6613 # use project owner for feed author
6614 "<author><name>$owner</name></author>\n";
6615 if (defined $favicon) {
6616 print "<icon>" . esc_url($favicon) . "</icon>\n";
6617 }
6618 if (defined $logo_url) {
6619 # not twice as wide as tall: 72 x 27 pixels
Jakub Narebskie1147262006-12-04 14:09:43 +01006620 print "<logo>" . esc_url($logo) . "</logo>\n";
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006621 }
6622 if (! %latest_date) {
6623 # dummy date to keep the feed valid until commits trickle in:
6624 print "<updated>1970-01-01T00:00:00Z</updated>\n";
6625 } else {
6626 print "<updated>$latest_date{'iso-8601'}</updated>\n";
6627 }
Giuseppe Bilottaad59a7a2009-01-26 12:50:12 +01006628 print "<generator version='$version/$git_version'>gitweb</generator>\n";
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006629 }
Jakub Narebski717b8312006-07-31 21:22:15 +02006630
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006631 # contents
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00006632 for (my $i = 0; $i <= $#commitlist; $i++) {
6633 my %co = %{$commitlist[$i]};
6634 my $commit = $co{'id'};
Jakub Narebski717b8312006-07-31 21:22:15 +02006635 # we read 150, we always show 30 and the ones more recent than 48 hours
Jakub Narebski91fd2bf2006-11-25 15:54:34 +01006636 if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02006637 last;
6638 }
Jakub Narebski91fd2bf2006-11-25 15:54:34 +01006639 my %cd = parse_date($co{'author_epoch'});
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006640
6641 # get list of changed files
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00006642 open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebskic906b182007-05-19 02:47:51 +02006643 $co{'parent'} || "--root",
6644 $co{'id'}, "--", (defined $file_name ? $file_name : ())
Jakub Narebski6bcf4b42006-08-27 23:49:36 +02006645 or next;
Jakub Narebski717b8312006-07-31 21:22:15 +02006646 my @difftree = map { chomp; $_ } <$fd>;
Jakub Narebski6bcf4b42006-08-27 23:49:36 +02006647 close $fd
6648 or next;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006649
6650 # print element (entry, item)
Florian La Rochee62a6412008-02-03 12:38:46 +01006651 my $co_url = href(-full=>1, action=>"commitdiff", hash=>$commit);
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006652 if ($format eq 'rss') {
6653 print "<item>\n" .
6654 "<title>" . esc_html($co{'title'}) . "</title>\n" .
6655 "<author>" . esc_html($co{'author'}) . "</author>\n" .
6656 "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
6657 "<guid isPermaLink=\"true\">$co_url</guid>\n" .
6658 "<link>$co_url</link>\n" .
6659 "<description>" . esc_html($co{'title'}) . "</description>\n" .
6660 "<content:encoded>" .
6661 "<![CDATA[\n";
6662 } elsif ($format eq 'atom') {
6663 print "<entry>\n" .
6664 "<title type=\"html\">" . esc_html($co{'title'}) . "</title>\n" .
6665 "<updated>$cd{'iso-8601'}</updated>\n" .
Jakub Narebskiab23c192006-11-25 15:54:33 +01006666 "<author>\n" .
6667 " <name>" . esc_html($co{'author_name'}) . "</name>\n";
6668 if ($co{'author_email'}) {
6669 print " <email>" . esc_html($co{'author_email'}) . "</email>\n";
6670 }
6671 print "</author>\n" .
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006672 # use committer for contributor
Jakub Narebskiab23c192006-11-25 15:54:33 +01006673 "<contributor>\n" .
6674 " <name>" . esc_html($co{'committer_name'}) . "</name>\n";
6675 if ($co{'committer_email'}) {
6676 print " <email>" . esc_html($co{'committer_email'}) . "</email>\n";
6677 }
6678 print "</contributor>\n" .
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006679 "<published>$cd{'iso-8601'}</published>\n" .
6680 "<link rel=\"alternate\" type=\"text/html\" href=\"$co_url\" />\n" .
6681 "<id>$co_url</id>\n" .
6682 "<content type=\"xhtml\" xml:base=\"" . esc_url($my_url) . "\">\n" .
6683 "<div xmlns=\"http://www.w3.org/1999/xhtml\">\n";
6684 }
Jakub Narebski717b8312006-07-31 21:22:15 +02006685 my $comment = $co{'comment'};
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006686 print "<pre>\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02006687 foreach my $line (@$comment) {
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006688 $line = esc_html($line);
6689 print "$line\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02006690 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006691 print "</pre><ul>\n";
6692 foreach my $difftree_line (@difftree) {
6693 my %difftree = parse_difftree_raw_line($difftree_line);
6694 next if !$difftree{'from_id'};
6695
6696 my $file = $difftree{'file'} || $difftree{'to_file'};
6697
6698 print "<li>" .
6699 "[" .
6700 $cgi->a({-href => href(-full=>1, action=>"blobdiff",
6701 hash=>$difftree{'to_id'}, hash_parent=>$difftree{'from_id'},
6702 hash_base=>$co{'id'}, hash_parent_base=>$co{'parent'},
6703 file_name=>$file, file_parent=>$difftree{'from_file'}),
6704 -title => "diff"}, 'D');
6705 if ($have_blame) {
6706 print $cgi->a({-href => href(-full=>1, action=>"blame",
6707 file_name=>$file, hash_base=>$commit),
6708 -title => "blame"}, 'B');
Jakub Narebski717b8312006-07-31 21:22:15 +02006709 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006710 # if this is not a feed of a file history
6711 if (!defined $file_name || $file_name ne $file) {
6712 print $cgi->a({-href => href(-full=>1, action=>"history",
6713 file_name=>$file, hash=>$commit),
6714 -title => "history"}, 'H');
6715 }
6716 $file = esc_path($file);
6717 print "] ".
6718 "$file</li>\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02006719 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006720 if ($format eq 'rss') {
6721 print "</ul>]]>\n" .
6722 "</content:encoded>\n" .
6723 "</item>\n";
6724 } elsif ($format eq 'atom') {
6725 print "</ul>\n</div>\n" .
6726 "</content>\n" .
6727 "</entry>\n";
6728 }
Jakub Narebski717b8312006-07-31 21:22:15 +02006729 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006730
6731 # end of feed
6732 if ($format eq 'rss') {
6733 print "</channel>\n</rss>\n";
Jakub Narebski3278fbc2009-05-11 19:37:28 +02006734 } elsif ($format eq 'atom') {
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006735 print "</feed>\n";
6736 }
6737}
6738
6739sub git_rss {
6740 git_feed('rss');
6741}
6742
6743sub git_atom {
6744 git_feed('atom');
Jakub Narebski717b8312006-07-31 21:22:15 +02006745}
6746
6747sub git_opml {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006748 my @list = git_get_projects_list();
Jakub Narebski717b8312006-07-31 21:22:15 +02006749
Giuseppe Bilottaae357852009-01-02 13:49:30 +01006750 print $cgi->header(
6751 -type => 'text/xml',
6752 -charset => 'utf-8',
6753 -content_disposition => 'inline; filename="opml.xml"');
6754
Jakub Narebski59b9f612006-08-22 23:42:53 +02006755 print <<XML;
6756<?xml version="1.0" encoding="utf-8"?>
6757<opml version="1.0">
6758<head>
Petr Baudis8be28902006-10-24 05:18:39 +02006759 <title>$site_name OPML Export</title>
Jakub Narebski59b9f612006-08-22 23:42:53 +02006760</head>
6761<body>
6762<outline text="git RSS feeds">
6763XML
Jakub Narebski717b8312006-07-31 21:22:15 +02006764
6765 foreach my $pr (@list) {
6766 my %proj = %$pr;
Jakub Narebski847e01f2006-08-14 02:05:47 +02006767 my $head = git_get_head_hash($proj{'path'});
Jakub Narebski717b8312006-07-31 21:22:15 +02006768 if (!defined $head) {
6769 next;
6770 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02006771 $git_dir = "$projectroot/$proj{'path'}";
Jakub Narebski847e01f2006-08-14 02:05:47 +02006772 my %co = parse_commit($head);
Jakub Narebski717b8312006-07-31 21:22:15 +02006773 if (!%co) {
6774 next;
6775 }
6776
6777 my $path = esc_html(chop_str($proj{'path'}, 25, 5));
Giuseppe Bilottadf63fbb2009-01-02 13:15:28 +01006778 my $rss = href('project' => $proj{'path'}, 'action' => 'rss', -full => 1);
6779 my $html = href('project' => $proj{'path'}, 'action' => 'summary', -full => 1);
Jakub Narebski717b8312006-07-31 21:22:15 +02006780 print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";
6781 }
Jakub Narebski59b9f612006-08-22 23:42:53 +02006782 print <<XML;
6783</outline>
6784</body>
6785</opml>
6786XML
Jakub Narebski717b8312006-07-31 21:22:15 +02006787}