All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCHv2 00/11] Splitting gitweb
@ 2010-07-15  7:29 Pavan Kumar Sunkara
  2010-07-15  7:29 ` [PATCHv2 GSOC 01/11] gitweb: fix esc_url Pavan Kumar Sunkara
                   ` (11 more replies)
  0 siblings, 12 replies; 27+ messages in thread
From: Pavan Kumar Sunkara @ 2010-07-15  7:29 UTC (permalink / raw)
  To: git, jnareb, chriscool, pasky; +Cc: Pavan Kumar Sunkara

The patch series is based on 'pu' branch.

10 patches out of the 11 patches in this patch series split gitweb into
several small sized modules which is one of the goal of my GSoC's project.

The first patch of this patch series is for fixing esc_url function which is
previously missed by commit 425e225.

The second patch is produced by my commit ammend to jakub nareski's
initial commit to prepare splitting of gitweb in the message-id:
http://mid.gmane.org/1276531710-22945-4-git-send-email-jnareb@gmail.com

There is a small ammendment to that patch.
$(INSTALL) -m 644 $(mod) '$(DESTDIR_SQ)$(gitwebdir_SQ)/$(mod)'
is changed to
$(INSTALL) -m 644 $(mod) '$(DESTDIR_SQ)$(gitwebdir_SQ)/$(dir $(mod))';)
because it is better if we gave 'dir $(mod)' as the target for install command
rather than '$(mod)' because it may cause problems in the future when gitweblibdir
is used and the modules are installed elsewhere rather than in /use/share/gitweb.

The modules created and being used by Gitweb are
       Gitweb::Git
       Gitweb::Config
       Gitweb::Request
       Gitweb::Escape
       Gitweb::RepoConfig
       Gitweb::View
       Gitweb::Util
       Gitweb::Format
       Gitweb::Parse

Table of contents:
=======

1. gitweb: fix esc_url
The custom CGI escaping done in esc_url failed to escape UTF-8
properly. Fix by using CGI::escape on each sequence of matched
characters instead of sprintf()ing a custom escaping for each byte.

Additionally, the space -> + escape was being escaped due to greedy
matching on the first substitution. Fix by adding space to the
list of characters not handled on the first substitution.

Finally, remove an unnecessary escaping of the + sign.

commit 452e225 has missed fixing esc_url.

2. gitweb: Prepare for splitting gitweb
Prepare gitweb for having been split into modules that are to be
installed alongside gitweb in 'lib/' subdirectory, by adding

  use lib __DIR__.'/lib';

to gitweb.perl (to main gitweb script), and preparing for putting
modules (relative path) in $(GITWEB_MODULES) in gitweb/Makefile.

3. gitweb: Create Gitweb::Git module
Create a Gitweb::Git module in  'gitweb/lib/Gitweb/Git.pm'
to deal with running git commands (and also processing output
of git commands with external programs) from gitweb.

This module is intended as standalone module, which does not require
(include) other gitweb' modules to avoid circular dependencies.  That
is why it includes $GIT variable, even though this variable is
configured during building gitweb.  On the other hand $GIT is more
about git configuration, than gitweb configuration.

Subroutines moved:
	evaluate_git_version
	git_cmd
	quote_command

Update gitweb/Makefile to install Gitweb::Git module alongside gitweb

4. gitweb: Create Gitweb::Config module
Create Gitweb::Config module in 'gitweb/lib/Gitweb/Config.pm'
to store all the configuration variables and subroutines
regarding the gitweb.perl script.

This module depends only on $git_dir from Gitweb::Git and
includes the configuration related variables and
subroutines (Including those required for configuration
of gitweb features)

Subroutines moved:
	evaluate_gitweb_config
	configure_gitweb_features
	filter_snapshot_fmts
	gitweb_get_feature
	gitweb_check_feature

Update gitweb/Makefile to install Gitweb::Config module alongside gitweb

5. gitweb: Create Gitweb::Request module
Create a Gitweb::Request module in 'gitweb/lib/Gitweb/Request.pm'
to store and handle all the cgi params and related variables
regarding the gitweb.perl script.

This module is intended as standalone module, which does not require
(include) other gitweb' modules to avoid circular dependencies.

Subroutines moved:
	evaluate_uri
	evaluate_query_params

Update gitweb/Makefile to install Gitweb::Request module alongside gitweb

6. gitweb: Create Gitweb::Escape module
Create a Gitweb::Escape module in 'gitweb/lib/Gitweb/Escape.pm'
to store all the quoting/unquoting and escaping subroutines
regarding the gitweb.perl script.

This module imports $fallback_encoding variable from
Gitweb::Config module to use it in sub 'to_utf8'

Subroutines moved:
	to_utf8
	esc_param
	esc_url
	esc_html
	esc_path
	quot_cec
	quot_upr
	untabify

Update gitweb/Makefile to install Gitweb::Escape module alongside gitweb

7. gitweb: Create Gitweb::RepoConfig module
Create a Gitweb::RepoConfig module in 'gitweb/lib/Gitweb/RepoConfig.pm'
to store and handle all the configuration and subroutines
related to a single repository regarding the gitweb.perl script.

This module depend on several other modules like Git.pm,
Config.pm, Request.pm and Escape.pm.

It also include subroutines regarding project_list and
it's handling.

Subroutines moved:
	check_head_link
	check_export_ok
	hash_set_multi
	git_parse_project_config
	config_to_bool
	config_to_int
	config_to_multi
	feature_bool
	feature_snapshot
	feature_patches
	feature_avatar
	git_get_project_config
	git_get_project_description
	git_get_project_ctags
	git_populate_project_tagcloud
	git_show_project_tagcloud
	git_get_project_url_list
	git_get_projects_list
	git_get_project_list_from_file
	git_get_project_owner
	get_file_owner
	project_in_list

Update gitweb/Makefile to install Gitweb::RepoConfig module
alongside gitweb

8. gitweb: Create Gitweb::View module
Create Gitweb::View module in 'gitweb/lib/Gitweb/View.pm'
to store the subroutines related to the HTML output
for gitweb.

This module depends on Git.pm, Config.pm, Request.pm,
Escape.pm and RepoConfig.pm. Action specific HTML div
subroutines are not included in this module due to
unmet dependencies of Gitweb::Parse and Gitweb::Format.

Subroutines moved:
	*href
	 get_feed_info
	*chop_str
	 chop_and_escape_str
	*age_class
	 age_string
	*S_ISGITLINK
	 mode_str
	 file_type
	 file_type_long
	*get_page_title
	 git_header_html
	 git_footer_html
	 die_error
	*git_print_page_nav
	 format_paging_nav
	 git_print_header_div
	 git_print_page_path
	*print_local_time
	 format_local_time
	*insert_file
	*git_get_link_target
	 normalize_link_target
	 git_print_tree_entry
	*print_sort_th
	 format_sort_th

Update 'gitweb/Makefile' to install Gitweb::View alongside gitweb.

9. gitweb: Create Gitweb::Util module
Create Gitweb::Util module in 'gitweb/lib/Gitweb/Util.pm'
to store the git utility subroutines related to gitweb.

This module include subroutines in various categories
such as git utility subs invoking git commands, git
utility subs accessing git repository, mimetype related
subs and HTML output utility subs.

Subroutines moved:
	git_get_head_hash
	git_get_full_hash
	git_get_short_hash
	git_get_hash
	git_get_type
	git_get_hash_by_path
	git_get_path_by_hash
	git_get_last_activity
	git_get_references
	git_get_rev_name_tags
	git_get_heads_list
	git_get_tags_list
	mimetype_guess_file
	mimetype_guess
	blob_mimetype
	blob_contenttype
	guess_file_syntax
	run_highlighter
	fill_from_file_info
	is_deleted
	is_patch_split

Update 'gitweb/Makefile' to install Gitweb::Util alongside gitweb.

10. gitweb: Create Gitweb::Format module
Create Gitweb::Format module in 'gitweb/lib/Gitweb/Format.pm'
to store the subroutines related to formatting of HTML
fragments required for gitweb.

This module depends on Config.pm, View.pm, Escape.pm,
Util.pm and Request.pm. It mainly contain functions returning
short HTML fragments or transforming HTML fragments. Also
include subroutines regarding avatar formatting.

Subroutines moved:
	format_log_line_html
	format_ref_marker
	format_subject_html
	picon_url
	gravatar_url
	git_get_avatar
	format_search_author
	format_author_html
	format_git_diff_header_line
	format_extended_diff_header_line
	format_diff_from_to_header
	format_diff_cc_simplified
	format_diff_line
	format_snapshot_links

Update 'gitweb/Makefile' to install Gitweb::Format alongside gitweb.

11. gitweb: Create Gitweb::Parse module
Create Gitweb::Parse module in 'gitweb/lib/Gitweb/Parse.pm'
to store the subroutines which related to parsing functions
required for gitweb.

This module depends on Git.pm, Escape.pm, View.pm and Util.pm.

Subroutines moved:
	unquote
	parse_date
	parse_tag
	parse_commit_text
	parse_commit
	parse_commits
	parse_difftree_raw_line
	parsed_difftree_line
	parse_ls_tree_line
	parse_from_to_diffinfo

Update 'gitweb/Makefile' to install Gitweb::Parse alongside gitweb.


Pavan Kumar Sunkara (11):
  gitweb: fix esc_url
  gitweb: Prepare for splitting gitweb
  gitweb: Create Gitweb::Git module
  gitweb: Create Gitweb::Config module
  gitweb: Create Gitweb::Request module
  gitweb: Create Gitweb::Escape module
  gitweb: Create Gitweb::RepoConfig module
  gitweb: Create Gitweb::View module
  gitweb: Create Gitweb::Util module
  gitweb: Create Gitweb::Format module
  gitweb: Create Gitweb::Parse module

 gitweb/Makefile                 |   14 +
 gitweb/gitweb.perl              | 3861 +++------------------------------------
 gitweb/lib/Gitweb/Config.pm     |  498 +++++
 gitweb/lib/Gitweb/Escape.pm     |  138 ++
 gitweb/lib/Gitweb/Format.pm     |  537 ++++++
 gitweb/lib/Gitweb/Git.pm        |   48 +
 gitweb/lib/Gitweb/Parse.pm      |  414 +++++
 gitweb/lib/Gitweb/RepoConfig.pm |  466 +++++
 gitweb/lib/Gitweb/Request.pm    |  119 ++
 gitweb/lib/Gitweb/Util.pm       |  447 +++++
 gitweb/lib/Gitweb/View.pm       | 1022 +++++++++++
 11 files changed, 3915 insertions(+), 3649 deletions(-)
 create mode 100644 gitweb/lib/Gitweb/Config.pm
 create mode 100644 gitweb/lib/Gitweb/Escape.pm
 create mode 100644 gitweb/lib/Gitweb/Format.pm
 create mode 100644 gitweb/lib/Gitweb/Git.pm
 create mode 100644 gitweb/lib/Gitweb/Parse.pm
 create mode 100644 gitweb/lib/Gitweb/RepoConfig.pm
 create mode 100644 gitweb/lib/Gitweb/Request.pm
 create mode 100644 gitweb/lib/Gitweb/Util.pm
 create mode 100644 gitweb/lib/Gitweb/View.pm

^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCHv2 GSOC 01/11] gitweb: fix esc_url
  2010-07-15  7:29 [PATCHv2 00/11] Splitting gitweb Pavan Kumar Sunkara
@ 2010-07-15  7:29 ` Pavan Kumar Sunkara
  2010-07-15 13:52   ` Jakub Narebski
  2010-07-15  7:29 ` [PATCHv2 GSOC 02/11] gitweb: Prepare for splitting gitweb Pavan Kumar Sunkara
                   ` (10 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: Pavan Kumar Sunkara @ 2010-07-15  7:29 UTC (permalink / raw)
  To: git, jnareb, chriscool, pasky; +Cc: Pavan Kumar Sunkara

The custom CGI escaping done in esc_url failed to escape UTF-8
properly. Fix by using CGI::escape on each sequence of matched
characters instead of sprintf()ing a custom escaping for each byte.

Additionally, the space -> + escape was being escaped due to greedy
matching on the first substitution. Fix by adding space to the
list of characters not handled on the first substitution.

Finally, remove an unnecessary escaping of the + sign.

commit 452e225 has missed fixing esc_url.

Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
---
 gitweb/gitweb.perl |    3 +--
 1 files changed, 1 insertions(+), 2 deletions(-)

diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 9446376..518328f 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -1322,8 +1322,7 @@ sub esc_param {
 sub esc_url {
 	my $str = shift;
 	return undef unless defined $str;
-	$str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
-	$str =~ s/\+/%2B/g;
+	$str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&= ]+)/CGI::escape($1)/eg;
 	$str =~ s/ /\+/g;
 	return $str;
 }
-- 
1.7.1.455.g8f441

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCHv2 GSOC 02/11] gitweb: Prepare for splitting gitweb
  2010-07-15  7:29 [PATCHv2 00/11] Splitting gitweb Pavan Kumar Sunkara
  2010-07-15  7:29 ` [PATCHv2 GSOC 01/11] gitweb: fix esc_url Pavan Kumar Sunkara
@ 2010-07-15  7:29 ` Pavan Kumar Sunkara
  2010-07-15 18:05   ` Jakub Narebski
  2010-07-15  7:29 ` [PATCHv2 GSOC 03/11] gitweb: Create Gitweb::Git module Pavan Kumar Sunkara
                   ` (9 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: Pavan Kumar Sunkara @ 2010-07-15  7:29 UTC (permalink / raw)
  To: git, jnareb, chriscool, pasky; +Cc: Pavan Kumar Sunkara

Prepare gitweb for having been split into modules that are to be
installed alongside gitweb in 'lib/' subdirectory, by adding

  use lib __DIR__.'/lib';

to gitweb.perl (to main gitweb script), and preparing for putting
modules (relative path) in $(GITWEB_MODULES) in gitweb/Makefile.

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
---
 gitweb/Makefile    |    3 +++
 gitweb/gitweb.perl |    9 +++++++++
 2 files changed, 12 insertions(+), 0 deletions(-)

diff --git a/gitweb/Makefile b/gitweb/Makefile
index d2584fe..c7610b3 100644
--- a/gitweb/Makefile
+++ b/gitweb/Makefile
@@ -55,6 +55,7 @@ PERL_PATH  ?= /usr/bin/perl
 bindir_SQ = $(subst ','\'',$(bindir))#'
 gitwebdir_SQ = $(subst ','\'',$(gitwebdir))#'
 gitwebstaticdir_SQ = $(subst ','\'',$(gitwebdir)/static)#'
+gitweblibdir_SQ = $(subst ','\'',$(gitwebdir)/lib)#'
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))#'
 PERL_PATH_SQ  = $(subst ','\'',$(PERL_PATH))#'
 DESTDIR_SQ    = $(subst ','\'',$(DESTDIR))#'
@@ -150,6 +151,8 @@ install: all
 	$(INSTALL) -m 755 $(GITWEB_PROGRAMS) '$(DESTDIR_SQ)$(gitwebdir_SQ)'
 	$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)'
 	$(INSTALL) -m 644 $(GITWEB_FILES) '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)'
+	$(foreach dir,$(sort $(dir $(GITWEB_MODULES))),test -d '$(DESTDIR_SQ)$(gitwebdir_SQ)/$(dir)' || $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitwebdir_SQ)/$(dir)';)
+	$(foreach mod,$(GITWEB_MODULES),$(INSTALL) -m 644 $(mod) '$(DESTDIR_SQ)$(gitwebdir_SQ)/$(dir $(mod))';)
 
 ### Cleaning rules
 
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 518328f..bda7da3 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -9,6 +9,14 @@
 
 use strict;
 use warnings;
+
+use File::Spec;
+# __DIR__ is taken from Dir::Self __DIR__ fragment
+sub __DIR__ () {
+	File::Spec->rel2abs(join '', (File::Spec->splitpath(__FILE__))[0, 1]);
+}
+use lib __DIR__ . '/lib';
+
 use CGI qw(:standard :escapeHTML -nosticky);
 use CGI::Util qw(unescape);
 use CGI::Carp qw(fatalsToBrowser set_message);
@@ -16,6 +24,7 @@ use Encode;
 use Fcntl ':mode';
 use File::Find qw();
 use File::Basename qw(basename);
+
 binmode STDOUT, ':utf8';
 
 our $t0;
-- 
1.7.1.455.g8f441

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCHv2 GSOC 03/11] gitweb: Create Gitweb::Git module
  2010-07-15  7:29 [PATCHv2 00/11] Splitting gitweb Pavan Kumar Sunkara
  2010-07-15  7:29 ` [PATCHv2 GSOC 01/11] gitweb: fix esc_url Pavan Kumar Sunkara
  2010-07-15  7:29 ` [PATCHv2 GSOC 02/11] gitweb: Prepare for splitting gitweb Pavan Kumar Sunkara
@ 2010-07-15  7:29 ` Pavan Kumar Sunkara
  2010-07-15 20:13   ` Jakub Narebski
  2010-07-15  7:29 ` [PATCHv2 GSOC 04/11] gitweb: Create Gitweb::Config module Pavan Kumar Sunkara
                   ` (8 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: Pavan Kumar Sunkara @ 2010-07-15  7:29 UTC (permalink / raw)
  To: git, jnareb, chriscool, pasky; +Cc: Pavan Kumar Sunkara

Create a Gitweb::Git module in  'gitweb/lib/Gitweb/Git.pm'
to deal with running git commands (and also processing output
of git commands with external programs) from gitweb.

This module is intended as standalone module, which does not require
(include) other gitweb' modules to avoid circular dependencies.  That
is why it includes $GIT variable, even though this variable is
configured during building gitweb.  On the other hand $GIT is more
about git configuration, than gitweb configuration.

Subroutines moved:
	evaluate_git_version
	git_cmd
	quote_command

Update gitweb/Makefile to install Gitweb::Git module alongside gitweb

Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
---
 gitweb/Makefile          |    3 ++
 gitweb/gitweb.perl       |   35 ++++----------------------------
 gitweb/lib/Gitweb/Git.pm |   48 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 56 insertions(+), 30 deletions(-)
 create mode 100644 gitweb/lib/Gitweb/Git.pm

diff --git a/gitweb/Makefile b/gitweb/Makefile
index c7610b3..5035c2e 100644
--- a/gitweb/Makefile
+++ b/gitweb/Makefile
@@ -111,6 +111,9 @@ endif
 
 GITWEB_FILES += static/git-logo.png static/git-favicon.png
 
+# Modules: Gitweb::*
+GITWEB_MODULES += lib/Gitweb/Git.pm
+
 GITWEB_REPLACE = \
 	-e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
 	-e 's|++GIT_BINDIR++|$(bindir)|g' \
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index bda7da3..778ac13 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -27,11 +27,12 @@ use File::Basename qw(basename);
 
 binmode STDOUT, ':utf8';
 
+use Gitweb::Git;
+
 our $t0;
 if (eval { require Time::HiRes; 1; }) {
 	$t0 = [Time::HiRes::gettimeofday()];
 }
-our $number_of_git_cmds = 0;
 
 BEGIN {
 	CGI->compile() if $ENV{'MOD_PERL'};
@@ -75,9 +76,8 @@ sub evaluate_uri {
 	our $home_link = $my_uri || "/";
 }
 
-# core git executable to use
-# this can just be "git" if your webserver has a sensible PATH
-our $GIT = "++GIT_BINDIR++/git";
+# $GIT is from Gitweb::Git
+$GIT = "++GIT_BINDIR++/git";
 
 # absolute fs-path which will be prepended to the project path
 #our $projectroot = "/pub/scm";
@@ -482,7 +482,6 @@ sub gitweb_get_feature {
 		$feature{$name}{'override'},
 		@{$feature{$name}{'default'}});
 	# project specific override is possible only if we have project
-	our $git_dir; # global variable, declared later
 	if (!$override || !defined $git_dir) {
 		return @defaults;
 	}
@@ -616,13 +615,6 @@ sub get_loadavg {
 	return 0;
 }
 
-# version of the core git binary
-our $git_version;
-sub evaluate_git_version {
-	our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
-	$number_of_git_cmds++;
-}
-
 sub check_loadavg {
 	if (defined $maxload && get_loadavg() > $maxload) {
 		die_error(503, "The load average on the server is too high");
@@ -972,10 +964,8 @@ sub evaluate_and_validate_params {
 	}
 }
 
-# path to the current git repository
-our $git_dir;
 sub evaluate_git_dir {
-	our $git_dir = "$projectroot/$project" if $project;
+	$git_dir = "$projectroot/$project" if $project;
 }
 
 our (@snapshot_fmts, $git_avatar);
@@ -2230,21 +2220,6 @@ sub get_feed_info {
 ## ----------------------------------------------------------------------
 ## git utility subroutines, invoking git commands
 
-# returns path to the core git executable and the --git-dir parameter as list
-sub git_cmd {
-	$number_of_git_cmds++;
-	return $GIT, '--git-dir='.$git_dir;
-}
-
-# quote the given arguments for passing them to the shell
-# quote_command("command", "arg 1", "arg with ' and ! characters")
-# => "'command' 'arg 1' 'arg with '\'' and '\!' characters'"
-# Try to avoid using this function wherever possible.
-sub quote_command {
-	return join(' ',
-		map { my $a = $_; $a =~ s/(['!])/'\\$1'/g; "'$a'" } @_ );
-}
-
 # get HEAD ref of given project as hash
 sub git_get_head_hash {
 	return git_get_full_hash(shift, 'HEAD');
diff --git a/gitweb/lib/Gitweb/Git.pm b/gitweb/lib/Gitweb/Git.pm
new file mode 100644
index 0000000..467497c
--- /dev/null
+++ b/gitweb/lib/Gitweb/Git.pm
@@ -0,0 +1,48 @@
+#!/usr/bin/perl
+#
+# Gitweb::Git -- gitweb's package dealing with running git commands
+#
+# This program is licensed under the GPLv2
+
+package Gitweb::Git;
+
+use strict;
+use warnings;
+use Exporter qw(import);
+
+our @EXPORT = qw($GIT $number_of_git_cmds $git_version $git_dir
+                 git_cmd quote_command evaluate_git_version);
+
+# core git executable to use
+# this can just be "git" if your webserver has a sensible PATH
+our $GIT;
+
+our $number_of_git_cmds = 0;
+
+# version of the core git binary
+our $git_version;
+
+# path to the current git repository
+our $git_dir;
+
+# returns path to the core git executable and the --git-dir parameter as list
+sub git_cmd {
+	$number_of_git_cmds++;
+	return $GIT, '--git-dir='.$git_dir;
+}
+
+# quote the given arguments for passing them to the shell
+# quote_command("command", "arg 1", "arg with ' and ! characters")
+# => "'command' 'arg 1' 'arg with '\'' and '\!' characters'"
+# Try to avoid using this function wherever possible.
+sub quote_command {
+	return join(' ',
+		map { my $a = $_; $a =~ s/(['!])/'\\$1'/g; "'$a'" } @_ );
+}
+
+sub evaluate_git_version {
+	$git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
+	$number_of_git_cmds++;
+}
+
+1;
-- 
1.7.1.455.g8f441

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCHv2 GSOC 04/11] gitweb: Create Gitweb::Config module
  2010-07-15  7:29 [PATCHv2 00/11] Splitting gitweb Pavan Kumar Sunkara
                   ` (2 preceding siblings ...)
  2010-07-15  7:29 ` [PATCHv2 GSOC 03/11] gitweb: Create Gitweb::Git module Pavan Kumar Sunkara
@ 2010-07-15  7:29 ` Pavan Kumar Sunkara
  2010-07-15 21:21   ` Jakub Narebski
  2010-07-15  7:29 ` [PATCHv2 GSOC 05/11] gitweb: Create Gitweb::Request module Pavan Kumar Sunkara
                   ` (7 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: Pavan Kumar Sunkara @ 2010-07-15  7:29 UTC (permalink / raw)
  To: git, jnareb, chriscool, pasky; +Cc: Pavan Kumar Sunkara

Create Gitweb::Config module in 'gitweb/lib/Gitweb/Config.pm'
to store all the configuration variables and subroutines
regarding the gitweb.perl script.

This module depends only on $git_dir from Gitweb::Git and
includes the configuration related variables and
subroutines (Including those required for configuration
of gitweb features)

Subroutines moved:
	evaluate_gitweb_config
	configure_gitweb_features
	filter_snapshot_fmts
	gitweb_get_feature
	gitweb_check_feature

Update gitweb/Makefile to install Gitweb::Config module alongside gitweb

Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
---
 gitweb/Makefile             |    1 +
 gitweb/gitweb.perl          |  495 ++----------------------------------------
 gitweb/lib/Gitweb/Config.pm |  498 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 522 insertions(+), 472 deletions(-)
 create mode 100644 gitweb/lib/Gitweb/Config.pm

diff --git a/gitweb/Makefile b/gitweb/Makefile
index 5035c2e..492b0fa 100644
--- a/gitweb/Makefile
+++ b/gitweb/Makefile
@@ -113,6 +113,7 @@ GITWEB_FILES += static/git-logo.png static/git-favicon.png
 
 # Modules: Gitweb::*
 GITWEB_MODULES += lib/Gitweb/Git.pm
+GITWEB_MODULES += lib/Gitweb/Config.pm
 
 GITWEB_REPLACE = \
 	-e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 778ac13..b68cc71 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -28,6 +28,7 @@ use File::Basename qw(basename);
 binmode STDOUT, ':utf8';
 
 use Gitweb::Git;
+use Gitweb::Config;
 
 our $t0;
 if (eval { require Time::HiRes; 1; }) {
@@ -38,7 +39,10 @@ BEGIN {
 	CGI->compile() if $ENV{'MOD_PERL'};
 }
 
-our $version = "++GIT_VERSION++";
+# Only configuration variables with build-time overridable
+# defaults are listed below. The complete set of variables
+# with their descriptions is listed in Gitweb::Config.
+$version = "++GIT_VERSION++";
 
 our ($my_url, $my_uri, $base_url, $path_info, $home_link);
 sub evaluate_uri {
@@ -79,434 +83,31 @@ sub evaluate_uri {
 # $GIT is from Gitweb::Git
 $GIT = "++GIT_BINDIR++/git";
 
-# absolute fs-path which will be prepended to the project path
-#our $projectroot = "/pub/scm";
-our $projectroot = "++GITWEB_PROJECTROOT++";
+$projectroot = "++GITWEB_PROJECTROOT++";
+$project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++";
 
-# fs traversing limit for getting project list
-# the number is relative to the projectroot
-our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++";
-
-# string of the home link on top of all pages
-our $home_link_str = "++GITWEB_HOME_LINK_STR++";
-
-# name of your site or organization to appear in page titles
-# replace this with something more descriptive for clearer bookmarks
-our $site_name = "++GITWEB_SITENAME++"
+$home_link_str = "++GITWEB_HOME_LINK_STR++";
+$site_name = "++GITWEB_SITENAME++"
                  || ($ENV{'SERVER_NAME'} || "Untitled") . " Git";
+$site_header = "++GITWEB_SITE_HEADER++";
+$home_text = "++GITWEB_HOMETEXT++";
+$site_footer = "++GITWEB_SITE_FOOTER++";
 
-# filename of html text to include at top of each page
-our $site_header = "++GITWEB_SITE_HEADER++";
-# html text to include at home page
-our $home_text = "++GITWEB_HOMETEXT++";
-# filename of html text to include at bottom of each page
-our $site_footer = "++GITWEB_SITE_FOOTER++";
-
-# URI of stylesheets
-our @stylesheets = ("++GITWEB_CSS++");
-# URI of a single stylesheet, which can be overridden in GITWEB_CONFIG.
-our $stylesheet = undef;
-# URI of GIT logo (72x27 size)
-our $logo = "++GITWEB_LOGO++";
-# URI of GIT favicon, assumed to be image/png type
-our $favicon = "++GITWEB_FAVICON++";
-# URI of gitweb.js (JavaScript code for gitweb)
-our $javascript = "++GITWEB_JS++";
-
-# URI and label (title) of GIT logo link
-#our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/";
-#our $logo_label = "git documentation";
-our $logo_url = "http://git-scm.com/";
-our $logo_label = "git homepage";
-
-# source of projects list
-our $projects_list = "++GITWEB_LIST++";
-
-# the width (in characters) of the projects list "Description" column
-our $projects_list_description_width = 25;
-
-# default order of projects list
-# valid values are none, project, descr, owner, and age
-our $default_projects_order = "project";
-
-# show repository only if this file exists
-# (only effective if this variable evaluates to true)
-our $export_ok = "++GITWEB_EXPORT_OK++";
-
-# show repository only if this subroutine returns true
-# when given the path to the project, for example:
-#    sub { return -e "$_[0]/git-daemon-export-ok"; }
-our $export_auth_hook = undef;
-
-# only allow viewing of repositories also shown on the overview page
-our $strict_export = "++GITWEB_STRICT_EXPORT++";
-
-# list of git base URLs used for URL to where fetch project from,
-# i.e. full URL is "$git_base_url/$project"
-our @git_base_url_list = grep { $_ ne '' } ("++GITWEB_BASE_URL++");
-
-# default blob_plain mimetype and default charset for text/plain blob
-our $default_blob_plain_mimetype = 'text/plain';
-our $default_text_plain_charset  = undef;
-
-# file to use for guessing MIME types before trying /etc/mime.types
-# (relative to the current git repository)
-our $mimetypes_file = undef;
-
-# assume this charset if line contains non-UTF-8 characters;
-# it should be valid encoding (see Encoding::Supported(3pm) for list),
-# for which encoding all byte sequences are valid, for example
-# 'iso-8859-1' aka 'latin1' (it is decoded without checking, so it
-# could be even 'utf-8' for the old behavior)
-our $fallback_encoding = 'latin1';
-
-# rename detection options for git-diff and git-diff-tree
-# - default is '-M', with the cost proportional to
-#   (number of removed files) * (number of new files).
-# - more costly is '-C' (which implies '-M'), with the cost proportional to
-#   (number of changed files + number of removed files) * (number of new files)
-# - even more costly is '-C', '--find-copies-harder' with cost
-#   (number of files in the original tree) * (number of new files)
-# - one might want to include '-B' option, e.g. '-B', '-M'
-our @diff_opts = ('-M'); # taken from git_commit
-
-# Disables features that would allow repository owners to inject script into
-# the gitweb domain.
-our $prevent_xss = 0;
-
-# information about snapshot formats that gitweb is capable of serving
-our %known_snapshot_formats = (
-	# name => {
-	# 	'display' => display name,
-	# 	'type' => mime type,
-	# 	'suffix' => filename suffix,
-	# 	'format' => --format for git-archive,
-	# 	'compressor' => [compressor command and arguments]
-	# 	                (array reference, optional)
-	# 	'disabled' => boolean (optional)}
-	#
-	'tgz' => {
-		'display' => 'tar.gz',
-		'type' => 'application/x-gzip',
-		'suffix' => '.tar.gz',
-		'format' => 'tar',
-		'compressor' => ['gzip']},
-
-	'tbz2' => {
-		'display' => 'tar.bz2',
-		'type' => 'application/x-bzip2',
-		'suffix' => '.tar.bz2',
-		'format' => 'tar',
-		'compressor' => ['bzip2']},
-
-	'txz' => {
-		'display' => 'tar.xz',
-		'type' => 'application/x-xz',
-		'suffix' => '.tar.xz',
-		'format' => 'tar',
-		'compressor' => ['xz'],
-		'disabled' => 1},
-
-	'zip' => {
-		'display' => 'zip',
-		'type' => 'application/x-zip',
-		'suffix' => '.zip',
-		'format' => 'zip'},
-);
+@stylesheets = ("++GITWEB_CSS++");
+$stylesheet = undef;
+$logo = "++GITWEB_LOGO++";
+$favicon = "++GITWEB_FAVICON++";
+$javascript = "++GITWEB_JS++";
 
-# Aliases so we understand old gitweb.snapshot values in repository
-# configuration.
-our %known_snapshot_format_aliases = (
-	'gzip'  => 'tgz',
-	'bzip2' => 'tbz2',
-	'xz'    => 'txz',
-
-	# backward compatibility: legacy gitweb config support
-	'x-gzip' => undef, 'gz' => undef,
-	'x-bzip2' => undef, 'bz2' => undef,
-	'x-zip' => undef, '' => undef,
-);
+$projects_list = "++GITWEB_LIST++";
 
-# Pixel sizes for icons and avatars. If the default font sizes or lineheights
-# are changed, it may be appropriate to change these values too via
-# $GITWEB_CONFIG.
-our %avatar_size = (
-	'default' => 16,
-	'double'  => 32
-);
+$export_ok = "++GITWEB_EXPORT_OK++";
+$strict_export = "++GITWEB_STRICT_EXPORT++";
 
-# Used to set the maximum load that we will still respond to gitweb queries.
-# If server load exceed this value then return "503 server busy" error.
-# If gitweb cannot determined server load, it is taken to be 0.
-# Leave it undefined (or set to 'undef') to turn off load checking.
-our $maxload = 300;
-
-# You define site-wide feature defaults here; override them with
-# $GITWEB_CONFIG as necessary.
-our %feature = (
-	# feature => {
-	# 	'sub' => feature-sub (subroutine),
-	# 	'override' => allow-override (boolean),
-	# 	'default' => [ default options...] (array reference)}
-	#
-	# if feature is overridable (it means that allow-override has true value),
-	# then feature-sub will be called with default options as parameters;
-	# return value of feature-sub indicates if to enable specified feature
-	#
-	# if there is no 'sub' key (no feature-sub), then feature cannot be
-	# overriden
-	#
-	# use gitweb_get_feature(<feature>) to retrieve the <feature> value
-	# (an array) or gitweb_check_feature(<feature>) to check if <feature>
-	# is enabled
-
-	# Enable the 'blame' blob view, showing the last commit that modified
-	# each line in the file. This can be very CPU-intensive.
-
-	# To enable system wide have in $GITWEB_CONFIG
-	# $feature{'blame'}{'default'} = [1];
-	# To have project specific config enable override in $GITWEB_CONFIG
-	# $feature{'blame'}{'override'} = 1;
-	# and in project config gitweb.blame = 0|1;
-	'blame' => {
-		'sub' => sub { feature_bool('blame', @_) },
-		'override' => 0,
-		'default' => [0]},
-
-	# Enable the 'snapshot' link, providing a compressed archive of any
-	# tree. This can potentially generate high traffic if you have large
-	# project.
-
-	# Value is a list of formats defined in %known_snapshot_formats that
-	# you wish to offer.
-	# To disable system wide have in $GITWEB_CONFIG
-	# $feature{'snapshot'}{'default'} = [];
-	# To have project specific config enable override in $GITWEB_CONFIG
-	# $feature{'snapshot'}{'override'} = 1;
-	# and in project config, a comma-separated list of formats or "none"
-	# to disable.  Example: gitweb.snapshot = tbz2,zip;
-	'snapshot' => {
-		'sub' => \&feature_snapshot,
-		'override' => 0,
-		'default' => ['tgz']},
-
-	# Enable text search, which will list the commits which match author,
-	# committer or commit text to a given string.  Enabled by default.
-	# Project specific override is not supported.
-	'search' => {
-		'override' => 0,
-		'default' => [1]},
-
-	# Enable grep search, which will list the files in currently selected
-	# tree containing the given string. Enabled by default. This can be
-	# potentially CPU-intensive, of course.
-
-	# To enable system wide have in $GITWEB_CONFIG
-	# $feature{'grep'}{'default'} = [1];
-	# To have project specific config enable override in $GITWEB_CONFIG
-	# $feature{'grep'}{'override'} = 1;
-	# and in project config gitweb.grep = 0|1;
-	'grep' => {
-		'sub' => sub { feature_bool('grep', @_) },
-		'override' => 0,
-		'default' => [1]},
-
-	# Enable the pickaxe search, which will list the commits that modified
-	# a given string in a file. This can be practical and quite faster
-	# alternative to 'blame', but still potentially CPU-intensive.
-
-	# To enable system wide have in $GITWEB_CONFIG
-	# $feature{'pickaxe'}{'default'} = [1];
-	# To have project specific config enable override in $GITWEB_CONFIG
-	# $feature{'pickaxe'}{'override'} = 1;
-	# and in project config gitweb.pickaxe = 0|1;
-	'pickaxe' => {
-		'sub' => sub { feature_bool('pickaxe', @_) },
-		'override' => 0,
-		'default' => [1]},
-
-	# Enable showing size of blobs in a 'tree' view, in a separate
-	# column, similar to what 'ls -l' does.  This cost a bit of IO.
-
-	# To disable system wide have in $GITWEB_CONFIG
-	# $feature{'show-sizes'}{'default'} = [0];
-	# To have project specific config enable override in $GITWEB_CONFIG
-	# $feature{'show-sizes'}{'override'} = 1;
-	# and in project config gitweb.showsizes = 0|1;
-	'show-sizes' => {
-		'sub' => sub { feature_bool('showsizes', @_) },
-		'override' => 0,
-		'default' => [1]},
-
-	# Make gitweb use an alternative format of the URLs which can be
-	# more readable and natural-looking: project name is embedded
-	# directly in the path and the query string contains other
-	# auxiliary information. All gitweb installations recognize
-	# URL in either format; this configures in which formats gitweb
-	# generates links.
-
-	# To enable system wide have in $GITWEB_CONFIG
-	# $feature{'pathinfo'}{'default'} = [1];
-	# Project specific override is not supported.
-
-	# Note that you will need to change the default location of CSS,
-	# favicon, logo and possibly other files to an absolute URL. Also,
-	# if gitweb.cgi serves as your indexfile, you will need to force
-	# $my_uri to contain the script name in your $GITWEB_CONFIG.
-	'pathinfo' => {
-		'override' => 0,
-		'default' => [0]},
-
-	# Make gitweb consider projects in project root subdirectories
-	# to be forks of existing projects. Given project $projname.git,
-	# projects matching $projname/*.git will not be shown in the main
-	# projects list, instead a '+' mark will be added to $projname
-	# there and a 'forks' view will be enabled for the project, listing
-	# all the forks. If project list is taken from a file, forks have
-	# to be listed after the main project.
-
-	# To enable system wide have in $GITWEB_CONFIG
-	# $feature{'forks'}{'default'} = [1];
-	# Project specific override is not supported.
-	'forks' => {
-		'override' => 0,
-		'default' => [0]},
-
-	# Insert custom links to the action bar of all project pages.
-	# This enables you mainly to link to third-party scripts integrating
-	# into gitweb; e.g. git-browser for graphical history representation
-	# or custom web-based repository administration interface.
-
-	# The 'default' value consists of a list of triplets in the form
-	# (label, link, position) where position is the label after which
-	# to insert the link and link is a format string where %n expands
-	# to the project name, %f to the project path within the filesystem,
-	# %h to the current hash (h gitweb parameter) and %b to the current
-	# hash base (hb gitweb parameter); %% expands to %.
-
-	# To enable system wide have in $GITWEB_CONFIG e.g.
-	# $feature{'actions'}{'default'} = [('graphiclog',
-	# 	'/git-browser/by-commit.html?r=%n', 'summary')];
-	# Project specific override is not supported.
-	'actions' => {
-		'override' => 0,
-		'default' => []},
-
-	# Allow gitweb scan project content tags described in ctags/
-	# of project repository, and display the popular Web 2.0-ish
-	# "tag cloud" near the project list. Note that this is something
-	# COMPLETELY different from the normal Git tags.
-
-	# gitweb by itself can show existing tags, but it does not handle
-	# tagging itself; you need an external application for that.
-	# For an example script, check Girocco's cgi/tagproj.cgi.
-	# You may want to install the HTML::TagCloud Perl module to get
-	# a pretty tag cloud instead of just a list of tags.
-
-	# To enable system wide have in $GITWEB_CONFIG
-	# $feature{'ctags'}{'default'} = ['path_to_tag_script'];
-	# Project specific override is not supported.
-	'ctags' => {
-		'override' => 0,
-		'default' => [0]},
-
-	# The maximum number of patches in a patchset generated in patch
-	# view. Set this to 0 or undef to disable patch view, or to a
-	# negative number to remove any limit.
-
-	# To disable system wide have in $GITWEB_CONFIG
-	# $feature{'patches'}{'default'} = [0];
-	# To have project specific config enable override in $GITWEB_CONFIG
-	# $feature{'patches'}{'override'} = 1;
-	# and in project config gitweb.patches = 0|n;
-	# where n is the maximum number of patches allowed in a patchset.
-	'patches' => {
-		'sub' => \&feature_patches,
-		'override' => 0,
-		'default' => [16]},
-
-	# Avatar support. When this feature is enabled, views such as
-	# shortlog or commit will display an avatar associated with
-	# the email of the committer(s) and/or author(s).
-
-	# Currently available providers are gravatar and picon.
-	# If an unknown provider is specified, the feature is disabled.
-
-	# Gravatar depends on Digest::MD5.
-	# Picon currently relies on the indiana.edu database.
-
-	# To enable system wide have in $GITWEB_CONFIG
-	# $feature{'avatar'}{'default'} = ['<provider>'];
-	# where <provider> is either gravatar or picon.
-	# To have project specific config enable override in $GITWEB_CONFIG
-	# $feature{'avatar'}{'override'} = 1;
-	# and in project config gitweb.avatar = <provider>;
-	'avatar' => {
-		'sub' => \&feature_avatar,
-		'override' => 0,
-		'default' => ['']},
-
-	# Enable displaying how much time and how many git commands
-	# it took to generate and display page.  Disabled by default.
-	# Project specific override is not supported.
-	'timed' => {
-		'override' => 0,
-		'default' => [0]},
-
-	# Enable turning some links into links to actions which require
-	# JavaScript to run (like 'blame_incremental').  Not enabled by
-	# default.  Project specific override is currently not supported.
-	'javascript-actions' => {
-		'override' => 0,
-		'default' => [0]},
-
-	# Syntax highlighting support. This is based on Daniel Svensson's
-	# and Sham Chukoury's work in gitweb-xmms2.git.
-	# It requires the 'highlight' program present in $PATH,
-	# and therefore is disabled by default.
-
-	# To enable system wide have in $GITWEB_CONFIG
-	# $feature{'highlight'}{'default'} = [1];
-
-	'highlight' => {
-		'sub' => sub { feature_bool('highlight', @_) },
-		'override' => 0,
-		'default' => [0]},
-);
-
-sub gitweb_get_feature {
-	my ($name) = @_;
-	return unless exists $feature{$name};
-	my ($sub, $override, @defaults) = (
-		$feature{$name}{'sub'},
-		$feature{$name}{'override'},
-		@{$feature{$name}{'default'}});
-	# project specific override is possible only if we have project
-	if (!$override || !defined $git_dir) {
-		return @defaults;
-	}
-	if (!defined $sub) {
-		warn "feature $name is not overridable";
-		return @defaults;
-	}
-	return $sub->(@defaults);
-}
-
-# A wrapper to check if a given feature is enabled.
-# With this, you can say
-#
-#   my $bool_feat = gitweb_check_feature('bool_feat');
-#   gitweb_check_feature('bool_feat') or somecode;
-#
-# instead of
-#
-#   my ($bool_feat) = gitweb_get_feature('bool_feat');
-#   (gitweb_get_feature('bool_feat'))[0] or somecode;
-#
-sub gitweb_check_feature {
-	return (gitweb_get_feature(@_))[0];
-}
+@git_base_url_list = grep { $_ ne '' } ("++GITWEB_BASE_URL++");
 
+$GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
+$GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
 
 sub feature_bool {
 	my $key = shift;
@@ -566,33 +167,6 @@ sub check_export_ok {
 		(!$export_auth_hook || $export_auth_hook->($dir)));
 }
 
-# process alternate names for backward compatibility
-# filter out unsupported (unknown) snapshot formats
-sub filter_snapshot_fmts {
-	my @fmts = @_;
-
-	@fmts = map {
-		exists $known_snapshot_format_aliases{$_} ?
-		       $known_snapshot_format_aliases{$_} : $_} @fmts;
-	@fmts = grep {
-		exists $known_snapshot_formats{$_} &&
-		!$known_snapshot_formats{$_}{'disabled'}} @fmts;
-}
-
-our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM);
-sub evaluate_gitweb_config {
-	our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
-	our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
-	# die if there are errors parsing config file
-	if (-e $GITWEB_CONFIG) {
-		do $GITWEB_CONFIG;
-		die $@ if $@;
-	} elsif (-e $GITWEB_CONFIG_SYSTEM) {
-		do $GITWEB_CONFIG_SYSTEM;
-		die $@ if $@;
-	}
-}
-
 # Get loadavg of system, to compare against $maxload.
 # Currently it requires '/proc/loadavg' present to get loadavg;
 # if it is not present it returns 0, which means no load checking.
@@ -968,26 +542,6 @@ sub evaluate_git_dir {
 	$git_dir = "$projectroot/$project" if $project;
 }
 
-our (@snapshot_fmts, $git_avatar);
-sub configure_gitweb_features {
-	# list of supported snapshot formats
-	our @snapshot_fmts = gitweb_get_feature('snapshot');
-	@snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
-
-	# check that the avatar feature is set to a known provider name,
-	# and for each provider check if the dependencies are satisfied.
-	# if the provider name is invalid or the dependencies are not met,
-	# reset $git_avatar to the empty string.
-	our ($git_avatar) = gitweb_get_feature('avatar');
-	if ($git_avatar eq 'gravatar') {
-		$git_avatar = '' unless (eval { require Digest::MD5; 1; });
-	} elsif ($git_avatar eq 'picon') {
-		# no dependencies
-	} else {
-		$git_avatar = '';
-	}
-}
-
 # custom error handler: 'die <message>' is Internal Server Error
 sub handle_errors_html {
 	my $msg = shift; # it is already HTML escaped
@@ -2552,9 +2106,6 @@ sub git_get_projects_list {
 			follow_skip => 2, # ignore duplicates
 			dangling_symlinks => 0, # ignore dangling symlinks, silently
 			wanted => sub {
-				# global variables
-				our $project_maxdepth;
-				our $projectroot;
 				# skip project-list toplevel, if we get it.
 				return if (m!^[/.]$!);
 				# only directories can be git repositories
diff --git a/gitweb/lib/Gitweb/Config.pm b/gitweb/lib/Gitweb/Config.pm
new file mode 100644
index 0000000..c528595
--- /dev/null
+++ b/gitweb/lib/Gitweb/Config.pm
@@ -0,0 +1,498 @@
+#!/usr/bin/perl
+#
+# Gitweb::Config -- gitweb configuration package
+#
+# This program is licensed under the GPLv2
+
+package Gitweb::Config;
+
+use strict;
+use warnings;
+use Exporter qw(import);
+
+our @EXPORT = qw(evaluate_gitweb_config gitweb_check_feature gitweb_get_feature configure_gitweb_features
+                 filter_snapshot_fmts %known_snapshot_formats $version $projectroot $project_maxdepth
+                 $projects_list @git_base_url_list $export_ok $strict_export $home_link_str $site_name
+                 $site_header $site_footer $home_text @stylesheets $stylesheet $logo $favicon $javascript
+                 $GITWEB_CONFIG $GITWEB_CONFIG_SYSTEM $logo_url $logo_label $export_auth_hook
+                 $projects_list_description_width $default_projects_order $default_blob_plain_mimetype
+                 $default_text_plain_charset $fallback_encoding @diff_opts $prevent_xss $maxload
+                 $git_avatar %avatar_size %feature @snapshot_fmts $mimetypes_file);
+
+use Gitweb::Git qw($git_dir);
+
+# The following variables are affected by build-time configuration
+# and hence their initialisation is put in gitweb.perl script
+
+our $version;
+
+# absolute fs-path which will be prepended to the project path
+#our $projectroot = "/pub/scm";
+our $projectroot;
+# fs traversing limit for getting project list
+# the number is relative to the projectroot
+our $project_maxdepth;
+# source of projects list
+our $projects_list;
+# list of git base URLs used for URL to where fetch project from,
+# i.e. full URL is "$git_base_url/$project"
+our @git_base_url_list;
+
+# show repository only if this file exists
+# (only effective if this variable evaluates to true)
+our $export_ok;
+# only allow viewing of repositories also shown on the overview page
+our $strict_export;
+
+# string of the home link on top of all pages
+our $home_link_str;
+# name of your site or organization to appear in page titles
+# replace this with something more descriptive for clearer bookmarks
+our $site_name;
+# filename of html text to include at top of each page
+our $site_header;
+# filename of html text to include at bottom of each page
+our $site_footer;
+# html text to include at home page
+our $home_text;
+
+# URI of stylesheets
+our @stylesheets;
+# URI of a single stylesheet, which can be overridden in GITWEB_CONFIG.
+our $stylesheet;
+# URI of GIT logo (72x27 size)
+our $logo;
+# URI of GIT favicon, assumed to be image/png type
+our $favicon;
+# URI of gitweb.js (JavaScript code for gitweb)
+our $javascript;
+
+# gitweb config
+our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM);
+
+# URI and label (title) of GIT logo link
+#our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/";
+#our $logo_label = "git documentation";
+our $logo_url = "http://git-scm.com/";
+our $logo_label = "git homepage";
+
+# the width (in characters) of the projects list "Description" column
+our $projects_list_description_width = 25;
+
+# default order of projects list
+# valid values are none, project, descr, owner, and age
+our $default_projects_order = "project";
+
+# show repository only if this subroutine returns true
+# when given the path to the project, for example:
+#    sub { return -e "$_[0]/git-daemon-export-ok"; }
+our $export_auth_hook = undef;
+
+# default blob_plain mimetype and default charset for text/plain blob
+our $default_blob_plain_mimetype = 'text/plain';
+our $default_text_plain_charset  = undef;
+
+# file to use for guessing MIME types before trying /etc/mime.types
+# (relative to the current git repository)
+our $mimetypes_file = undef;
+
+# assume this charset if line contains non-UTF-8 characters;
+# it should be valid encoding (see Encoding::Supported(3pm) for list),
+# for which encoding all byte sequences are valid, for example
+# 'iso-8859-1' aka 'latin1' (it is decoded without checking, so it
+# could be even 'utf-8' for the old behavior)
+our $fallback_encoding = 'latin1';
+
+# rename detection options for git-diff and git-diff-tree
+# - default is '-M', with the cost proportional to
+#   (number of removed files) * (number of new files).
+# - more costly is '-C' (which implies '-M'), with the cost proportional to
+#   (number of changed files + number of removed files) * (number of new files)
+# - even more costly is '-C', '--find-copies-harder' with cost
+#   (number of files in the original tree) * (number of new files)
+# - one might want to include '-B' option, e.g. '-B', '-M'
+our @diff_opts = ('-M'); # taken from git_commit
+
+# Disables features that would allow repository owners to inject script into
+# the gitweb domain.
+our $prevent_xss = 0;
+
+# information about snapshot formats that gitweb is capable of serving
+our %known_snapshot_formats = (
+	# name => {
+	# 	'display' => display name,
+	# 	'type' => mime type,
+	# 	'suffix' => filename suffix,
+	# 	'format' => --format for git-archive,
+	# 	'compressor' => [compressor command and arguments]
+	# 	                (array reference, optional)
+	# 	'disabled' => boolean (optional)}
+	#
+	'tgz' => {
+		'display' => 'tar.gz',
+		'type' => 'application/x-gzip',
+		'suffix' => '.tar.gz',
+		'format' => 'tar',
+		'compressor' => ['gzip']},
+
+	'tbz2' => {
+		'display' => 'tar.bz2',
+		'type' => 'application/x-bzip2',
+		'suffix' => '.tar.bz2',
+		'format' => 'tar',
+		'compressor' => ['bzip2']},
+
+	'txz' => {
+		'display' => 'tar.xz',
+		'type' => 'application/x-xz',
+		'suffix' => '.tar.xz',
+		'format' => 'tar',
+		'compressor' => ['xz'],
+		'disabled' => 1},
+
+	'zip' => {
+		'display' => 'zip',
+		'type' => 'application/x-zip',
+		'suffix' => '.zip',
+		'format' => 'zip'},
+);
+
+# Aliases so we understand old gitweb.snapshot values in repository
+# configuration.
+our %known_snapshot_format_aliases = (
+	'gzip'  => 'tgz',
+	'bzip2' => 'tbz2',
+	'xz'    => 'txz',
+
+	# backward compatibility: legacy gitweb config support
+	'x-gzip' => undef, 'gz' => undef,
+	'x-bzip2' => undef, 'bz2' => undef,
+	'x-zip' => undef, '' => undef,
+);
+
+# Pixel sizes for icons and avatars. If the default font sizes or lineheights
+# are changed, it may be appropriate to change these values too via
+# $GITWEB_CONFIG.
+our %avatar_size = (
+	'default' => 16,
+	'double'  => 32
+);
+
+# Used to set the maximum load that we will still respond to gitweb queries.
+# If server load exceed this value then return "503 server busy" error.
+# If gitweb cannot determined server load, it is taken to be 0.
+# Leave it undefined (or set to 'undef') to turn off load checking.
+our $maxload = 300;
+
+# You define site-wide feature defaults here; override them with
+# $GITWEB_CONFIG as necessary.
+our %feature = (
+	# feature => {
+	# 	'sub' => feature-sub (subroutine),
+	# 	'override' => allow-override (boolean),
+	# 	'default' => [ default options...] (array reference)}
+	#
+	# if feature is overridable (it means that allow-override has true value),
+	# then feature-sub will be called with default options as parameters;
+	# return value of feature-sub indicates if to enable specified feature
+	#
+	# if there is no 'sub' key (no feature-sub), then feature cannot be
+	# overriden
+	#
+	# use gitweb_get_feature(<feature>) to retrieve the <feature> value
+	# (an array) or gitweb_check_feature(<feature>) to check if <feature>
+	# is enabled
+
+	# Enable the 'blame' blob view, showing the last commit that modified
+	# each line in the file. This can be very CPU-intensive.
+
+	# To enable system wide have in $GITWEB_CONFIG
+	# $feature{'blame'}{'default'} = [1];
+	# To have project specific config enable override in $GITWEB_CONFIG
+	# $feature{'blame'}{'override'} = 1;
+	# and in project config gitweb.blame = 0|1;
+	'blame' => {
+		'sub' => sub { feature_bool('blame', @_) },
+		'override' => 0,
+		'default' => [0]},
+
+	# Enable the 'snapshot' link, providing a compressed archive of any
+	# tree. This can potentially generate high traffic if you have large
+	# project.
+
+	# Value is a list of formats defined in %known_snapshot_formats that
+	# you wish to offer.
+	# To disable system wide have in $GITWEB_CONFIG
+	# $feature{'snapshot'}{'default'} = [];
+	# To have project specific config enable override in $GITWEB_CONFIG
+	# $feature{'snapshot'}{'override'} = 1;
+	# and in project config, a comma-separated list of formats or "none"
+	# to disable.  Example: gitweb.snapshot = tbz2,zip;
+	'snapshot' => {
+		'sub' => \&feature_snapshot,
+		'override' => 0,
+		'default' => ['tgz']},
+
+	# Enable text search, which will list the commits which match author,
+	# committer or commit text to a given string.  Enabled by default.
+	# Project specific override is not supported.
+	'search' => {
+		'override' => 0,
+		'default' => [1]},
+
+	# Enable grep search, which will list the files in currently selected
+	# tree containing the given string. Enabled by default. This can be
+	# potentially CPU-intensive, of course.
+
+	# To enable system wide have in $GITWEB_CONFIG
+	# $feature{'grep'}{'default'} = [1];
+	# To have project specific config enable override in $GITWEB_CONFIG
+	# $feature{'grep'}{'override'} = 1;
+	# and in project config gitweb.grep = 0|1;
+	'grep' => {
+		'sub' => sub { feature_bool('grep', @_) },
+		'override' => 0,
+		'default' => [1]},
+
+	# Enable the pickaxe search, which will list the commits that modified
+	# a given string in a file. This can be practical and quite faster
+	# alternative to 'blame', but still potentially CPU-intensive.
+
+	# To enable system wide have in $GITWEB_CONFIG
+	# $feature{'pickaxe'}{'default'} = [1];
+	# To have project specific config enable override in $GITWEB_CONFIG
+	# $feature{'pickaxe'}{'override'} = 1;
+	# and in project config gitweb.pickaxe = 0|1;
+	'pickaxe' => {
+		'sub' => sub { feature_bool('pickaxe', @_) },
+		'override' => 0,
+		'default' => [1]},
+
+	# Enable showing size of blobs in a 'tree' view, in a separate
+	# column, similar to what 'ls -l' does.  This cost a bit of IO.
+
+	# To disable system wide have in $GITWEB_CONFIG
+	# $feature{'show-sizes'}{'default'} = [0];
+	# To have project specific config enable override in $GITWEB_CONFIG
+	# $feature{'show-sizes'}{'override'} = 1;
+	# and in project config gitweb.showsizes = 0|1;
+	'show-sizes' => {
+		'sub' => sub { feature_bool('showsizes', @_) },
+		'override' => 0,
+		'default' => [1]},
+
+	# Make gitweb use an alternative format of the URLs which can be
+	# more readable and natural-looking: project name is embedded
+	# directly in the path and the query string contains other
+	# auxiliary information. All gitweb installations recognize
+	# URL in either format; this configures in which formats gitweb
+	# generates links.
+
+	# To enable system wide have in $GITWEB_CONFIG
+	# $feature{'pathinfo'}{'default'} = [1];
+	# Project specific override is not supported.
+
+	# Note that you will need to change the default location of CSS,
+	# favicon, logo and possibly other files to an absolute URL. Also,
+	# if gitweb.cgi serves as your indexfile, you will need to force
+	# $my_uri to contain the script name in your $GITWEB_CONFIG.
+	'pathinfo' => {
+		'override' => 0,
+		'default' => [0]},
+
+	# Make gitweb consider projects in project root subdirectories
+	# to be forks of existing projects. Given project $projname.git,
+	# projects matching $projname/*.git will not be shown in the main
+	# projects list, instead a '+' mark will be added to $projname
+	# there and a 'forks' view will be enabled for the project, listing
+	# all the forks. If project list is taken from a file, forks have
+	# to be listed after the main project.
+
+	# To enable system wide have in $GITWEB_CONFIG
+	# $feature{'forks'}{'default'} = [1];
+	# Project specific override is not supported.
+	'forks' => {
+		'override' => 0,
+		'default' => [0]},
+
+	# Insert custom links to the action bar of all project pages.
+	# This enables you mainly to link to third-party scripts integrating
+	# into gitweb; e.g. git-browser for graphical history representation
+	# or custom web-based repository administration interface.
+
+	# The 'default' value consists of a list of triplets in the form
+	# (label, link, position) where position is the label after which
+	# to insert the link and link is a format string where %n expands
+	# to the project name, %f to the project path within the filesystem,
+	# %h to the current hash (h gitweb parameter) and %b to the current
+	# hash base (hb gitweb parameter); %% expands to %.
+
+	# To enable system wide have in $GITWEB_CONFIG e.g.
+	# $feature{'actions'}{'default'} = [('graphiclog',
+	# 	'/git-browser/by-commit.html?r=%n', 'summary')];
+	# Project specific override is not supported.
+	'actions' => {
+		'override' => 0,
+		'default' => []},
+
+	# Allow gitweb scan project content tags described in ctags/
+	# of project repository, and display the popular Web 2.0-ish
+	# "tag cloud" near the project list. Note that this is something
+	# COMPLETELY different from the normal Git tags.
+
+	# gitweb by itself can show existing tags, but it does not handle
+	# tagging itself; you need an external application for that.
+	# For an example script, check Girocco's cgi/tagproj.cgi.
+	# You may want to install the HTML::TagCloud Perl module to get
+	# a pretty tag cloud instead of just a list of tags.
+
+	# To enable system wide have in $GITWEB_CONFIG
+	# $feature{'ctags'}{'default'} = ['path_to_tag_script'];
+	# Project specific override is not supported.
+	'ctags' => {
+		'override' => 0,
+		'default' => [0]},
+
+	# The maximum number of patches in a patchset generated in patch
+	# view. Set this to 0 or undef to disable patch view, or to a
+	# negative number to remove any limit.
+
+	# To disable system wide have in $GITWEB_CONFIG
+	# $feature{'patches'}{'default'} = [0];
+	# To have project specific config enable override in $GITWEB_CONFIG
+	# $feature{'patches'}{'override'} = 1;
+	# and in project config gitweb.patches = 0|n;
+	# where n is the maximum number of patches allowed in a patchset.
+	'patches' => {
+		'sub' => \&feature_patches,
+		'override' => 0,
+		'default' => [16]},
+
+	# Avatar support. When this feature is enabled, views such as
+	# shortlog or commit will display an avatar associated with
+	# the email of the committer(s) and/or author(s).
+
+	# Currently available providers are gravatar and picon.
+	# If an unknown provider is specified, the feature is disabled.
+
+	# Gravatar depends on Digest::MD5.
+	# Picon currently relies on the indiana.edu database.
+
+	# To enable system wide have in $GITWEB_CONFIG
+	# $feature{'avatar'}{'default'} = ['<provider>'];
+	# where <provider> is either gravatar or picon.
+	# To have project specific config enable override in $GITWEB_CONFIG
+	# $feature{'avatar'}{'override'} = 1;
+	# and in project config gitweb.avatar = <provider>;
+	'avatar' => {
+		'sub' => \&feature_avatar,
+		'override' => 0,
+		'default' => ['']},
+
+	# Enable displaying how much time and how many git commands
+	# it took to generate and display page.  Disabled by default.
+	# Project specific override is not supported.
+	'timed' => {
+		'override' => 0,
+		'default' => [0]},
+
+	# Enable turning some links into links to actions which require
+	# JavaScript to run (like 'blame_incremental').  Not enabled by
+	# default.  Project specific override is currently not supported.
+	'javascript-actions' => {
+		'override' => 0,
+		'default' => [0]},
+
+	# Syntax highlighting support. This is based on Daniel Svensson's
+	# and Sham Chukoury's work in gitweb-xmms2.git.
+	# It requires the 'highlight' program present in $PATH,
+	# and therefore is disabled by default.
+
+	# To enable system wide have in $GITWEB_CONFIG
+	# $feature{'highlight'}{'default'} = [1];
+
+	'highlight' => {
+		'sub' => sub { feature_bool('highlight', @_) },
+		'override' => 0,
+		'default' => [0]},
+);
+
+sub evaluate_gitweb_config {
+	# die if there are errors parsing config file
+	if (-e $GITWEB_CONFIG) {
+		do $GITWEB_CONFIG;
+		die $@ if $@;
+	} elsif (-e $GITWEB_CONFIG_SYSTEM) {
+		do $GITWEB_CONFIG_SYSTEM;
+		die $@ if $@;
+	}
+}
+
+
+sub gitweb_get_feature {
+	my ($name) = @_;
+	return unless exists $feature{$name};
+	my ($sub, $override, @defaults) = (
+		$feature{$name}{'sub'},
+		$feature{$name}{'override'},
+		@{$feature{$name}{'default'}});
+	# project specific override is possible only if we have project
+	if (!$override || !defined $git_dir) {
+		return @defaults;
+	}
+	if (!defined $sub) {
+		warn "feature $name is not overridable";
+		return @defaults;
+	}
+	return $sub->(@defaults);
+}
+
+# A wrapper to check if a given feature is enabled.
+# With this, you can say
+#
+#   my $bool_feat = gitweb_check_feature('bool_feat');
+#   gitweb_check_feature('bool_feat') or somecode;
+#
+# instead of
+#
+#   my ($bool_feat) = gitweb_get_feature('bool_feat');
+#   (gitweb_get_feature('bool_feat'))[0] or somecode;
+#
+sub gitweb_check_feature {
+	return (gitweb_get_feature(@_))[0];
+}
+
+# process alternate names for backward compatibility
+# filter out unsupported (unknown) snapshot formats
+sub filter_snapshot_fmts {
+	my @fmts = @_;
+
+	@fmts = map {
+		exists $known_snapshot_format_aliases{$_} ?
+		       $known_snapshot_format_aliases{$_} : $_} @fmts;
+	@fmts = grep {
+		exists $known_snapshot_formats{$_} &&
+		!$known_snapshot_formats{$_}{'disabled'}} @fmts;
+}
+
+our (@snapshot_fmts, $git_avatar);
+sub configure_gitweb_features {
+	# list of supported snapshot formats
+	our @snapshot_fmts = gitweb_get_feature('snapshot');
+	@snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
+
+	# check that the avatar feature is set to a known provider name,
+	# and for each provider check if the dependencies are satisfied.
+	# if the provider name is invalid or the dependencies are not met,
+	# reset $git_avatar to the empty string.
+	our ($git_avatar) = gitweb_get_feature('avatar');
+	if ($git_avatar eq 'gravatar') {
+		$git_avatar = '' unless (eval { require Digest::MD5; 1; });
+	} elsif ($git_avatar eq 'picon') {
+		# no dependencies
+	} else {
+		$git_avatar = '';
+	}
+}
+
+1;
-- 
1.7.1.455.g8f441

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCHv2 GSOC 05/11] gitweb: Create Gitweb::Request module
  2010-07-15  7:29 [PATCHv2 00/11] Splitting gitweb Pavan Kumar Sunkara
                   ` (3 preceding siblings ...)
  2010-07-15  7:29 ` [PATCHv2 GSOC 04/11] gitweb: Create Gitweb::Config module Pavan Kumar Sunkara
@ 2010-07-15  7:29 ` Pavan Kumar Sunkara
  2010-07-16  0:11   ` Jakub Narebski
  2010-07-15  7:29 ` [PATCHv2 GSOC 06/11] gitweb: Create Gitweb::Escape module Pavan Kumar Sunkara
                   ` (6 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: Pavan Kumar Sunkara @ 2010-07-15  7:29 UTC (permalink / raw)
  To: git, jnareb, chriscool, pasky; +Cc: Pavan Kumar Sunkara

Create a Gitweb::Request module in 'gitweb/lib/Gitweb/Request.pm'
to store and handle all the cgi params and related variables
regarding the gitweb.perl script.

This module is intended as standalone module, which does not require
(include) other gitweb' modules to avoid circular dependencies.

Subroutines moved:
	evaluate_uri
	evaluate_query_params

Update gitweb/Makefile to install Gitweb::Request module alongside gitweb

Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
---
 gitweb/Makefile              |    1 +
 gitweb/gitweb.perl           |  130 +++++-------------------------------------
 gitweb/lib/Gitweb/Request.pm |  119 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 134 insertions(+), 116 deletions(-)
 create mode 100644 gitweb/lib/Gitweb/Request.pm

diff --git a/gitweb/Makefile b/gitweb/Makefile
index 492b0fa..cd36dc6 100644
--- a/gitweb/Makefile
+++ b/gitweb/Makefile
@@ -114,6 +114,7 @@ GITWEB_FILES += static/git-logo.png static/git-favicon.png
 # Modules: Gitweb::*
 GITWEB_MODULES += lib/Gitweb/Git.pm
 GITWEB_MODULES += lib/Gitweb/Config.pm
+GITWEB_MODULES += lib/Gitweb/Request.pm
 
 GITWEB_REPLACE = \
 	-e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index b68cc71..90cb6eb 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -29,11 +29,7 @@ binmode STDOUT, ':utf8';
 
 use Gitweb::Git;
 use Gitweb::Config;
-
-our $t0;
-if (eval { require Time::HiRes; 1; }) {
-	$t0 = [Time::HiRes::gettimeofday()];
-}
+use Gitweb::Request;
 
 BEGIN {
 	CGI->compile() if $ENV{'MOD_PERL'};
@@ -44,42 +40,6 @@ BEGIN {
 # with their descriptions is listed in Gitweb::Config.
 $version = "++GIT_VERSION++";
 
-our ($my_url, $my_uri, $base_url, $path_info, $home_link);
-sub evaluate_uri {
-	our $cgi;
-
-	our $my_url = $cgi->url();
-	our $my_uri = $cgi->url(-absolute => 1);
-
-	# Base URL for relative URLs in gitweb ($logo, $favicon, ...),
-	# needed and used only for URLs with nonempty PATH_INFO
-	our $base_url = $my_url;
-
-	# When the script is used as DirectoryIndex, the URL does not contain the name
-	# of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
-	# have to do it ourselves. We make $path_info global because it's also used
-	# later on.
-	#
-	# Another issue with the script being the DirectoryIndex is that the resulting
-	# $my_url data is not the full script URL: this is good, because we want
-	# generated links to keep implying the script name if it wasn't explicitly
-	# indicated in the URL we're handling, but it means that $my_url cannot be used
-	# as base URL.
-	# Therefore, if we needed to strip PATH_INFO, then we know that we have
-	# to build the base URL ourselves:
-	our $path_info = $ENV{"PATH_INFO"};
-	if ($path_info) {
-		if ($my_url =~ s,\Q$path_info\E$,, &&
-		    $my_uri =~ s,\Q$path_info\E$,, &&
-		    defined $ENV{'SCRIPT_NAME'}) {
-			$base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
-		}
-	}
-
-	# target of the home link on top of all pages
-	our $home_link = $my_uri || "/";
-}
-
 # $GIT is from Gitweb::Git
 $GIT = "++GIT_BINDIR++/git";
 
@@ -198,42 +158,6 @@ sub check_loadavg {
 # ======================================================================
 # input validation and dispatch
 
-# input parameters can be collected from a variety of sources (presently, CGI
-# and PATH_INFO), so we define an %input_params hash that collects them all
-# together during validation: this allows subsequent uses (e.g. href()) to be
-# agnostic of the parameter origin
-
-our %input_params = ();
-
-# input parameters are stored with the long parameter name as key. This will
-# also be used in the href subroutine to convert parameters to their CGI
-# equivalent, and since the href() usage is the most frequent one, we store
-# the name -> CGI key mapping here, instead of the reverse.
-#
-# XXX: Warning: If you touch this, check the search form for updating,
-# too.
-
-our @cgi_param_mapping = (
-	project => "p",
-	action => "a",
-	file_name => "f",
-	file_parent => "fp",
-	hash => "h",
-	hash_parent => "hp",
-	hash_base => "hb",
-	hash_parent_base => "hpb",
-	page => "pg",
-	order => "o",
-	searchtext => "s",
-	searchtype => "st",
-	snapshot_format => "sf",
-	extra_options => "opt",
-	search_use_regexp => "sr",
-	# this must be last entry (for manipulation from JavaScript)
-	javascript => "js"
-);
-our %cgi_param_mapping = @cgi_param_mapping;
-
 # we will also need to know the possible actions, for validation
 our %actions = (
 	"blame" => \&git_blame,
@@ -269,27 +193,6 @@ our %actions = (
 	"project_index" => \&git_project_index,
 );
 
-# finally, we have the hash of allowed extra_options for the commands that
-# allow them
-our %allowed_options = (
-	"--no-merges" => [ qw(rss atom log shortlog history) ],
-);
-
-# fill %input_params with the CGI parameters. All values except for 'opt'
-# should be single values, but opt can be an array. We should probably
-# build an array of parameters that can be multi-valued, but since for the time
-# being it's only this one, we just single it out
-sub evaluate_query_params {
-	our $cgi;
-
-	while (my ($name, $symbol) = each %cgi_param_mapping) {
-		if ($symbol eq 'opt') {
-			$input_params{$name} = [ $cgi->param($symbol) ];
-		} else {
-			$input_params{$name} = $cgi->param($symbol);
-		}
-	}
-}
 
 # now read PATH_INFO and update the parameter list for missing parameters
 sub evaluate_path_info {
@@ -435,11 +338,8 @@ sub evaluate_path_info {
 	}
 }
 
-our ($action, $project, $file_name, $file_parent, $hash, $hash_parent, $hash_base,
-     $hash_parent_base, @extra_options, $page, $searchtype, $search_use_regexp,
-     $searchtext, $search_regexp);
 sub evaluate_and_validate_params {
-	our $action = $input_params{'action'};
+	$action = $input_params{'action'};
 	if (defined $action) {
 		if (!validate_action($action)) {
 			die_error(400, "Invalid action parameter");
@@ -447,7 +347,7 @@ sub evaluate_and_validate_params {
 	}
 
 	# parameters which are pathnames
-	our $project = $input_params{'project'};
+	$project = $input_params{'project'};
 	if (defined $project) {
 		if (!validate_project($project)) {
 			undef $project;
@@ -455,14 +355,14 @@ sub evaluate_and_validate_params {
 		}
 	}
 
-	our $file_name = $input_params{'file_name'};
+	$file_name = $input_params{'file_name'};
 	if (defined $file_name) {
 		if (!validate_pathname($file_name)) {
 			die_error(400, "Invalid file parameter");
 		}
 	}
 
-	our $file_parent = $input_params{'file_parent'};
+	$file_parent = $input_params{'file_parent'};
 	if (defined $file_parent) {
 		if (!validate_pathname($file_parent)) {
 			die_error(400, "Invalid file parent parameter");
@@ -470,28 +370,28 @@ sub evaluate_and_validate_params {
 	}
 
 	# parameters which are refnames
-	our $hash = $input_params{'hash'};
+	$hash = $input_params{'hash'};
 	if (defined $hash) {
 		if (!validate_refname($hash)) {
 			die_error(400, "Invalid hash parameter");
 		}
 	}
 
-	our $hash_parent = $input_params{'hash_parent'};
+	$hash_parent = $input_params{'hash_parent'};
 	if (defined $hash_parent) {
 		if (!validate_refname($hash_parent)) {
 			die_error(400, "Invalid hash parent parameter");
 		}
 	}
 
-	our $hash_base = $input_params{'hash_base'};
+	$hash_base = $input_params{'hash_base'};
 	if (defined $hash_base) {
 		if (!validate_refname($hash_base)) {
 			die_error(400, "Invalid hash base parameter");
 		}
 	}
 
-	our @extra_options = @{$input_params{'extra_options'}};
+	@extra_options = @{$input_params{'extra_options'}};
 	# @extra_options is always defined, since it can only be (currently) set from
 	# CGI, and $cgi->param() returns the empty array in array context if the param
 	# is not set
@@ -504,7 +404,7 @@ sub evaluate_and_validate_params {
 		}
 	}
 
-	our $hash_parent_base = $input_params{'hash_parent_base'};
+	$hash_parent_base = $input_params{'hash_parent_base'};
 	if (defined $hash_parent_base) {
 		if (!validate_refname($hash_parent_base)) {
 			die_error(400, "Invalid hash parent base parameter");
@@ -512,24 +412,23 @@ sub evaluate_and_validate_params {
 	}
 
 	# other parameters
-	our $page = $input_params{'page'};
+	$page = $input_params{'page'};
 	if (defined $page) {
 		if ($page =~ m/[^0-9]/) {
 			die_error(400, "Invalid page parameter");
 		}
 	}
 
-	our $searchtype = $input_params{'searchtype'};
+	$searchtype = $input_params{'searchtype'};
 	if (defined $searchtype) {
 		if ($searchtype =~ m/[^a-z]/) {
 			die_error(400, "Invalid searchtype parameter");
 		}
 	}
 
-	our $search_use_regexp = $input_params{'search_use_regexp'};
+	$search_use_regexp = $input_params{'search_use_regexp'};
 
-	our $searchtext = $input_params{'searchtext'};
-	our $search_regexp;
+	$searchtext = $input_params{'searchtext'};
 	if (defined $searchtext) {
 		if (length($searchtext) < 2) {
 			die_error(403, "At least two characters are required for search parameter");
@@ -605,7 +504,6 @@ sub run_request {
 our $is_last_request = sub { 1 };
 our ($pre_dispatch_hook, $post_dispatch_hook, $pre_listen_hook);
 our $CGI = 'CGI';
-our $cgi;
 sub configure_as_fcgi {
 	require CGI::Fast;
 	our $CGI = 'CGI::Fast';
diff --git a/gitweb/lib/Gitweb/Request.pm b/gitweb/lib/Gitweb/Request.pm
new file mode 100644
index 0000000..1581a94
--- /dev/null
+++ b/gitweb/lib/Gitweb/Request.pm
@@ -0,0 +1,119 @@
+#!/usr/bin/perl
+#
+# Gitweb::Request -- gitweb request(cgi) package
+#
+# This program is licensed under the GPLv2
+
+package Gitweb::Request;
+
+use strict;
+use warnings;
+use Exporter qw(import);
+
+our @EXPORT = qw($cgi $my_url $my_uri $base_url $path_info $home_link $action $project $file_name
+                 $file_parent $hash $hash_parent $hash_base $hash_parent_base @extra_options $page
+                 $searchtype $search_use_regexp $searchtext $search_regexp %input_params %allowed_options
+                 @cgi_param_mapping %cgi_param_mapping $t0 evaluate_query_params evaluate_uri);
+
+our $t0;
+if (eval { require Time::HiRes; 1; }) {
+	$t0 = [Time::HiRes::gettimeofday()];
+}
+
+our ($cgi, $my_url, $my_uri, $base_url, $path_info, $home_link);
+our ($action, $project, $file_name, $file_parent, $hash, $hash_parent, $hash_base,
+     $hash_parent_base, @extra_options, $page);
+our ($searchtype, $search_use_regexp, $searchtext, $search_regexp);
+
+# input parameters can be collected from a variety of sources (presently, CGI
+# and PATH_INFO), so we define an %input_params hash that collects them all
+# together during validation: this allows subsequent uses (e.g. href()) to be
+# agnostic of the parameter origin
+
+our %input_params = ();
+
+# input parameters are stored with the long parameter name as key. This will
+# also be used in the href subroutine to convert parameters to their CGI
+# equivalent, and since the href() usage is the most frequent one, we store
+# the name -> CGI key mapping here, instead of the reverse.
+#
+# XXX: Warning: If you touch this, check the search form for updating,
+# too.
+
+our @cgi_param_mapping = (
+	project => "p",
+	action => "a",
+	file_name => "f",
+	file_parent => "fp",
+	hash => "h",
+	hash_parent => "hp",
+	hash_base => "hb",
+	hash_parent_base => "hpb",
+	page => "pg",
+	order => "o",
+	searchtext => "s",
+	searchtype => "st",
+	snapshot_format => "sf",
+	extra_options => "opt",
+	search_use_regexp => "sr",
+	# this must be last entry (for manipulation from JavaScript)
+	javascript => "js"
+);
+our %cgi_param_mapping = @cgi_param_mapping;
+
+# finally, we have the hash of allowed extra_options for the commands that
+# allow them
+our %allowed_options = (
+	"--no-merges" => [ qw(rss atom log shortlog history) ],
+);
+
+# fill %input_params with the CGI parameters. All values except for 'opt'
+# should be single values, but opt can be an array. We should probably
+# build an array of parameters that can be multi-valued, but since for the time
+# being it's only this one, we just single it out
+sub evaluate_query_params {
+	while (my ($name, $symbol) = each %cgi_param_mapping) {
+		if ($symbol eq 'opt') {
+			$input_params{$name} = [ $cgi->param($symbol) ];
+		} else {
+			$input_params{$name} = $cgi->param($symbol);
+		}
+	}
+}
+
+sub evaluate_uri {
+	our $cgi;
+
+	our $my_url = $cgi->url();
+	our $my_uri = $cgi->url(-absolute => 1);
+
+	# Base URL for relative URLs in gitweb ($Gitweb::Config::logo, $Gitweb::Config::favicon, ...),
+	# needed and used only for URLs with nonempty PATH_INFO
+	our $base_url = $my_url;
+
+	# When the script is used as DirectoryIndex, the URL does not contain the name
+	# of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
+	# have to do it ourselves. We make $path_info global because it's also used
+	# later on.
+	#
+	# Another issue with the script being the DirectoryIndex is that the resulting
+	# $my_url data is not the full script URL: this is good, because we want
+	# generated links to keep implying the script name if it wasn't explicitly
+	# indicated in the URL we're handling, but it means that $my_url cannot be used
+	# as base URL.
+	# Therefore, if we needed to strip PATH_INFO, then we know that we have
+	# to build the base URL ourselves:
+	our $path_info = $ENV{"PATH_INFO"};
+	if ($path_info) {
+		if ($my_url =~ s,\Q$path_info\E$,, &&
+		    $my_uri =~ s,\Q$path_info\E$,, &&
+		    defined $ENV{'SCRIPT_NAME'}) {
+			$base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
+		}
+	}
+
+	# target of the home link on top of all pages
+	our $home_link = $my_uri || "/";
+}
+
+1;
-- 
1.7.1.455.g8f441

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCHv2 GSOC 06/11] gitweb: Create Gitweb::Escape module
  2010-07-15  7:29 [PATCHv2 00/11] Splitting gitweb Pavan Kumar Sunkara
                   ` (4 preceding siblings ...)
  2010-07-15  7:29 ` [PATCHv2 GSOC 05/11] gitweb: Create Gitweb::Request module Pavan Kumar Sunkara
@ 2010-07-15  7:29 ` Pavan Kumar Sunkara
  2010-07-16  9:01   ` Jakub Narebski
  2010-07-15  7:29 ` [PATCHv2 GSOC 07/11] gitweb: Create Gitweb::RepoConfig module Pavan Kumar Sunkara
                   ` (5 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: Pavan Kumar Sunkara @ 2010-07-15  7:29 UTC (permalink / raw)
  To: git, jnareb, chriscool, pasky; +Cc: Pavan Kumar Sunkara

Create a Gitweb::Escape module in 'gitweb/lib/Gitweb/Escape.pm'
to store all the quoting/unquoting and escaping subroutines
regarding the gitweb.perl script.

This module imports $fallback_encoding variable from
Gitweb::Config module to use it in sub 'to_utf8'

Subroutines moved:
	to_utf8
	esc_param
	esc_url
	esc_html
	esc_path
	quot_cec
	quot_upr
	untabify

Update gitweb/Makefile to install Gitweb::Escape module alongside gitweb

Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
---
 gitweb/Makefile             |    1 +
 gitweb/gitweb.perl          |  120 +-------------------------------------
 gitweb/lib/Gitweb/Escape.pm |  138 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 140 insertions(+), 119 deletions(-)
 create mode 100644 gitweb/lib/Gitweb/Escape.pm

diff --git a/gitweb/Makefile b/gitweb/Makefile
index cd36dc6..c145ebd 100644
--- a/gitweb/Makefile
+++ b/gitweb/Makefile
@@ -115,6 +115,7 @@ GITWEB_FILES += static/git-logo.png static/git-favicon.png
 GITWEB_MODULES += lib/Gitweb/Git.pm
 GITWEB_MODULES += lib/Gitweb/Config.pm
 GITWEB_MODULES += lib/Gitweb/Request.pm
+GITWEB_MODULES += lib/Gitweb/Escape.pm
 
 GITWEB_REPLACE = \
 	-e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 90cb6eb..ed64910 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -20,7 +20,6 @@ use lib __DIR__ . '/lib';
 use CGI qw(:standard :escapeHTML -nosticky);
 use CGI::Util qw(unescape);
 use CGI::Carp qw(fatalsToBrowser set_message);
-use Encode;
 use Fcntl ':mode';
 use File::Find qw();
 use File::Basename qw(basename);
@@ -30,6 +29,7 @@ binmode STDOUT, ':utf8';
 use Gitweb::Git;
 use Gitweb::Config;
 use Gitweb::Request;
+use Gitweb::Escape;
 
 BEGIN {
 	CGI->compile() if $ENV{'MOD_PERL'};
@@ -745,110 +745,6 @@ sub validate_refname {
 	return $input;
 }
 
-# decode sequences of octets in utf8 into Perl's internal form,
-# which is utf-8 with utf8 flag set if needed.  gitweb writes out
-# in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning
-sub to_utf8 {
-	my $str = shift;
-	return undef unless defined $str;
-	if (utf8::valid($str)) {
-		utf8::decode($str);
-		return $str;
-	} else {
-		return decode($fallback_encoding, $str, Encode::FB_DEFAULT);
-	}
-}
-
-# quote unsafe chars, but keep the slash, even when it's not
-# correct, but quoted slashes look too horrible in bookmarks
-sub esc_param {
-	my $str = shift;
-	return undef unless defined $str;
-	$str =~ s/([^A-Za-z0-9\-_.~()\/:@ ]+)/CGI::escape($1)/eg;
-	$str =~ s/ /\+/g;
-	return $str;
-}
-
-# quote unsafe chars in whole URL, so some charactrs cannot be quoted
-sub esc_url {
-	my $str = shift;
-	return undef unless defined $str;
-	$str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&= ]+)/CGI::escape($1)/eg;
-	$str =~ s/ /\+/g;
-	return $str;
-}
-
-# replace invalid utf8 character with SUBSTITUTION sequence
-sub esc_html {
-	my $str = shift;
-	my %opts = @_;
-
-	return undef unless defined $str;
-
-	$str = to_utf8($str);
-	$str = $cgi->escapeHTML($str);
-	if ($opts{'-nbsp'}) {
-		$str =~ s/ /&nbsp;/g;
-	}
-	$str =~ s|([[:cntrl:]])|(($1 ne "\t") ? quot_cec($1) : $1)|eg;
-	return $str;
-}
-
-# quote control characters and escape filename to HTML
-sub esc_path {
-	my $str = shift;
-	my %opts = @_;
-
-	return undef unless defined $str;
-
-	$str = to_utf8($str);
-	$str = $cgi->escapeHTML($str);
-	if ($opts{'-nbsp'}) {
-		$str =~ s/ /&nbsp;/g;
-	}
-	$str =~ s|([[:cntrl:]])|quot_cec($1)|eg;
-	return $str;
-}
-
-# Make control characters "printable", using character escape codes (CEC)
-sub quot_cec {
-	my $cntrl = shift;
-	my %opts = @_;
-	my %es = ( # character escape codes, aka escape sequences
-		"\t" => '\t',   # tab            (HT)
-		"\n" => '\n',   # line feed      (LF)
-		"\r" => '\r',   # carrige return (CR)
-		"\f" => '\f',   # form feed      (FF)
-		"\b" => '\b',   # backspace      (BS)
-		"\a" => '\a',   # alarm (bell)   (BEL)
-		"\e" => '\e',   # escape         (ESC)
-		"\013" => '\v', # vertical tab   (VT)
-		"\000" => '\0', # nul character  (NUL)
-	);
-	my $chr = ( (exists $es{$cntrl})
-		    ? $es{$cntrl}
-		    : sprintf('\%2x', ord($cntrl)) );
-	if ($opts{-nohtml}) {
-		return $chr;
-	} else {
-		return "<span class=\"cntrl\">$chr</span>";
-	}
-}
-
-# Alternatively use unicode control pictures codepoints,
-# Unicode "printable representation" (PR)
-sub quot_upr {
-	my $cntrl = shift;
-	my %opts = @_;
-
-	my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
-	if ($opts{-nohtml}) {
-		return $chr;
-	} else {
-		return "<span class=\"cntrl\">$chr</span>";
-	}
-}
-
 # git may return quoted and escaped filenames
 sub unquote {
 	my $str = shift;
@@ -885,20 +781,6 @@ sub unquote {
 	return $str;
 }
 
-# escape tabs (convert tabs to spaces)
-sub untabify {
-	my $line = shift;
-
-	while ((my $pos = index($line, "\t")) != -1) {
-		if (my $count = (8 - ($pos % 8))) {
-			my $spaces = ' ' x $count;
-			$line =~ s/\t/$spaces/;
-		}
-	}
-
-	return $line;
-}
-
 sub project_in_list {
 	my $project = shift;
 	my @list = git_get_projects_list();
diff --git a/gitweb/lib/Gitweb/Escape.pm b/gitweb/lib/Gitweb/Escape.pm
new file mode 100644
index 0000000..3d81682
--- /dev/null
+++ b/gitweb/lib/Gitweb/Escape.pm
@@ -0,0 +1,138 @@
+#!/usr/bin/perl
+#
+# Gitweb::Escape -- gitweb's quoting/unquoting, escaping package
+#
+# This program is licensed under the GPLv2
+
+package Gitweb::Escape;
+
+use strict;
+use warnings;
+use Exporter qw(import);
+
+our @EXPORT = qw(to_utf8 esc_param esc_url esc_html esc_path quot_cec quot_upr untabify);
+
+use Encode;
+use CGI;
+
+use Gitweb::Config qw($fallback_encoding);
+
+# decode sequences of octets in utf8 into Perl's internal form,
+# which is utf-8 with utf8 flag set if needed.  gitweb writes out
+# in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning of gitweb.perl
+sub to_utf8 {
+	my $str = shift;
+	return undef unless defined $str;
+	if (utf8::valid($str)) {
+		utf8::decode($str);
+		return $str;
+	} else {
+		return decode($fallback_encoding, $str, Encode::FB_DEFAULT);
+	}
+}
+
+# quote unsafe chars, but keep the slash, even when it's not
+# correct, but quoted slashes look too horrible in bookmarks
+sub esc_param {
+	my $str = shift;
+	return undef unless defined $str;
+	$str =~ s/([^A-Za-z0-9\-_.~()\/:@ ]+)/CGI::escape($1)/eg;
+	$str =~ s/ /\+/g;
+	return $str;
+}
+
+# quote unsafe chars in whole URL, so some charactrs cannot be quoted
+sub esc_url {
+	my $str = shift;
+	return undef unless defined $str;
+	$str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&= ]+)/CGI::escape($1)/eg;
+	$str =~ s/ /\+/g;
+	return $str;
+}
+
+# replace invalid utf8 character with SUBSTITUTION sequence
+sub esc_html {
+	my $str = shift;
+	my %opts = @_;
+
+	return undef unless defined $str;
+
+	$str = to_utf8($str);
+	$str = CGI::escapeHTML($str);
+	if ($opts{'-nbsp'}) {
+		$str =~ s/ /&nbsp;/g;
+	}
+	$str =~ s|([[:cntrl:]])|(($1 ne "\t") ? quot_cec($1) : $1)|eg;
+	return $str;
+}
+
+# quote control characters and escape filename to HTML
+sub esc_path {
+	my $str = shift;
+	my %opts = @_;
+
+	return undef unless defined $str;
+
+	$str = to_utf8($str);
+	$str = CGI::escapeHTML($str);
+	if ($opts{'-nbsp'}) {
+		$str =~ s/ /&nbsp;/g;
+	}
+	$str =~ s|([[:cntrl:]])|quot_cec($1)|eg;
+	return $str;
+}
+
+# Make control characters "printable", using character escape codes (CEC)
+sub quot_cec {
+	my $cntrl = shift;
+	my %opts = @_;
+	my %es = ( # character escape codes, aka escape sequences
+		"\t" => '\t',   # tab            (HT)
+		"\n" => '\n',   # line feed      (LF)
+		"\r" => '\r',   # carrige return (CR)
+		"\f" => '\f',   # form feed      (FF)
+		"\b" => '\b',   # backspace      (BS)
+		"\a" => '\a',   # alarm (bell)   (BEL)
+		"\e" => '\e',   # escape         (ESC)
+		"\013" => '\v', # vertical tab   (VT)
+		"\000" => '\0', # nul character  (NUL)
+	);
+	my $chr = ( (exists $es{$cntrl})
+		    ? $es{$cntrl}
+		    : sprintf('\%2x', ord($cntrl)) );
+	if ($opts{-nohtml}) {
+		return $chr;
+	} else {
+		return "<span class=\"cntrl\">$chr</span>";
+	}
+}
+
+# Alternatively use unicode control pictures codepoints,
+# Unicode "printable representation" (PR)
+sub quot_upr {
+	my $cntrl = shift;
+	my %opts = @_;
+
+	my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
+	if ($opts{-nohtml}) {
+		return $chr;
+	} else {
+		return "<span class=\"cntrl\">$chr</span>";
+	}
+}
+
+# escape tabs (convert tabs to spaces)
+sub untabify {
+	my $line = shift;
+
+	while ((my $pos = index($line, "\t")) != -1) {
+		if (my $count = (8 - ($pos % 8))) {
+			my $spaces = ' ' x $count;
+			$line =~ s/\t/$spaces/;
+		}
+	}
+
+	return $line;
+}
+
+1;
-- 
1.7.1.455.g8f441

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCHv2 GSOC 07/11] gitweb: Create Gitweb::RepoConfig module
  2010-07-15  7:29 [PATCHv2 00/11] Splitting gitweb Pavan Kumar Sunkara
                   ` (5 preceding siblings ...)
  2010-07-15  7:29 ` [PATCHv2 GSOC 06/11] gitweb: Create Gitweb::Escape module Pavan Kumar Sunkara
@ 2010-07-15  7:29 ` Pavan Kumar Sunkara
  2010-07-16 12:11   ` Jakub Narebski
  2010-07-15  7:29 ` [PATCHv2 GSOC 08/11] gitweb: Create Gitweb::View module Pavan Kumar Sunkara
                   ` (4 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: Pavan Kumar Sunkara @ 2010-07-15  7:29 UTC (permalink / raw)
  To: git, jnareb, chriscool, pasky; +Cc: Pavan Kumar Sunkara

Create a Gitweb::RepoConfig module in 'gitweb/lib/Gitweb/RepoConfig.pm'
to store and handle all the configuration and subroutines
related to a single repository regarding the gitweb.perl script.

This module depend on several other modules like Git.pm,
Config.pm, Request.pm and Escape.pm.

It also include subroutines regarding project_list and
it's handling.

Subroutines moved:
	check_head_link
	check_export_ok
	hash_set_multi
	git_parse_project_config
	config_to_bool
	config_to_int
	config_to_multi
	feature_bool
	feature_snapshot
	feature_patches
	feature_avatar
	git_get_project_config
	git_get_project_description
	git_get_project_ctags
	git_populate_project_tagcloud
	git_show_project_tagcloud
	git_get_project_url_list
	git_get_projects_list
	git_get_project_list_from_file
	git_get_project_owner
	get_file_owner
	project_in_list

Update gitweb/Makefile to install Gitweb::RepoConfig module
alongside gitweb

Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
---
 gitweb/Makefile                 |    1 +
 gitweb/gitweb.perl              |  441 +------------------------------------
 gitweb/lib/Gitweb/RepoConfig.pm |  466 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 468 insertions(+), 440 deletions(-)
 create mode 100644 gitweb/lib/Gitweb/RepoConfig.pm

diff --git a/gitweb/Makefile b/gitweb/Makefile
index c145ebd..726f393 100644
--- a/gitweb/Makefile
+++ b/gitweb/Makefile
@@ -116,6 +116,7 @@ GITWEB_MODULES += lib/Gitweb/Git.pm
 GITWEB_MODULES += lib/Gitweb/Config.pm
 GITWEB_MODULES += lib/Gitweb/Request.pm
 GITWEB_MODULES += lib/Gitweb/Escape.pm
+GITWEB_MODULES += lib/Gitweb/RepoConfig.pm
 
 GITWEB_REPLACE = \
 	-e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index ed64910..16e705e 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -18,7 +18,6 @@ sub __DIR__ () {
 use lib __DIR__ . '/lib';
 
 use CGI qw(:standard :escapeHTML -nosticky);
-use CGI::Util qw(unescape);
 use CGI::Carp qw(fatalsToBrowser set_message);
 use Fcntl ':mode';
 use File::Find qw();
@@ -30,6 +29,7 @@ use Gitweb::Git;
 use Gitweb::Config;
 use Gitweb::Request;
 use Gitweb::Escape;
+use Gitweb::RepoConfig;
 
 BEGIN {
 	CGI->compile() if $ENV{'MOD_PERL'};
@@ -69,64 +69,6 @@ $strict_export = "++GITWEB_STRICT_EXPORT++";
 $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
 $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
 
-sub feature_bool {
-	my $key = shift;
-	my ($val) = git_get_project_config($key, '--bool');
-
-	if (!defined $val) {
-		return ($_[0]);
-	} elsif ($val eq 'true') {
-		return (1);
-	} elsif ($val eq 'false') {
-		return (0);
-	}
-}
-
-sub feature_snapshot {
-	my (@fmts) = @_;
-
-	my ($val) = git_get_project_config('snapshot');
-
-	if ($val) {
-		@fmts = ($val eq 'none' ? () : split /\s*[,\s]\s*/, $val);
-	}
-
-	return @fmts;
-}
-
-sub feature_patches {
-	my @val = (git_get_project_config('patches', '--int'));
-
-	if (@val) {
-		return @val;
-	}
-
-	return ($_[0]);
-}
-
-sub feature_avatar {
-	my @val = (git_get_project_config('avatar'));
-
-	return @val ? @val : @_;
-}
-
-# checking HEAD file with -e is fragile if the repository was
-# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
-# and then pruned.
-sub check_head_link {
-	my ($dir) = @_;
-	my $headfile = "$dir/HEAD";
-	return ((-e $headfile) ||
-		(-l $headfile && readlink($headfile) =~ /^refs\/heads\//));
-}
-
-sub check_export_ok {
-	my ($dir) = @_;
-	return (check_head_link($dir) &&
-		(!$export_ok || -e "$dir/$export_ok") &&
-		(!$export_auth_hook || $export_auth_hook->($dir)));
-}
-
 # Get loadavg of system, to compare against $maxload.
 # Currently it requires '/proc/loadavg' present to get loadavg;
 # if it is not present it returns 0, which means no load checking.
@@ -781,12 +723,6 @@ sub unquote {
 	return $str;
 }
 
-sub project_in_list {
-	my $project = shift;
-	my @list = git_get_projects_list();
-	return @list && scalar(grep { $_->{'path'} eq $project } @list);
-}
-
 ## ----------------------------------------------------------------------
 ## HTML aware string manipulation
 
@@ -1595,129 +1531,6 @@ sub git_get_type {
 	return $type;
 }
 
-# repository configuration
-our $config_file = '';
-our %config;
-
-# store multiple values for single key as anonymous array reference
-# single values stored directly in the hash, not as [ <value> ]
-sub hash_set_multi {
-	my ($hash, $key, $value) = @_;
-
-	if (!exists $hash->{$key}) {
-		$hash->{$key} = $value;
-	} elsif (!ref $hash->{$key}) {
-		$hash->{$key} = [ $hash->{$key}, $value ];
-	} else {
-		push @{$hash->{$key}}, $value;
-	}
-}
-
-# return hash of git project configuration
-# optionally limited to some section, e.g. 'gitweb'
-sub git_parse_project_config {
-	my $section_regexp = shift;
-	my %config;
-
-	local $/ = "\0";
-
-	open my $fh, "-|", git_cmd(), "config", '-z', '-l',
-		or return;
-
-	while (my $keyval = <$fh>) {
-		chomp $keyval;
-		my ($key, $value) = split(/\n/, $keyval, 2);
-
-		hash_set_multi(\%config, $key, $value)
-			if (!defined $section_regexp || $key =~ /^(?:$section_regexp)\./o);
-	}
-	close $fh;
-
-	return %config;
-}
-
-# convert config value to boolean: 'true' or 'false'
-# no value, number > 0, 'true' and 'yes' values are true
-# rest of values are treated as false (never as error)
-sub config_to_bool {
-	my $val = shift;
-
-	return 1 if !defined $val;             # section.key
-
-	# strip leading and trailing whitespace
-	$val =~ s/^\s+//;
-	$val =~ s/\s+$//;
-
-	return (($val =~ /^\d+$/ && $val) ||   # section.key = 1
-	        ($val =~ /^(?:true|yes)$/i));  # section.key = true
-}
-
-# convert config value to simple decimal number
-# an optional value suffix of 'k', 'm', or 'g' will cause the value
-# to be multiplied by 1024, 1048576, or 1073741824
-sub config_to_int {
-	my $val = shift;
-
-	# strip leading and trailing whitespace
-	$val =~ s/^\s+//;
-	$val =~ s/\s+$//;
-
-	if (my ($num, $unit) = ($val =~ /^([0-9]*)([kmg])$/i)) {
-		$unit = lc($unit);
-		# unknown unit is treated as 1
-		return $num * ($unit eq 'g' ? 1073741824 :
-		               $unit eq 'm' ?    1048576 :
-		               $unit eq 'k' ?       1024 : 1);
-	}
-	return $val;
-}
-
-# convert config value to array reference, if needed
-sub config_to_multi {
-	my $val = shift;
-
-	return ref($val) ? $val : (defined($val) ? [ $val ] : []);
-}
-
-sub git_get_project_config {
-	my ($key, $type) = @_;
-
-	return unless defined $git_dir;
-
-	# key sanity check
-	return unless ($key);
-	$key =~ s/^gitweb\.//;
-	return if ($key =~ m/\W/);
-
-	# type sanity check
-	if (defined $type) {
-		$type =~ s/^--//;
-		$type = undef
-			unless ($type eq 'bool' || $type eq 'int');
-	}
-
-	# get config
-	if (!defined $config_file ||
-	    $config_file ne "$git_dir/config") {
-		%config = git_parse_project_config('gitweb');
-		$config_file = "$git_dir/config";
-	}
-
-	# check if config variable (key) exists
-	return unless exists $config{"gitweb.$key"};
-
-	# ensure given type
-	if (!defined $type) {
-		return $config{"gitweb.$key"};
-	} elsif ($type eq 'bool') {
-		# backward compatibility: 'git config --bool' returns true/false
-		return config_to_bool($config{"gitweb.$key"}) ? 'true' : 'false';
-	} elsif ($type eq 'int') {
-		return config_to_int($config{"gitweb.$key"});
-	}
-	return $config{"gitweb.$key"};
-}
-
 # get hash of given path at given ref
 sub git_get_hash_by_path {
 	my $base = shift;
@@ -1772,245 +1585,6 @@ sub git_get_path_by_hash {
 ## ......................................................................
 ## git utility functions, directly accessing git repository
 
-sub git_get_project_description {
-	my $path = shift;
-
-	$git_dir = "$projectroot/$path";
-	open my $fd, '<', "$git_dir/description"
-		or return git_get_project_config('description');
-	my $descr = <$fd>;
-	close $fd;
-	if (defined $descr) {
-		chomp $descr;
-	}
-	return $descr;
-}
-
-sub git_get_project_ctags {
-	my $path = shift;
-	my $ctags = {};
-
-	$git_dir = "$projectroot/$path";
-	opendir my $dh, "$git_dir/ctags"
-		or return $ctags;
-	foreach (grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh)) {
-		open my $ct, '<', $_ or next;
-		my $val = <$ct>;
-		chomp $val;
-		close $ct;
-		my $ctag = $_; $ctag =~ s#.*/##;
-		$ctags->{$ctag} = $val;
-	}
-	closedir $dh;
-	$ctags;
-}
-
-sub git_populate_project_tagcloud {
-	my $ctags = shift;
-
-	# First, merge different-cased tags; tags vote on casing
-	my %ctags_lc;
-	foreach (keys %$ctags) {
-		$ctags_lc{lc $_}->{count} += $ctags->{$_};
-		if (not $ctags_lc{lc $_}->{topcount}
-		    or $ctags_lc{lc $_}->{topcount} < $ctags->{$_}) {
-			$ctags_lc{lc $_}->{topcount} = $ctags->{$_};
-			$ctags_lc{lc $_}->{topname} = $_;
-		}
-	}
-
-	my $cloud;
-	if (eval { require HTML::TagCloud; 1; }) {
-		$cloud = HTML::TagCloud->new;
-		foreach (sort keys %ctags_lc) {
-			# Pad the title with spaces so that the cloud looks
-			# less crammed.
-			my $title = $ctags_lc{$_}->{topname};
-			$title =~ s/ /&nbsp;/g;
-			$title =~ s/^/&nbsp;/g;
-			$title =~ s/$/&nbsp;/g;
-			$cloud->add($title, $home_link."?by_tag=".$_, $ctags_lc{$_}->{count});
-		}
-	} else {
-		$cloud = \%ctags_lc;
-	}
-	$cloud;
-}
-
-sub git_show_project_tagcloud {
-	my ($cloud, $count) = @_;
-	print STDERR ref($cloud)."..\n";
-	if (ref $cloud eq 'HTML::TagCloud') {
-		return $cloud->html_and_css($count);
-	} else {
-		my @tags = sort { $cloud->{$a}->{count} <=> $cloud->{$b}->{count} } keys %$cloud;
-		return '<p align="center">' . join (', ', map {
-			"<a href=\"$home_link?by_tag=$_\">$cloud->{$_}->{topname}</a>"
-		} splice(@tags, 0, $count)) . '</p>';
-	}
-}
-
-sub git_get_project_url_list {
-	my $path = shift;
-
-	$git_dir = "$projectroot/$path";
-	open my $fd, '<', "$git_dir/cloneurl"
-		or return wantarray ?
-		@{ config_to_multi(git_get_project_config('url')) } :
-		   config_to_multi(git_get_project_config('url'));
-	my @git_project_url_list = map { chomp; $_ } <$fd>;
-	close $fd;
-
-	return wantarray ? @git_project_url_list : \@git_project_url_list;
-}
-
-sub git_get_projects_list {
-	my ($filter) = @_;
-	my @list;
-
-	$filter ||= '';
-	$filter =~ s/\.git$//;
-
-	my $check_forks = gitweb_check_feature('forks');
-
-	if (-d $projects_list) {
-		# search in directory
-		my $dir = $projects_list . ($filter ? "/$filter" : '');
-		# remove the trailing "/"
-		$dir =~ s!/+$!!;
-		my $pfxlen = length("$dir");
-		my $pfxdepth = ($dir =~ tr!/!!);
-
-		File::Find::find({
-			follow_fast => 1, # follow symbolic links
-			follow_skip => 2, # ignore duplicates
-			dangling_symlinks => 0, # ignore dangling symlinks, silently
-			wanted => sub {
-				# skip project-list toplevel, if we get it.
-				return if (m!^[/.]$!);
-				# only directories can be git repositories
-				return unless (-d $_);
-				# don't traverse too deep (Find is super slow on os x)
-				if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) {
-					$File::Find::prune = 1;
-					return;
-				}
-
-				my $subdir = substr($File::Find::name, $pfxlen + 1);
-				# we check related file in $projectroot
-				my $path = ($filter ? "$filter/" : '') . $subdir;
-				if (check_export_ok("$projectroot/$path")) {
-					push @list, { path => $path };
-					$File::Find::prune = 1;
-				}
-			},
-		}, "$dir");
-
-	} elsif (-f $projects_list) {
-		# read from file(url-encoded):
-		# 'git%2Fgit.git Linus+Torvalds'
-		# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
-		# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
-		my %paths;
-		open my $fd, '<', $projects_list or return;
-	PROJECT:
-		while (my $line = <$fd>) {
-			chomp $line;
-			my ($path, $owner) = split ' ', $line;
-			$path = unescape($path);
-			$owner = unescape($owner);
-			if (!defined $path) {
-				next;
-			}
-			if ($filter ne '') {
-				# looking for forks;
-				my $pfx = substr($path, 0, length($filter));
-				if ($pfx ne $filter) {
-					next PROJECT;
-				}
-				my $sfx = substr($path, length($filter));
-				if ($sfx !~ /^\/.*\.git$/) {
-					next PROJECT;
-				}
-			} elsif ($check_forks) {
-			PATH:
-				foreach my $filter (keys %paths) {
-					# looking for forks;
-					my $pfx = substr($path, 0, length($filter));
-					if ($pfx ne $filter) {
-						next PATH;
-					}
-					my $sfx = substr($path, length($filter));
-					if ($sfx !~ /^\/.*\.git$/) {
-						next PATH;
-					}
-					# is a fork, don't include it in
-					# the list
-					next PROJECT;
-				}
-			}
-			if (check_export_ok("$projectroot/$path")) {
-				my $pr = {
-					path => $path,
-					owner => to_utf8($owner),
-				};
-				push @list, $pr;
-				(my $forks_path = $path) =~ s/\.git$//;
-				$paths{$forks_path}++;
-			}
-		}
-		close $fd;
-	}
-	return @list;
-}
-
-our $gitweb_project_owner = undef;
-sub git_get_project_list_from_file {
-
-	return if (defined $gitweb_project_owner);
-
-	$gitweb_project_owner = {};
-	# read from file (url-encoded):
-	# 'git%2Fgit.git Linus+Torvalds'
-	# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
-	# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
-	if (-f $projects_list) {
-		open(my $fd, '<', $projects_list);
-		while (my $line = <$fd>) {
-			chomp $line;
-			my ($pr, $ow) = split ' ', $line;
-			$pr = unescape($pr);
-			$ow = unescape($ow);
-			$gitweb_project_owner->{$pr} = to_utf8($ow);
-		}
-		close $fd;
-	}
-}
-
-sub git_get_project_owner {
-	my $project = shift;
-	my $owner;
-
-	return undef unless $project;
-	$git_dir = "$projectroot/$project";
-
-	if (!defined $gitweb_project_owner) {
-		git_get_project_list_from_file();
-	}
-
-	if (exists $gitweb_project_owner->{$project}) {
-		$owner = $gitweb_project_owner->{$project};
-	}
-	if (!defined $owner){
-		$owner = git_get_project_config('owner');
-	}
-	if (!defined $owner) {
-		$owner = get_file_owner("$git_dir");
-	}
-
-	return $owner;
-}
-
 sub git_get_last_activity {
 	my ($path) = @_;
 	my $fd;
@@ -2518,19 +2092,6 @@ sub git_get_tags_list {
 ## ----------------------------------------------------------------------
 ## filesystem-related functions
 
-sub get_file_owner {
-	my $path = shift;
-
-	my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path);
-	my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid);
-	if (!defined $gcos) {
-		return undef;
-	}
-	my $owner = $gcos;
-	$owner =~ s/[,;].*$//;
-	return to_utf8($owner);
-}
-
 # assume that file exists
 sub insert_file {
 	my $filename = shift;
diff --git a/gitweb/lib/Gitweb/RepoConfig.pm b/gitweb/lib/Gitweb/RepoConfig.pm
new file mode 100644
index 0000000..c8d961d
--- /dev/null
+++ b/gitweb/lib/Gitweb/RepoConfig.pm
@@ -0,0 +1,466 @@
+#!/usr/bin/perl
+#
+# Gitweb::RepoConfig -- gitweb's per-repository configuration subs package
+#
+# This program is licensed under the GPLv2
+
+package Gitweb::RepoConfig;
+
+use strict;
+use warnings;
+use Exporter qw(import);
+
+our @EXPORT = qw($config_file %config $gitweb_project_owner git_get_project_url_list
+                 git_get_project_config git_get_project_description git_get_project_ctags
+                 git_populate_project_tagcloud git_show_project_tagcloud check_head_link
+                 check_export_ok git_get_projects_list git_get_project_list_from_file
+                 git_get_project_owner project_in_list feature_bool feature_snapshot
+                 feature_patches feature_avatar);
+
+use CGI::Util qw(unescape);
+use Gitweb::Git qw(git_cmd $git_dir);
+use Gitweb::Config qw(gitweb_check_feature $projectroot $projects_list
+                      $project_maxdepth $export_ok $export_auth_hook);
+use Gitweb::Request qw($home_link);
+use Gitweb::Escape qw(to_utf8);
+
+# repository configuration
+our $config_file = '';
+our %config;
+
+# checking HEAD file with -e is fragile if the repository was
+# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
+# and then pruned.
+sub check_head_link {
+	my ($dir) = @_;
+	my $headfile = "$dir/HEAD";
+	return ((-e $headfile) ||
+		(-l $headfile && readlink($headfile) =~ /^refs\/heads\//));
+}
+
+sub check_export_ok {
+	my ($dir) = @_;
+	return (check_head_link($dir) &&
+		(!$export_ok || -e "$dir/$export_ok") &&
+		(!$export_auth_hook || $export_auth_hook->($dir)));
+}
+
+# store multiple values for single key as anonymous array reference
+# single values stored directly in the hash, not as [ <value> ]
+sub hash_set_multi {
+	my ($hash, $key, $value) = @_;
+
+	if (!exists $hash->{$key}) {
+		$hash->{$key} = $value;
+	} elsif (!ref $hash->{$key}) {
+		$hash->{$key} = [ $hash->{$key}, $value ];
+	} else {
+		push @{$hash->{$key}}, $value;
+	}
+}
+
+# return hash of git project configuration
+# optionally limited to some section, e.g. 'gitweb'
+sub git_parse_project_config {
+	my $section_regexp = shift;
+	my %config;
+
+	local $/ = "\0";
+
+	open my $fh, "-|", git_cmd(), "config", '-z', '-l',
+		or return;
+
+	while (my $keyval = <$fh>) {
+		chomp $keyval;
+		my ($key, $value) = split(/\n/, $keyval, 2);
+
+		hash_set_multi(\%config, $key, $value)
+			if (!defined $section_regexp || $key =~ /^(?:$section_regexp)\./o);
+	}
+	close $fh;
+
+	return %config;
+}
+
+# convert config value to boolean: 'true' or 'false'
+# no value, number > 0, 'true' and 'yes' values are true
+# rest of values are treated as false (never as error)
+sub config_to_bool {
+	my $val = shift;
+
+	return 1 if !defined $val;             # section.key
+
+	# strip leading and trailing whitespace
+	$val =~ s/^\s+//;
+	$val =~ s/\s+$//;
+
+	return (($val =~ /^\d+$/ && $val) ||   # section.key = 1
+	        ($val =~ /^(?:true|yes)$/i));  # section.key = true
+}
+
+# convert config value to simple decimal number
+# an optional value suffix of 'k', 'm', or 'g' will cause the value
+# to be multiplied by 1024, 1048576, or 1073741824
+sub config_to_int {
+	my $val = shift;
+
+	# strip leading and trailing whitespace
+	$val =~ s/^\s+//;
+	$val =~ s/\s+$//;
+
+	if (my ($num, $unit) = ($val =~ /^([0-9]*)([kmg])$/i)) {
+		$unit = lc($unit);
+		# unknown unit is treated as 1
+		return $num * ($unit eq 'g' ? 1073741824 :
+		               $unit eq 'm' ?    1048576 :
+		               $unit eq 'k' ?       1024 : 1);
+	}
+	return $val;
+}
+
+# convert config value to array reference, if needed
+sub config_to_multi {
+	my $val = shift;
+
+	return ref($val) ? $val : (defined($val) ? [ $val ] : []);
+}
+
+sub feature_bool {
+	my $key = shift;
+	my ($val) = git_get_project_config($key, '--bool');
+
+	if (!defined $val) {
+		return ($_[0]);
+	} elsif ($val eq 'true') {
+		return (1);
+	} elsif ($val eq 'false') {
+		return (0);
+	}
+}
+
+sub feature_snapshot {
+	my (@fmts) = @_;
+
+	my ($val) = git_get_project_config('snapshot');
+
+	if ($val) {
+		@fmts = ($val eq 'none' ? () : split /\s*[,\s]\s*/, $val);
+	}
+
+	return @fmts;
+}
+
+sub feature_patches {
+	my @val = (git_get_project_config('patches', '--int'));
+
+	if (@val) {
+		return @val;
+	}
+
+	return ($_[0]);
+}
+
+sub feature_avatar {
+	my @val = (git_get_project_config('avatar'));
+
+	return @val ? @val : @_;
+}
+
+sub git_get_project_config {
+	my ($key, $type) = @_;
+
+	return unless defined $git_dir;
+
+	# key sanity check
+	return unless ($key);
+	$key =~ s/^gitweb\.//;
+	return if ($key =~ m/\W/);
+
+	# type sanity check
+	if (defined $type) {
+		$type =~ s/^--//;
+		$type = undef
+			unless ($type eq 'bool' || $type eq 'int');
+	}
+
+	# get config
+	if (!defined $config_file ||
+	    $config_file ne "$git_dir/config") {
+		%config = git_parse_project_config('gitweb');
+		$config_file = "$git_dir/config";
+	}
+
+	# check if config variable (key) exists
+	return unless exists $config{"gitweb.$key"};
+
+	# ensure given type
+	if (!defined $type) {
+		return $config{"gitweb.$key"};
+	} elsif ($type eq 'bool') {
+		# backward compatibility: 'git config --bool' returns true/false
+		return config_to_bool($config{"gitweb.$key"}) ? 'true' : 'false';
+	} elsif ($type eq 'int') {
+		return config_to_int($config{"gitweb.$key"});
+	}
+	return $config{"gitweb.$key"};
+}
+
+sub git_get_project_description {
+	my $path = shift;
+
+	$git_dir = "$projectroot/$path";
+	open my $fd, '<', "$git_dir/description"
+		or return git_get_project_config('description');
+	my $descr = <$fd>;
+	close $fd;
+	if (defined $descr) {
+		chomp $descr;
+	}
+	return $descr;
+}
+
+sub git_get_project_ctags {
+	my $path = shift;
+	my $ctags = {};
+
+	$git_dir = "$projectroot/$path";
+	opendir my $dh, "$git_dir/ctags"
+		or return $ctags;
+	foreach (grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh)) {
+		open my $ct, '<', $_ or next;
+		my $val = <$ct>;
+		chomp $val;
+		close $ct;
+		my $ctag = $_; $ctag =~ s#.*/##;
+		$ctags->{$ctag} = $val;
+	}
+	closedir $dh;
+	$ctags;
+}
+
+sub git_populate_project_tagcloud {
+	my $ctags = shift;
+
+	# First, merge different-cased tags; tags vote on casing
+	my %ctags_lc;
+	foreach (keys %$ctags) {
+		$ctags_lc{lc $_}->{count} += $ctags->{$_};
+		if (not $ctags_lc{lc $_}->{topcount}
+		    or $ctags_lc{lc $_}->{topcount} < $ctags->{$_}) {
+			$ctags_lc{lc $_}->{topcount} = $ctags->{$_};
+			$ctags_lc{lc $_}->{topname} = $_;
+		}
+	}
+
+	my $cloud;
+	if (eval { require HTML::TagCloud; 1; }) {
+		$cloud = HTML::TagCloud->new;
+		foreach (sort keys %ctags_lc) {
+			# Pad the title with spaces so that the cloud looks
+			# less crammed.
+			my $title = $ctags_lc{$_}->{topname};
+			$title =~ s/ /&nbsp;/g;
+			$title =~ s/^/&nbsp;/g;
+			$title =~ s/$/&nbsp;/g;
+			$cloud->add($title, $home_link."?by_tag=".$_, $ctags_lc{$_}->{count});
+		}
+	} else {
+		$cloud = \%ctags_lc;
+	}
+	$cloud;
+}
+
+sub git_show_project_tagcloud {
+	my ($cloud, $count) = @_;
+	print STDERR ref($cloud)."..\n";
+	if (ref $cloud eq 'HTML::TagCloud') {
+		return $cloud->html_and_css($count);
+	} else {
+		my @tags = sort { $cloud->{$a}->{count} <=> $cloud->{$b}->{count} } keys %$cloud;
+		return '<p align="center">' . join (', ', map {
+			"<a href=\"$home_link?by_tag=$_\">$cloud->{$_}->{topname}</a>"
+		} splice(@tags, 0, $count)) . '</p>';
+	}
+}
+
+sub git_get_project_url_list {
+	my $path = shift;
+
+	$git_dir = "$projectroot/$path";
+	open my $fd, '<', "$git_dir/cloneurl"
+		or return wantarray ?
+		@{ config_to_multi(git_get_project_config('url')) } :
+		   config_to_multi(git_get_project_config('url'));
+	my @git_project_url_list = map { chomp; $_ } <$fd>;
+	close $fd;
+
+	return wantarray ? @git_project_url_list : \@git_project_url_list;
+}
+
+sub git_get_projects_list {
+	my ($filter) = @_;
+	my @list;
+
+	$filter ||= '';
+	$filter =~ s/\.git$//;
+
+	my $check_forks = gitweb_check_feature('forks');
+
+	if (-d $projects_list) {
+		# search in directory
+		my $dir = $projects_list . ($filter ? "/$filter" : '');
+		# remove the trailing "/"
+		$dir =~ s!/+$!!;
+		my $pfxlen = length("$dir");
+		my $pfxdepth = ($dir =~ tr!/!!);
+
+		File::Find::find({
+			follow_fast => 1, # follow symbolic links
+			follow_skip => 2, # ignore duplicates
+			dangling_symlinks => 0, # ignore dangling symlinks, silently
+			wanted => sub {
+				# skip project-list toplevel, if we get it.
+				return if (m!^[/.]$!);
+				# only directories can be git repositories
+				return unless (-d $_);
+				# don't traverse too deep (Find is super slow on os x)
+				if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) {
+					$File::Find::prune = 1;
+					return;
+				}
+
+				my $subdir = substr($File::Find::name, $pfxlen + 1);
+				# we check related file in $projectroot
+				my $path = ($filter ? "$filter/" : '') . $subdir;
+				if (check_export_ok("$projectroot/$path")) {
+					push @list, { path => $path };
+					$File::Find::prune = 1;
+				}
+			},
+		}, "$dir");
+
+	} elsif (-f $projects_list) {
+		# read from file(url-encoded):
+		# 'git%2Fgit.git Linus+Torvalds'
+		# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
+		# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
+		my %paths;
+		open my $fd, '<', $projects_list or return;
+	PROJECT:
+		while (my $line = <$fd>) {
+			chomp $line;
+			my ($path, $owner) = split ' ', $line;
+			$path = unescape($path);
+			$owner = unescape($owner);
+			if (!defined $path) {
+				next;
+			}
+			if ($filter ne '') {
+				# looking for forks;
+				my $pfx = substr($path, 0, length($filter));
+				if ($pfx ne $filter) {
+					next PROJECT;
+				}
+				my $sfx = substr($path, length($filter));
+				if ($sfx !~ /^\/.*\.git$/) {
+					next PROJECT;
+				}
+			} elsif ($check_forks) {
+			PATH:
+				foreach my $filter (keys %paths) {
+					# looking for forks;
+					my $pfx = substr($path, 0, length($filter));
+					if ($pfx ne $filter) {
+						next PATH;
+					}
+					my $sfx = substr($path, length($filter));
+					if ($sfx !~ /^\/.*\.git$/) {
+						next PATH;
+					}
+					# is a fork, don't include it in
+					# the list
+					next PROJECT;
+				}
+			}
+			if (check_export_ok("$projectroot/$path")) {
+				my $pr = {
+					path => $path,
+					owner => to_utf8($owner),
+				};
+				push @list, $pr;
+				(my $forks_path = $path) =~ s/\.git$//;
+				$paths{$forks_path}++;
+			}
+		}
+		close $fd;
+	}
+	return @list;
+}
+
+our $gitweb_project_owner = undef;
+sub git_get_project_list_from_file {
+
+	return if (defined $gitweb_project_owner);
+
+	$gitweb_project_owner = {};
+	# read from file (url-encoded):
+	# 'git%2Fgit.git Linus+Torvalds'
+	# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
+	# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
+	if (-f $projects_list) {
+		open(my $fd, '<', $projects_list);
+		while (my $line = <$fd>) {
+			chomp $line;
+			my ($pr, $ow) = split ' ', $line;
+			$pr = unescape($pr);
+			$ow = unescape($ow);
+			$gitweb_project_owner->{$pr} = to_utf8($ow);
+		}
+		close $fd;
+	}
+}
+
+sub git_get_project_owner {
+	my $project = shift;
+	my $owner;
+
+	return undef unless $project;
+	$git_dir = "$projectroot/$project";
+
+	if (!defined $gitweb_project_owner) {
+		git_get_project_list_from_file();
+	}
+
+	if (exists $gitweb_project_owner->{$project}) {
+		$owner = $gitweb_project_owner->{$project};
+	}
+	if (!defined $owner){
+		$owner = git_get_project_config('owner');
+	}
+	if (!defined $owner) {
+		$owner = get_file_owner("$git_dir");
+	}
+
+	return $owner;
+}
+
+sub get_file_owner {
+	my $path = shift;
+
+	my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path);
+	my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid);
+	if (!defined $gcos) {
+		return undef;
+	}
+	my $owner = $gcos;
+	$owner =~ s/[,;].*$//;
+	return to_utf8($owner);
+}
+
+sub project_in_list {
+	my $project = shift;
+	my @list = git_get_projects_list();
+	return @list && scalar(grep { $_->{'path'} eq $project } @list);
+}
+
+1;
-- 
1.7.1.455.g8f441

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCHv2 GSOC 08/11] gitweb: Create Gitweb::View module
  2010-07-15  7:29 [PATCHv2 00/11] Splitting gitweb Pavan Kumar Sunkara
                   ` (6 preceding siblings ...)
  2010-07-15  7:29 ` [PATCHv2 GSOC 07/11] gitweb: Create Gitweb::RepoConfig module Pavan Kumar Sunkara
@ 2010-07-15  7:29 ` Pavan Kumar Sunkara
  2010-07-18 15:10   ` Jakub Narebski
  2010-07-15  7:29 ` [PATCHv2 GSOC 09/11] gitweb: Create Gitweb::Util module Pavan Kumar Sunkara
                   ` (3 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: Pavan Kumar Sunkara @ 2010-07-15  7:29 UTC (permalink / raw)
  To: git, jnareb, chriscool, pasky; +Cc: Pavan Kumar Sunkara

Create Gitweb::View module in 'gitweb/lib/Gitweb/View.pm'
to store the subroutines related to the HTML output
for gitweb.

This module depends on Git.pm, Config.pm, Request.pm,
Escape.pm and RepoConfig.pm. Action specific HTML div
subroutines are not included in this module due to
unmet dependencies of Gitweb::Parse and Gitweb::Format.

Subroutines moved:
	*href
	 get_feed_info
	*chop_str
	 chop_and_escape_str
	*age_class
	 age_string
	*S_ISGITLINK
	 mode_str
	 file_type
	 file_type_long
	*get_page_title
	 git_header_html
	 git_footer_html
	 die_error
	*git_print_page_nav
	 format_paging_nav
	 git_print_header_div
	 git_print_page_path
	*print_local_time
	 format_local_time
	*insert_file
	*git_get_link_target
	 normalize_link_target
	 git_print_tree_entry
	*print_sort_th
	 format_sort_th

Update 'gitweb/Makefile' to install Gitweb::View alongside gitweb.

Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
---
 gitweb/Makefile           |    1 +
 gitweb/gitweb.perl        |  996 +-------------------------------------------
 gitweb/lib/Gitweb/View.pm | 1022 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1024 insertions(+), 995 deletions(-)
 create mode 100644 gitweb/lib/Gitweb/View.pm

diff --git a/gitweb/Makefile b/gitweb/Makefile
index 726f393..ce9f372 100644
--- a/gitweb/Makefile
+++ b/gitweb/Makefile
@@ -117,6 +117,7 @@ GITWEB_MODULES += lib/Gitweb/Config.pm
 GITWEB_MODULES += lib/Gitweb/Request.pm
 GITWEB_MODULES += lib/Gitweb/Escape.pm
 GITWEB_MODULES += lib/Gitweb/RepoConfig.pm
+GITWEB_MODULES += lib/Gitweb/View.pm
 
 GITWEB_REPLACE = \
 	-e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 16e705e..80c4a40 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -30,6 +30,7 @@ use Gitweb::Config;
 use Gitweb::Request;
 use Gitweb::Escape;
 use Gitweb::RepoConfig;
+use Gitweb::View;
 
 BEGIN {
 	CGI->compile() if $ENV{'MOD_PERL'};
@@ -512,128 +513,6 @@ if (defined caller) {
 }
 
 ## ======================================================================
-## action links
-
-# possible values of extra options
-# -full => 0|1      - use absolute/full URL ($my_uri/$my_url as base)
-# -replay => 1      - start from a current view (replay with modifications)
-# -path_info => 0|1 - don't use/use path_info URL (if possible)
-sub href {
-	my %params = @_;
-	# default is to use -absolute url() i.e. $my_uri
-	my $href = $params{-full} ? $my_url : $my_uri;
-
-	$params{'project'} = $project unless exists $params{'project'};
-
-	if ($params{-replay}) {
-		while (my ($name, $symbol) = each %cgi_param_mapping) {
-			if (!exists $params{$name}) {
-				$params{$name} = $input_params{$name};
-			}
-		}
-	}
-
-	my $use_pathinfo = gitweb_check_feature('pathinfo');
-	if (defined $params{'project'} &&
-	    (exists $params{-path_info} ? $params{-path_info} : $use_pathinfo)) {
-		# try to put as many parameters as possible in PATH_INFO:
-		#   - project name
-		#   - action
-		#   - hash_parent or hash_parent_base:/file_parent
-		#   - hash or hash_base:/filename
-		#   - the snapshot_format as an appropriate suffix
-
-		# When the script is the root DirectoryIndex for the domain,
-		# $href here would be something like http://gitweb.example.com/
-		# Thus, we strip any trailing / from $href, to spare us double
-		# slashes in the final URL
-		$href =~ s,/$,,;
-
-		# Then add the project name, if present
-		$href .= "/".esc_url($params{'project'});
-		delete $params{'project'};
-
-		# since we destructively absorb parameters, we keep this
-		# boolean that remembers if we're handling a snapshot
-		my $is_snapshot = $params{'action'} eq 'snapshot';
-
-		# Summary just uses the project path URL, any other action is
-		# added to the URL
-		if (defined $params{'action'}) {
-			$href .= "/".esc_url($params{'action'}) unless $params{'action'} eq 'summary';
-			delete $params{'action'};
-		}
-
-		# Next, we put hash_parent_base:/file_parent..hash_base:/file_name,
-		# stripping nonexistent or useless pieces
-		$href .= "/" if ($params{'hash_base'} || $params{'hash_parent_base'}
-			|| $params{'hash_parent'} || $params{'hash'});
-		if (defined $params{'hash_base'}) {
-			if (defined $params{'hash_parent_base'}) {
-				$href .= esc_url($params{'hash_parent_base'});
-				# skip the file_parent if it's the same as the file_name
-				if (defined $params{'file_parent'}) {
-					if (defined $params{'file_name'} && $params{'file_parent'} eq $params{'file_name'}) {
-						delete $params{'file_parent'};
-					} elsif ($params{'file_parent'} !~ /\.\./) {
-						$href .= ":/".esc_url($params{'file_parent'});
-						delete $params{'file_parent'};
-					}
-				}
-				$href .= "..";
-				delete $params{'hash_parent'};
-				delete $params{'hash_parent_base'};
-			} elsif (defined $params{'hash_parent'}) {
-				$href .= esc_url($params{'hash_parent'}). "..";
-				delete $params{'hash_parent'};
-			}
-
-			$href .= esc_url($params{'hash_base'});
-			if (defined $params{'file_name'} && $params{'file_name'} !~ /\.\./) {
-				$href .= ":/".esc_url($params{'file_name'});
-				delete $params{'file_name'};
-			}
-			delete $params{'hash'};
-			delete $params{'hash_base'};
-		} elsif (defined $params{'hash'}) {
-			$href .= esc_url($params{'hash'});
-			delete $params{'hash'};
-		}
-
-		# If the action was a snapshot, we can absorb the
-		# snapshot_format parameter too
-		if ($is_snapshot) {
-			my $fmt = $params{'snapshot_format'};
-			# snapshot_format should always be defined when href()
-			# is called, but just in case some code forgets, we
-			# fall back to the default
-			$fmt ||= $snapshot_fmts[0];
-			$href .= $known_snapshot_formats{$fmt}{'suffix'};
-			delete $params{'snapshot_format'};
-		}
-	}
-
-	# now encode the parameters explicitly
-	my @result = ();
-	for (my $i = 0; $i < @cgi_param_mapping; $i += 2) {
-		my ($name, $symbol) = ($cgi_param_mapping[$i], $cgi_param_mapping[$i+1]);
-		if (defined $params{$name}) {
-			if (ref($params{$name}) eq "ARRAY") {
-				foreach my $par (@{$params{$name}}) {
-					push @result, $symbol . "=" . esc_param($par);
-				}
-			} else {
-				push @result, $symbol . "=" . esc_param($params{$name});
-			}
-		}
-	}
-	$href .= "?" . join(';', @result) if scalar @result;
-
-	return $href;
-}
-
-
-## ======================================================================
 ## validation, quoting/unquoting and escaping
 
 sub validate_action {
@@ -724,219 +603,6 @@ sub unquote {
 }
 
 ## ----------------------------------------------------------------------
-## HTML aware string manipulation
-
-# Try to chop given string on a word boundary between position
-# $len and $len+$add_len. If there is no word boundary there,
-# chop at $len+$add_len. Do not chop if chopped part plus ellipsis
-# (marking chopped part) would be longer than given string.
-sub chop_str {
-	my $str = shift;
-	my $len = shift;
-	my $add_len = shift || 10;
-	my $where = shift || 'right'; # 'left' | 'center' | 'right'
-
-	# Make sure perl knows it is utf8 encoded so we don't
-	# cut in the middle of a utf8 multibyte char.
-	$str = to_utf8($str);
-
-	# allow only $len chars, but don't cut a word if it would fit in $add_len
-	# if it doesn't fit, cut it if it's still longer than the dots we would add
-	# remove chopped character entities entirely
-
-	# when chopping in the middle, distribute $len into left and right part
-	# return early if chopping wouldn't make string shorter
-	if ($where eq 'center') {
-		return $str if ($len + 5 >= length($str)); # filler is length 5
-		$len = int($len/2);
-	} else {
-		return $str if ($len + 4 >= length($str)); # filler is length 4
-	}
-
-	# regexps: ending and beginning with word part up to $add_len
-	my $endre = qr/.{$len}\w{0,$add_len}/;
-	my $begre = qr/\w{0,$add_len}.{$len}/;
-
-	if ($where eq 'left') {
-		$str =~ m/^(.*?)($begre)$/;
-		my ($lead, $body) = ($1, $2);
-		if (length($lead) > 4) {
-			$lead = " ...";
-		}
-		return "$lead$body";
-
-	} elsif ($where eq 'center') {
-		$str =~ m/^($endre)(.*)$/;
-		my ($left, $str)  = ($1, $2);
-		$str =~ m/^(.*?)($begre)$/;
-		my ($mid, $right) = ($1, $2);
-		if (length($mid) > 5) {
-			$mid = " ... ";
-		}
-		return "$left$mid$right";
-
-	} else {
-		$str =~ m/^($endre)(.*)$/;
-		my $body = $1;
-		my $tail = $2;
-		if (length($tail) > 4) {
-			$tail = "... ";
-		}
-		return "$body$tail";
-	}
-}
-
-# takes the same arguments as chop_str, but also wraps a <span> around the
-# result with a title attribute if it does get chopped. Additionally, the
-# string is HTML-escaped.
-sub chop_and_escape_str {
-	my ($str) = @_;
-
-	my $chopped = chop_str(@_);
-	if ($chopped eq $str) {
-		return esc_html($chopped);
-	} else {
-		$str =~ s/[[:cntrl:]]/?/g;
-		return $cgi->span({-title=>$str}, esc_html($chopped));
-	}
-}
-
-## ----------------------------------------------------------------------
-## functions returning short strings
-
-# CSS class for given age value (in seconds)
-sub age_class {
-	my $age = shift;
-
-	if (!defined $age) {
-		return "noage";
-	} elsif ($age < 60*60*2) {
-		return "age0";
-	} elsif ($age < 60*60*24*2) {
-		return "age1";
-	} else {
-		return "age2";
-	}
-}
-
-# convert age in seconds to "nn units ago" string
-sub age_string {
-	my $age = shift;
-	my $age_str;
-
-	if ($age > 60*60*24*365*2) {
-		$age_str = (int $age/60/60/24/365);
-		$age_str .= " years ago";
-	} elsif ($age > 60*60*24*(365/12)*2) {
-		$age_str = int $age/60/60/24/(365/12);
-		$age_str .= " months ago";
-	} elsif ($age > 60*60*24*7*2) {
-		$age_str = int $age/60/60/24/7;
-		$age_str .= " weeks ago";
-	} elsif ($age > 60*60*24*2) {
-		$age_str = int $age/60/60/24;
-		$age_str .= " days ago";
-	} elsif ($age > 60*60*2) {
-		$age_str = int $age/60/60;
-		$age_str .= " hours ago";
-	} elsif ($age > 60*2) {
-		$age_str = int $age/60;
-		$age_str .= " min ago";
-	} elsif ($age > 2) {
-		$age_str = int $age;
-		$age_str .= " sec ago";
-	} else {
-		$age_str .= " right now";
-	}
-	return $age_str;
-}
-
-use constant {
-	S_IFINVALID => 0030000,
-	S_IFGITLINK => 0160000,
-};
-
-# submodule/subproject, a commit object reference
-sub S_ISGITLINK {
-	my $mode = shift;
-
-	return (($mode & S_IFMT) == S_IFGITLINK)
-}
-
-# convert file mode in octal to symbolic file mode string
-sub mode_str {
-	my $mode = oct shift;
-
-	if (S_ISGITLINK($mode)) {
-		return 'm---------';
-	} elsif (S_ISDIR($mode & S_IFMT)) {
-		return 'drwxr-xr-x';
-	} elsif (S_ISLNK($mode)) {
-		return 'lrwxrwxrwx';
-	} elsif (S_ISREG($mode)) {
-		# git cares only about the executable bit
-		if ($mode & S_IXUSR) {
-			return '-rwxr-xr-x';
-		} else {
-			return '-rw-r--r--';
-		};
-	} else {
-		return '----------';
-	}
-}
-
-# convert file mode in octal to file type string
-sub file_type {
-	my $mode = shift;
-
-	if ($mode !~ m/^[0-7]+$/) {
-		return $mode;
-	} else {
-		$mode = oct $mode;
-	}
-
-	if (S_ISGITLINK($mode)) {
-		return "submodule";
-	} elsif (S_ISDIR($mode & S_IFMT)) {
-		return "directory";
-	} elsif (S_ISLNK($mode)) {
-		return "symlink";
-	} elsif (S_ISREG($mode)) {
-		return "file";
-	} else {
-		return "unknown";
-	}
-}
-
-# convert file mode in octal to file type description string
-sub file_type_long {
-	my $mode = shift;
-
-	if ($mode !~ m/^[0-7]+$/) {
-		return $mode;
-	} else {
-		$mode = oct $mode;
-	}
-
-	if (S_ISGITLINK($mode)) {
-		return "submodule";
-	} elsif (S_ISDIR($mode & S_IFMT)) {
-		return "directory";
-	} elsif (S_ISLNK($mode)) {
-		return "symlink";
-	} elsif (S_ISREG($mode)) {
-		if ($mode & S_IXUSR) {
-			return "executable";
-		} else {
-			return "file";
-		};
-	} else {
-		return "unknown";
-	}
-}
-
-
-## ----------------------------------------------------------------------
 ## functions returning short HTML fragments, or transforming HTML fragments
 ## which don't belong to other sections
 
@@ -1447,46 +1113,6 @@ sub format_snapshot_links {
 	}
 }
 
-## ......................................................................
-## functions returning values to be passed, perhaps after some
-## transformation, to other functions; e.g. returning arguments to href()
-
-# returns hash to be passed to href to generate gitweb URL
-# in -title key it returns description of link
-sub get_feed_info {
-	my $format = shift || 'Atom';
-	my %res = (action => lc($format));
-
-	# feed links are possible only for project views
-	return unless (defined $project);
-	# some views should link to OPML, or to generic project feed,
-	# or don't have specific feed yet (so they should use generic)
-	return if ($action =~ /^(?:tags|heads|forks|tag|search)$/x);
-
-	my $branch;
-	# branches refs uses 'refs/heads/' prefix (fullname) to differentiate
-	# from tag links; this also makes possible to detect branch links
-	if ((defined $hash_base && $hash_base =~ m!^refs/heads/(.*)$!) ||
-	    (defined $hash      && $hash      =~ m!^refs/heads/(.*)$!)) {
-		$branch = $1;
-	}
-	# find log type for feed description (title)
-	my $type = 'log';
-	if (defined $file_name) {
-		$type  = "history of $file_name";
-		$type .= "/" if ($action eq 'tree');
-		$type .= " on '$branch'" if (defined $branch);
-	} else {
-		$type = "log of $branch" if (defined $branch);
-	}
-
-	$res{-title} = $type;
-	$res{'hash'} = (defined $branch ? "refs/heads/$branch" : undef);
-	$res{'file_name'} = $file_name;
-
-	return %res;
-}
-
 ## ----------------------------------------------------------------------
 ## git utility subroutines, invoking git commands
 
@@ -2089,18 +1715,6 @@ sub git_get_tags_list {
 	return wantarray ? @tagslist : \@tagslist;
 }
 
-## ----------------------------------------------------------------------
-## filesystem-related functions
-
-# assume that file exists
-sub insert_file {
-	my $filename = shift;
-
-	open my $fd, '<', $filename;
-	print map { to_utf8($_) } <$fd>;
-	close $fd;
-}
-
 ## ......................................................................
 ## mimetype related functions
 
@@ -2237,405 +1851,9 @@ sub run_highlighter {
 	return $fd;
 }
 
-## ======================================================================
-## functions printing HTML: header, footer, error page
-
-sub get_page_title {
-	my $title = to_utf8($site_name);
-
-	return $title unless (defined $project);
-	$title .= " - " . to_utf8($project);
-
-	return $title unless (defined $action);
-	$title .= "/$action"; # $action is US-ASCII (7bit ASCII)
-
-	return $title unless (defined $file_name);
-	$title .= " - " . esc_path($file_name);
-	if ($action eq "tree" && $file_name !~ m|/$|) {
-		$title .= "/";
-	}
-
-	return $title;
-}
-
-sub git_header_html {
-	my $status = shift || "200 OK";
-	my $expires = shift;
-	my %opts = @_;
-
-	my $title = get_page_title();
-	my $content_type;
-	# require explicit support from the UA if we are to send the page as
-	# 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
-	# we have to do this because MSIE sometimes globs '*/*', pretending to
-	# support xhtml+xml but choking when it gets what it asked for.
-	if (defined $cgi->http('HTTP_ACCEPT') &&
-	    $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
-	    $cgi->Accept('application/xhtml+xml') != 0) {
-		$content_type = 'application/xhtml+xml';
-	} else {
-		$content_type = 'text/html';
-	}
-	print $cgi->header(-type=>$content_type, -charset => 'utf-8',
-	                   -status=> $status, -expires => $expires)
-		unless ($opts{'-no_http_header'});
-	my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
-	print <<EOF;
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
-<!-- git web interface version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
-<!-- git core binaries version $git_version -->
-<head>
-<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
-<meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/>
-<meta name="robots" content="index, nofollow"/>
-<title>$title</title>
-EOF
-	# the stylesheet, favicon etc urls won't work correctly with path_info
-	# unless we set the appropriate base URL
-	if ($ENV{'PATH_INFO'}) {
-		print "<base href=\"".esc_url($base_url)."\" />\n";
-	}
-	# print out each stylesheet that exist, providing backwards capability
-	# for those people who defined $stylesheet in a config file
-	if (defined $stylesheet) {
-		print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
-	} else {
-		foreach my $stylesheet (@stylesheets) {
-			next unless $stylesheet;
-			print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
-		}
-	}
-	if (defined $project) {
-		my %href_params = get_feed_info();
-		if (!exists $href_params{'-title'}) {
-			$href_params{'-title'} = 'log';
-		}
-
-		foreach my $format qw(RSS Atom) {
-			my $type = lc($format);
-			my %link_attr = (
-				'-rel' => 'alternate',
-				'-title' => "$project - $href_params{'-title'} - $format feed",
-				'-type' => "application/$type+xml"
-			);
-
-			$href_params{'action'} = $type;
-			$link_attr{'-href'} = href(%href_params);
-			print "<link ".
-			      "rel=\"$link_attr{'-rel'}\" ".
-			      "title=\"$link_attr{'-title'}\" ".
-			      "href=\"$link_attr{'-href'}\" ".
-			      "type=\"$link_attr{'-type'}\" ".
-			      "/>\n";
-
-			$href_params{'extra_options'} = '--no-merges';
-			$link_attr{'-href'} = href(%href_params);
-			$link_attr{'-title'} .= ' (no merges)';
-			print "<link ".
-			      "rel=\"$link_attr{'-rel'}\" ".
-			      "title=\"$link_attr{'-title'}\" ".
-			      "href=\"$link_attr{'-href'}\" ".
-			      "type=\"$link_attr{'-type'}\" ".
-			      "/>\n";
-		}
-
-	} else {
-		printf('<link rel="alternate" title="%s projects list" '.
-		       'href="%s" type="text/plain; charset=utf-8" />'."\n",
-		       $site_name, href(project=>undef, action=>"project_index"));
-		printf('<link rel="alternate" title="%s projects feeds" '.
-		       'href="%s" type="text/x-opml" />'."\n",
-		       $site_name, href(project=>undef, action=>"opml"));
-	}
-	if (defined $favicon) {
-		print qq(<link rel="shortcut icon" href="$favicon" type="image/png" />\n);
-	}
-
-	print "</head>\n" .
-	      "<body>\n";
-
-	if (defined $site_header && -f $site_header) {
-		insert_file($site_header);
-	}
-
-	print "<div class=\"page_header\">\n" .
-	      $cgi->a({-href => esc_url($logo_url),
-	               -title => $logo_label},
-	              qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
-	print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
-	if (defined $project) {
-		print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
-		if (defined $action) {
-			print " / $action";
-		}
-		print "\n";
-	}
-	print "</div>\n";
-
-	my $have_search = gitweb_check_feature('search');
-	if (defined $project && $have_search) {
-		if (!defined $searchtext) {
-			$searchtext = "";
-		}
-		my $search_hash;
-		if (defined $hash_base) {
-			$search_hash = $hash_base;
-		} elsif (defined $hash) {
-			$search_hash = $hash;
-		} else {
-			$search_hash = "HEAD";
-		}
-		my $action = $my_uri;
-		my $use_pathinfo = gitweb_check_feature('pathinfo');
-		if ($use_pathinfo) {
-			$action .= "/".esc_url($project);
-		}
-		print $cgi->startform(-method => "get", -action => $action) .
-		      "<div class=\"search\">\n" .
-		      (!$use_pathinfo &&
-		      $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
-		      $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
-		      $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
-		      $cgi->popup_menu(-name => 'st', -default => 'commit',
-		                       -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
-		      $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
-		      " search:\n",
-		      $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
-		      "<span title=\"Extended regular expression\">" .
-		      $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
-		                     -checked => $search_use_regexp) .
-		      "</span>" .
-		      "</div>" .
-		      $cgi->end_form() . "\n";
-	}
-}
-
-sub git_footer_html {
-	my $feed_class = 'rss_logo';
-
-	print "<div class=\"page_footer\">\n";
-	if (defined $project) {
-		my $descr = git_get_project_description($project);
-		if (defined $descr) {
-			print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
-		}
-
-		my %href_params = get_feed_info();
-		if (!%href_params) {
-			$feed_class .= ' generic';
-		}
-		$href_params{'-title'} ||= 'log';
-
-		foreach my $format qw(RSS Atom) {
-			$href_params{'action'} = lc($format);
-			print $cgi->a({-href => href(%href_params),
-			              -title => "$href_params{'-title'} $format feed",
-			              -class => $feed_class}, $format)."\n";
-		}
-
-	} else {
-		print $cgi->a({-href => href(project=>undef, action=>"opml"),
-		              -class => $feed_class}, "OPML") . " ";
-		print $cgi->a({-href => href(project=>undef, action=>"project_index"),
-		              -class => $feed_class}, "TXT") . "\n";
-	}
-	print "</div>\n"; # class="page_footer"
-
-	if (defined $t0 && gitweb_check_feature('timed')) {
-		print "<div id=\"generating_info\">\n";
-		print 'This page took '.
-		      '<span id="generating_time" class="time_span">'.
-		      Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
-		      ' seconds </span>'.
-		      ' and '.
-		      '<span id="generating_cmd">'.
-		      $number_of_git_cmds.
-		      '</span> git commands '.
-		      " to generate.\n";
-		print "</div>\n"; # class="page_footer"
-	}
-
-	if (defined $site_footer && -f $site_footer) {
-		insert_file($site_footer);
-	}
-
-	print qq!<script type="text/javascript" src="$javascript"></script>\n!;
-	if (defined $action &&
-	    $action eq 'blame_incremental') {
-		print qq!<script type="text/javascript">\n!.
-		      qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
-		      qq!           "!. href() .qq!");\n!.
-		      qq!</script>\n!;
-	} elsif (gitweb_check_feature('javascript-actions')) {
-		print qq!<script type="text/javascript">\n!.
-		      qq!window.onload = fixLinks;\n!.
-		      qq!</script>\n!;
-	}
-
-	print "</body>\n" .
-	      "</html>";
-}
-
-# die_error(<http_status_code>, <error_message>[, <detailed_html_description>])
-# Example: die_error(404, 'Hash not found')
-# By convention, use the following status codes (as defined in RFC 2616):
-# 400: Invalid or missing CGI parameters, or
-#      requested object exists but has wrong type.
-# 403: Requested feature (like "pickaxe" or "snapshot") not enabled on
-#      this server or project.
-# 404: Requested object/revision/project doesn't exist.
-# 500: The server isn't configured properly, or
-#      an internal error occurred (e.g. failed assertions caused by bugs), or
-#      an unknown error occurred (e.g. the git binary died unexpectedly).
-# 503: The server is currently unavailable (because it is overloaded,
-#      or down for maintenance).  Generally, this is a temporary state.
-sub die_error {
-	my $status = shift || 500;
-	my $error = esc_html(shift) || "Internal Server Error";
-	my $extra = shift;
-	my %opts = @_;
-
-	my %http_responses = (
-		400 => '400 Bad Request',
-		403 => '403 Forbidden',
-		404 => '404 Not Found',
-		500 => '500 Internal Server Error',
-		503 => '503 Service Unavailable',
-	);
-	git_header_html($http_responses{$status}, undef, %opts);
-	print <<EOF;
-<div class="page_body">
-<br /><br />
-$status - $error
-<br />
-EOF
-	if (defined $extra) {
-		print "<hr />\n" .
-		      "$extra\n";
-	}
-	print "</div>\n";
-
-	git_footer_html();
-	goto DONE_GITWEB
-		unless ($opts{'-error_handler'});
-}
-
-## ----------------------------------------------------------------------
-## functions printing or outputting HTML: navigation
-
-sub git_print_page_nav {
-	my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_;
-	$extra = '' if !defined $extra; # pager or formats
-
-	my @navs = qw(summary shortlog log commit commitdiff tree);
-	if ($suppress) {
-		@navs = grep { $_ ne $suppress } @navs;
-	}
-
-	my %arg = map { $_ => {action=>$_} } @navs;
-	if (defined $head) {
-		for (qw(commit commitdiff)) {
-			$arg{$_}{'hash'} = $head;
-		}
-		if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
-			for (qw(shortlog log)) {
-				$arg{$_}{'hash'} = $head;
-			}
-		}
-	}
-
-	$arg{'tree'}{'hash'} = $treehead if defined $treehead;
-	$arg{'tree'}{'hash_base'} = $treebase if defined $treebase;
-
-	my @actions = gitweb_get_feature('actions');
-	my %repl = (
-		'%' => '%',
-		'n' => $project,         # project name
-		'f' => $git_dir,         # project path within filesystem
-		'h' => $treehead || '',  # current hash ('h' parameter)
-		'b' => $treebase || '',  # hash base ('hb' parameter)
-	);
-	while (@actions) {
-		my ($label, $link, $pos) = splice(@actions,0,3);
-		# insert
-		@navs = map { $_ eq $pos ? ($_, $label) : $_ } @navs;
-		# munch munch
-		$link =~ s/%([%nfhb])/$repl{$1}/g;
-		$arg{$label}{'_href'} = $link;
-	}
-
-	print "<div class=\"page_nav\">\n" .
-		(join " | ",
-		 map { $_ eq $current ?
-		       $_ : $cgi->a({-href => ($arg{$_}{_href} ? $arg{$_}{_href} : href(%{$arg{$_}}))}, "$_")
-		 } @navs);
-	print "<br/>\n$extra<br/>\n" .
-	      "</div>\n";
-}
-
-sub format_paging_nav {
-	my ($action, $page, $has_next_link) = @_;
-	my $paging_nav;
-
-
-	if ($page > 0) {
-		$paging_nav .=
-			$cgi->a({-href => href(-replay=>1, page=>undef)}, "first") .
-			" &sdot; " .
-			$cgi->a({-href => href(-replay=>1, page=>$page-1),
-			         -accesskey => "p", -title => "Alt-p"}, "prev");
-	} else {
-		$paging_nav .= "first &sdot; prev";
-	}
-
-	if ($has_next_link) {
-		$paging_nav .= " &sdot; " .
-			$cgi->a({-href => href(-replay=>1, page=>$page+1),
-			         -accesskey => "n", -title => "Alt-n"}, "next");
-	} else {
-		$paging_nav .= " &sdot; next";
-	}
-
-	return $paging_nav;
-}
-
 ## ......................................................................
 ## functions printing or outputting HTML: div
 
-sub git_print_header_div {
-	my ($action, $title, $hash, $hash_base) = @_;
-	my %args = ();
-
-	$args{'action'} = $action;
-	$args{'hash'} = $hash if $hash;
-	$args{'hash_base'} = $hash_base if $hash_base;
-
-	print "<div class=\"header\">\n" .
-	      $cgi->a({-href => href(%args), -class => "title"},
-	      $title ? $title : $action) .
-	      "\n</div>\n";
-}
-
-sub print_local_time {
-	print format_local_time(@_);
-}
-
-sub format_local_time {
-	my $localtime = '';
-	my %date = @_;
-	if ($date{'hour_local'} < 6) {
-		$localtime .= sprintf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
-			$date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
-	} else {
-		$localtime .= sprintf(" (%02d:%02d %s)",
-			$date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
-	}
-
-	return $localtime;
-}
-
 # Outputs the author name and date in long form
 sub git_print_authorship {
 	my $co = shift;
@@ -2680,44 +1898,6 @@ sub git_print_authorship_rows {
 	}
 }
 
-sub git_print_page_path {
-	my $name = shift;
-	my $type = shift;
-	my $hb = shift;
-
-
-	print "<div class=\"page_path\">";
-	print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
-	              -title => 'tree root'}, to_utf8("[$project]"));
-	print " / ";
-	if (defined $name) {
-		my @dirname = split '/', $name;
-		my $basename = pop @dirname;
-		my $fullname = '';
-
-		foreach my $dir (@dirname) {
-			$fullname .= ($fullname ? '/' : '') . $dir;
-			print $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
-			                             hash_base=>$hb),
-			              -title => $fullname}, esc_path($dir));
-			print " / ";
-		}
-		if (defined $type && $type eq 'blob') {
-			print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name,
-			                             hash_base=>$hb),
-			              -title => $name}, esc_path($basename));
-		} elsif (defined $type && $type eq 'tree') {
-			print $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
-			                             hash_base=>$hb),
-			              -title => $name}, esc_path($basename));
-			print " / ";
-		} else {
-			print esc_path($basename);
-		}
-	}
-	print "<br/></div>\n";
-}
-
 sub git_print_log {
 	my $log = shift;
 	my %opts = @_;
@@ -2767,157 +1947,6 @@ sub git_print_log {
 	}
 }
 
-# return link target (what link points to)
-sub git_get_link_target {
-	my $hash = shift;
-	my $link_target;
-
-	# read link
-	open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
-		or return;
-	{
-		local $/ = undef;
-		$link_target = <$fd>;
-	}
-	close $fd
-		or return;
-
-	return $link_target;
-}
-
-# given link target, and the directory (basedir) the link is in,
-# return target of link relative to top directory (top tree);
-# return undef if it is not possible (including absolute links).
-sub normalize_link_target {
-	my ($link_target, $basedir) = @_;
-
-	# absolute symlinks (beginning with '/') cannot be normalized
-	return if (substr($link_target, 0, 1) eq '/');
-
-	# normalize link target to path from top (root) tree (dir)
-	my $path;
-	if ($basedir) {
-		$path = $basedir . '/' . $link_target;
-	} else {
-		# we are in top (root) tree (dir)
-		$path = $link_target;
-	}
-
-	# remove //, /./, and /../
-	my @path_parts;
-	foreach my $part (split('/', $path)) {
-		# discard '.' and ''
-		next if (!$part || $part eq '.');
-		# handle '..'
-		if ($part eq '..') {
-			if (@path_parts) {
-				pop @path_parts;
-			} else {
-				# link leads outside repository (outside top dir)
-				return;
-			}
-		} else {
-			push @path_parts, $part;
-		}
-	}
-	$path = join('/', @path_parts);
-
-	return $path;
-}
-
-# print tree entry (row of git_tree), but without encompassing <tr> element
-sub git_print_tree_entry {
-	my ($t, $basedir, $hash_base, $have_blame) = @_;
-
-	my %base_key = ();
-	$base_key{'hash_base'} = $hash_base if defined $hash_base;
-
-	# The format of a table row is: mode list link.  Where mode is
-	# the mode of the entry, list is the name of the entry, an href,
-	# and link is the action links of the entry.
-
-	print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n";
-	if (exists $t->{'size'}) {
-		print "<td class=\"size\">$t->{'size'}</td>\n";
-	}
-	if ($t->{'type'} eq "blob") {
-		print "<td class=\"list\">" .
-			$cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
-			                       file_name=>"$basedir$t->{'name'}", %base_key),
-			        -class => "list"}, esc_path($t->{'name'}));
-		if (S_ISLNK(oct $t->{'mode'})) {
-			my $link_target = git_get_link_target($t->{'hash'});
-			if ($link_target) {
-				my $norm_target = normalize_link_target($link_target, $basedir);
-				if (defined $norm_target) {
-					print " -> " .
-					      $cgi->a({-href => href(action=>"object", hash_base=>$hash_base,
-					                             file_name=>$norm_target),
-					               -title => $norm_target}, esc_path($link_target));
-				} else {
-					print " -> " . esc_path($link_target);
-				}
-			}
-		}
-		print "</td>\n";
-		print "<td class=\"link\">";
-		print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
-		                             file_name=>"$basedir$t->{'name'}", %base_key)},
-		              "blob");
-		if ($have_blame) {
-			print " | " .
-			      $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
-			                             file_name=>"$basedir$t->{'name'}", %base_key)},
-			              "blame");
-		}
-		if (defined $hash_base) {
-			print " | " .
-			      $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
-			                             hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
-			              "history");
-		}
-		print " | " .
-			$cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base,
-			                       file_name=>"$basedir$t->{'name'}")},
-			        "raw");
-		print "</td>\n";
-
-	} elsif ($t->{'type'} eq "tree") {
-		print "<td class=\"list\">";
-		print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
-		                             file_name=>"$basedir$t->{'name'}",
-		                             %base_key)},
-		              esc_path($t->{'name'}));
-		print "</td>\n";
-		print "<td class=\"link\">";
-		print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
-		                             file_name=>"$basedir$t->{'name'}",
-		                             %base_key)},
-		              "tree");
-		if (defined $hash_base) {
-			print " | " .
-			      $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
-			                             file_name=>"$basedir$t->{'name'}")},
-			              "history");
-		}
-		print "</td>\n";
-	} else {
-		# unknown object: we can only present history for it
-		# (this includes 'commit' object, i.e. submodule support)
-		print "<td class=\"list\">" .
-		      esc_path($t->{'name'}) .
-		      "</td>\n";
-		print "<td class=\"link\">";
-		if (defined $hash_base) {
-			print $cgi->a({-href => href(action=>"history",
-			                             hash_base=>$hash_base,
-			                             file_name=>"$basedir$t->{'name'}")},
-			              "history");
-		}
-		print "</td>\n";
-	}
-}
-
 ## ......................................................................
 ## functions printing large fragments of HTML
 
@@ -3454,29 +2483,6 @@ sub fill_project_list_info {
 	return @projects;
 }
 
-# print 'sort by' <th> element, generating 'sort by $name' replay link
-# if that order is not selected
-sub print_sort_th {
-	print format_sort_th(@_);
-}
-
-sub format_sort_th {
-	my ($name, $order, $header) = @_;
-	my $sort_th = "";
-	$header ||= ucfirst($name);
-
-	if ($order eq $name) {
-		$sort_th .= "<th>$header</th>\n";
-	} else {
-		$sort_th .= "<th>" .
-		            $cgi->a({-href => href(-replay=>1, order=>$name),
-		                     -class => "header"}, $header) .
-		            "</th>\n";
-	}
-
-	return $sort_th;
-}
-
 sub git_project_list_body {
 	# actually uses global variable $project
 	my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
diff --git a/gitweb/lib/Gitweb/View.pm b/gitweb/lib/Gitweb/View.pm
new file mode 100644
index 0000000..3118f44
--- /dev/null
+++ b/gitweb/lib/Gitweb/View.pm
@@ -0,0 +1,1022 @@
+#!/usr/bin/perl
+#
+# Gitweb::View-- gitweb's viewing subs package
+#
+# This program is licensed under the GPLv2
+
+package Gitweb::View;
+
+use strict;
+use warnings;
+use Exporter qw(import);
+
+our @EXPORT = qw(href chop_str chop_and_escape_str age_class age_string mode_str
+                 file_type file_type_long git_header_html git_footer_html die_error
+                 git_print_page_nav format_paging_nav insert_file git_print_page_path
+                 git_print_header_div print_local_time format_local_time print_sort_th
+                 git_print_tree_entry git_get_link_target normalize_link_target
+                 format_sort_th);
+
+use Fcntl ':mode';
+use Gitweb::Git qw($git_version $number_of_git_cmds $git_dir git_cmd);
+use Gitweb::Config qw(gitweb_check_feature %known_snapshot_formats @snapshot_fmts
+                      $site_name $version $stylesheet @stylesheets $favicon $logo
+                      $site_header $logo_url $logo_label $javascript $home_link_str
+                      $site_footer gitweb_get_feature);
+use Gitweb::Request qw($cgi $project %cgi_param_mapping @cgi_param_mapping $my_url
+                       $my_uri %input_params $action $file_name $base_url $hash_base
+                       $searchtext $search_use_regexp $hash $t0 $home_link);
+use Gitweb::Escape;
+use Gitweb::RepoConfig qw(git_get_project_description);
+
+# possible values of extra options
+# -full => 0|1      - use absolute/full URL ($my_uri/$my_url as base)
+# -replay => 1      - start from a current view (replay with modifications)
+# -path_info => 0|1 - don't use/use path_info URL (if possible)
+sub href {
+	my %params = @_;
+	# default is to use -absolute url() i.e. $my_uri
+	my $href = $params{-full} ? $my_url : $my_uri;
+
+	$params{'project'} = $project unless exists $params{'project'};
+
+	if ($params{-replay}) {
+		while (my ($name, $symbol) = each %cgi_param_mapping) {
+			if (!exists $params{$name}) {
+				$params{$name} = $input_params{$name};
+			}
+		}
+	}
+
+	my $use_pathinfo = gitweb_check_feature('pathinfo');
+	if (defined $params{'project'} &&
+	    (exists $params{-path_info} ? $params{-path_info} : $use_pathinfo)) {
+		# try to put as many parameters as possible in PATH_INFO:
+		#   - project name
+		#   - action
+		#   - hash_parent or hash_parent_base:/file_parent
+		#   - hash or hash_base:/filename
+		#   - the snapshot_format as an appropriate suffix
+
+		# When the script is the root DirectoryIndex for the domain,
+		# $href here would be something like http://gitweb.example.com/
+		# Thus, we strip any trailing / from $href, to spare us double
+		# slashes in the final URL
+		$href =~ s,/$,,;
+
+		# Then add the project name, if present
+		$href .= "/".esc_url($params{'project'});
+		delete $params{'project'};
+
+		# since we destructively absorb parameters, we keep this
+		# boolean that remembers if we're handling a snapshot
+		my $is_snapshot = $params{'action'} eq 'snapshot';
+
+		# Summary just uses the project path URL, any other action is
+		# added to the URL
+		if (defined $params{'action'}) {
+			$href .= "/".esc_url($params{'action'}) unless $params{'action'} eq 'summary';
+			delete $params{'action'};
+		}
+
+		# Next, we put hash_parent_base:/file_parent..hash_base:/file_name,
+		# stripping nonexistent or useless pieces
+		$href .= "/" if ($params{'hash_base'} || $params{'hash_parent_base'}
+			|| $params{'hash_parent'} || $params{'hash'});
+		if (defined $params{'hash_base'}) {
+			if (defined $params{'hash_parent_base'}) {
+				$href .= esc_url($params{'hash_parent_base'});
+				# skip the file_parent if it's the same as the file_name
+				if (defined $params{'file_parent'}) {
+					if (defined $params{'file_name'} && $params{'file_parent'} eq $params{'file_name'}) {
+						delete $params{'file_parent'};
+					} elsif ($params{'file_parent'} !~ /\.\./) {
+						$href .= ":/".esc_url($params{'file_parent'});
+						delete $params{'file_parent'};
+					}
+				}
+				$href .= "..";
+				delete $params{'hash_parent'};
+				delete $params{'hash_parent_base'};
+			} elsif (defined $params{'hash_parent'}) {
+				$href .= esc_url($params{'hash_parent'}). "..";
+				delete $params{'hash_parent'};
+			}
+
+			$href .= esc_url($params{'hash_base'});
+			if (defined $params{'file_name'} && $params{'file_name'} !~ /\.\./) {
+				$href .= ":/".esc_url($params{'file_name'});
+				delete $params{'file_name'};
+			}
+			delete $params{'hash'};
+			delete $params{'hash_base'};
+		} elsif (defined $params{'hash'}) {
+			$href .= esc_url($params{'hash'});
+			delete $params{'hash'};
+		}
+
+		# If the action was a snapshot, we can absorb the
+		# snapshot_format parameter too
+		if ($is_snapshot) {
+			my $fmt = $params{'snapshot_format'};
+			# snapshot_format should always be defined when href()
+			# is called, but just in case some code forgets, we
+			# fall back to the default
+			$fmt ||= $snapshot_fmts[0];
+			$href .= $known_snapshot_formats{$fmt}{'suffix'};
+			delete $params{'snapshot_format'};
+		}
+	}
+
+	# now encode the parameters explicitly
+	my @result = ();
+	for (my $i = 0; $i < @cgi_param_mapping; $i += 2) {
+		my ($name, $symbol) = ($cgi_param_mapping[$i], $cgi_param_mapping[$i+1]);
+		if (defined $params{$name}) {
+			if (ref($params{$name}) eq "ARRAY") {
+				foreach my $par (@{$params{$name}}) {
+					push @result, $symbol . "=" . esc_param($par);
+				}
+			} else {
+				push @result, $symbol . "=" . esc_param($params{$name});
+			}
+		}
+	}
+	$href .= "?" . join(';', @result) if scalar @result;
+
+	return $href;
+}
+
+## ----------------------------------------------------------------------
+## HTML aware string manipulation
+
+# Try to chop given string on a word boundary between position
+# $len and $len+$add_len. If there is no word boundary there,
+# chop at $len+$add_len. Do not chop if chopped part plus ellipsis
+# (marking chopped part) would be longer than given string.
+sub chop_str {
+	my $str = shift;
+	my $len = shift;
+	my $add_len = shift || 10;
+	my $where = shift || 'right'; # 'left' | 'center' | 'right'
+
+	# Make sure perl knows it is utf8 encoded so we don't
+	# cut in the middle of a utf8 multibyte char.
+	$str = to_utf8($str);
+
+	# allow only $len chars, but don't cut a word if it would fit in $add_len
+	# if it doesn't fit, cut it if it's still longer than the dots we would add
+	# remove chopped character entities entirely
+
+	# when chopping in the middle, distribute $len into left and right part
+	# return early if chopping wouldn't make string shorter
+	if ($where eq 'center') {
+		return $str if ($len + 5 >= length($str)); # filler is length 5
+		$len = int($len/2);
+	} else {
+		return $str if ($len + 4 >= length($str)); # filler is length 4
+	}
+
+	# regexps: ending and beginning with word part up to $add_len
+	my $endre = qr/.{$len}\w{0,$add_len}/;
+	my $begre = qr/\w{0,$add_len}.{$len}/;
+
+	if ($where eq 'left') {
+		$str =~ m/^(.*?)($begre)$/;
+		my ($lead, $body) = ($1, $2);
+		if (length($lead) > 4) {
+			$lead = " ...";
+		}
+		return "$lead$body";
+
+	} elsif ($where eq 'center') {
+		$str =~ m/^($endre)(.*)$/;
+		my ($left, $str)  = ($1, $2);
+		$str =~ m/^(.*?)($begre)$/;
+		my ($mid, $right) = ($1, $2);
+		if (length($mid) > 5) {
+			$mid = " ... ";
+		}
+		return "$left$mid$right";
+
+	} else {
+		$str =~ m/^($endre)(.*)$/;
+		my $body = $1;
+		my $tail = $2;
+		if (length($tail) > 4) {
+			$tail = "... ";
+		}
+		return "$body$tail";
+	}
+}
+
+# takes the same arguments as chop_str, but also wraps a <span> around the
+# result with a title attribute if it does get chopped. Additionally, the
+# string is HTML-escaped.
+sub chop_and_escape_str {
+	my ($str) = @_;
+
+	my $chopped = chop_str(@_);
+	if ($chopped eq $str) {
+		return esc_html($chopped);
+	} else {
+		$str =~ s/[[:cntrl:]]/?/g;
+		return $cgi->span({-title=>$str}, esc_html($chopped));
+	}
+}
+
+## ----------------------------------------------------------------------
+## functions returning short strings
+
+# CSS class for given age value (in seconds)
+sub age_class {
+	my $age = shift;
+
+	if (!defined $age) {
+		return "noage";
+	} elsif ($age < 60*60*2) {
+		return "age0";
+	} elsif ($age < 60*60*24*2) {
+		return "age1";
+	} else {
+		return "age2";
+	}
+}
+
+# convert age in seconds to "nn units ago" string
+sub age_string {
+	my $age = shift;
+	my $age_str;
+
+	if ($age > 60*60*24*365*2) {
+		$age_str = (int $age/60/60/24/365);
+		$age_str .= " years ago";
+	} elsif ($age > 60*60*24*(365/12)*2) {
+		$age_str = int $age/60/60/24/(365/12);
+		$age_str .= " months ago";
+	} elsif ($age > 60*60*24*7*2) {
+		$age_str = int $age/60/60/24/7;
+		$age_str .= " weeks ago";
+	} elsif ($age > 60*60*24*2) {
+		$age_str = int $age/60/60/24;
+		$age_str .= " days ago";
+	} elsif ($age > 60*60*2) {
+		$age_str = int $age/60/60;
+		$age_str .= " hours ago";
+	} elsif ($age > 60*2) {
+		$age_str = int $age/60;
+		$age_str .= " min ago";
+	} elsif ($age > 2) {
+		$age_str = int $age;
+		$age_str .= " sec ago";
+	} else {
+		$age_str .= " right now";
+	}
+	return $age_str;
+}
+
+use constant {
+	S_IFINVALID => 0030000,
+	S_IFGITLINK => 0160000,
+};
+
+# submodule/subproject, a commit object reference
+sub S_ISGITLINK {
+	my $mode = shift;
+
+	return (($mode & S_IFMT) == S_IFGITLINK)
+}
+
+# convert file mode in octal to symbolic file mode string
+sub mode_str {
+	my $mode = oct shift;
+
+	if (S_ISGITLINK($mode)) {
+		return 'm---------';
+	} elsif (S_ISDIR($mode & S_IFMT)) {
+		return 'drwxr-xr-x';
+	} elsif (S_ISLNK($mode)) {
+		return 'lrwxrwxrwx';
+	} elsif (S_ISREG($mode)) {
+		# git cares only about the executable bit
+		if ($mode & S_IXUSR) {
+			return '-rwxr-xr-x';
+		} else {
+			return '-rw-r--r--';
+		};
+	} else {
+		return '----------';
+	}
+}
+
+# convert file mode in octal to file type string
+sub file_type {
+	my $mode = shift;
+
+	if ($mode !~ m/^[0-7]+$/) {
+		return $mode;
+	} else {
+		$mode = oct $mode;
+	}
+
+	if (S_ISGITLINK($mode)) {
+		return "submodule";
+	} elsif (S_ISDIR($mode & S_IFMT)) {
+		return "directory";
+	} elsif (S_ISLNK($mode)) {
+		return "symlink";
+	} elsif (S_ISREG($mode)) {
+		return "file";
+	} else {
+		return "unknown";
+	}
+}
+
+# convert file mode in octal to file type description string
+sub file_type_long {
+	my $mode = shift;
+
+	if ($mode !~ m/^[0-7]+$/) {
+		return $mode;
+	} else {
+		$mode = oct $mode;
+	}
+
+	if (S_ISGITLINK($mode)) {
+		return "submodule";
+	} elsif (S_ISDIR($mode & S_IFMT)) {
+		return "directory";
+	} elsif (S_ISLNK($mode)) {
+		return "symlink";
+	} elsif (S_ISREG($mode)) {
+		if ($mode & S_IXUSR) {
+			return "executable";
+		} else {
+			return "file";
+		};
+	} else {
+		return "unknown";
+	}
+}
+
+## ======================================================================
+## functions printing HTML: header, footer, error page
+
+sub get_page_title {
+	my $title = to_utf8($site_name);
+
+	return $title unless (defined $project);
+	$title .= " - " . to_utf8($project);
+
+	return $title unless (defined $action);
+	$title .= "/$action"; # $action is US-ASCII (7bit ASCII)
+
+	return $title unless (defined $file_name);
+	$title .= " - " . esc_path($file_name);
+	if ($action eq "tree" && $file_name !~ m|/$|) {
+		$title .= "/";
+	}
+
+	return $title;
+}
+
+sub git_header_html {
+	my $status = shift || "200 OK";
+	my $expires = shift;
+	my %opts = @_;
+
+	my $title = get_page_title();
+	my $content_type;
+	# require explicit support from the UA if we are to send the page as
+	# 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
+	# we have to do this because MSIE sometimes globs '*/*', pretending to
+	# support xhtml+xml but choking when it gets what it asked for.
+	if (defined $cgi->http('HTTP_ACCEPT') &&
+	    $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
+	    $cgi->Accept('application/xhtml+xml') != 0) {
+		$content_type = 'application/xhtml+xml';
+	} else {
+		$content_type = 'text/html';
+	}
+	print $cgi->header(-type=>$content_type, -charset => 'utf-8',
+	                   -status=> $status, -expires => $expires)
+		unless ($opts{'-no_http_header'});
+	my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
+	print <<EOF;
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
+<!-- git web interface version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
+<!-- git core binaries version $git_version -->
+<head>
+<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
+<meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/>
+<meta name="robots" content="index, nofollow"/>
+<title>$title</title>
+EOF
+	# the stylesheet, favicon etc urls won't work correctly with path_info
+	# unless we set the appropriate base URL
+	if ($ENV{'PATH_INFO'}) {
+		print "<base href=\"".esc_url($base_url)."\" />\n";
+	}
+	# print out each stylesheet that exist, providing backwards capability
+	# for those people who defined $stylesheet in a config file
+	if (defined $stylesheet) {
+		print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+	} else {
+		foreach my $stylesheet (@stylesheets) {
+			next unless $stylesheet;
+			print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+		}
+	}
+	if (defined $project) {
+		my %href_params = get_feed_info();
+		if (!exists $href_params{'-title'}) {
+			$href_params{'-title'} = 'log';
+		}
+
+		foreach my $format qw(RSS Atom) {
+			my $type = lc($format);
+			my %link_attr = (
+				'-rel' => 'alternate',
+				'-title' => "$project - $href_params{'-title'} - $format feed",
+				'-type' => "application/$type+xml"
+			);
+
+			$href_params{'action'} = $type;
+			$link_attr{'-href'} = href(%href_params);
+			print "<link ".
+			      "rel=\"$link_attr{'-rel'}\" ".
+			      "title=\"$link_attr{'-title'}\" ".
+			      "href=\"$link_attr{'-href'}\" ".
+			      "type=\"$link_attr{'-type'}\" ".
+			      "/>\n";
+
+			$href_params{'extra_options'} = '--no-merges';
+			$link_attr{'-href'} = href(%href_params);
+			$link_attr{'-title'} .= ' (no merges)';
+			print "<link ".
+			      "rel=\"$link_attr{'-rel'}\" ".
+			      "title=\"$link_attr{'-title'}\" ".
+			      "href=\"$link_attr{'-href'}\" ".
+			      "type=\"$link_attr{'-type'}\" ".
+			      "/>\n";
+		}
+
+	} else {
+		printf('<link rel="alternate" title="%s projects list" '.
+		       'href="%s" type="text/plain; charset=utf-8" />'."\n",
+		       $site_name, href(project=>undef, action=>"project_index"));
+		printf('<link rel="alternate" title="%s projects feeds" '.
+		       'href="%s" type="text/x-opml" />'."\n",
+		       $site_name, href(project=>undef, action=>"opml"));
+	}
+	if (defined $favicon) {
+		print qq(<link rel="shortcut icon" href="$favicon" type="image/png" />\n);
+	}
+
+	print "</head>\n" .
+	      "<body>\n";
+
+	if (defined $site_header && -f $site_header) {
+		insert_file($site_header);
+	}
+
+	print "<div class=\"page_header\">\n" .
+	      $cgi->a({-href => esc_url($logo_url),
+	               -title => $logo_label},
+	              qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
+	print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
+	if (defined $project) {
+		print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
+		if (defined $action) {
+			print " / $action";
+		}
+		print "\n";
+	}
+	print "</div>\n";
+
+	my $have_search = gitweb_check_feature('search');
+	if (defined $project && $have_search) {
+		if (!defined $searchtext) {
+			$searchtext = "";
+		}
+		my $search_hash;
+		if (defined $hash_base) {
+			$search_hash = $hash_base;
+		} elsif (defined $hash) {
+			$search_hash = $hash;
+		} else {
+			$search_hash = "HEAD";
+		}
+		my $action = $my_uri;
+		my $use_pathinfo = gitweb_check_feature('pathinfo');
+		if ($use_pathinfo) {
+			$action .= "/".esc_url($project);
+		}
+		print $cgi->startform(-method => "get", -action => $action) .
+		      "<div class=\"search\">\n" .
+		      (!$use_pathinfo &&
+		      $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
+		      $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
+		      $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
+		      $cgi->popup_menu(-name => 'st', -default => 'commit',
+		                       -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
+		      $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
+		      " search:\n",
+		      $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
+		      "<span title=\"Extended regular expression\">" .
+		      $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
+		                     -checked => $search_use_regexp) .
+		      "</span>" .
+		      "</div>" .
+		      $cgi->end_form() . "\n";
+	}
+}
+
+sub git_footer_html {
+	my $feed_class = 'rss_logo';
+
+	print "<div class=\"page_footer\">\n";
+	if (defined $project) {
+		my $descr = git_get_project_description($project);
+		if (defined $descr) {
+			print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
+		}
+
+		my %href_params = get_feed_info();
+		if (!%href_params) {
+			$feed_class .= ' generic';
+		}
+		$href_params{'-title'} ||= 'log';
+
+		foreach my $format qw(RSS Atom) {
+			$href_params{'action'} = lc($format);
+			print $cgi->a({-href => href(%href_params),
+			              -title => "$href_params{'-title'} $format feed",
+			              -class => $feed_class}, $format)."\n";
+		}
+
+	} else {
+		print $cgi->a({-href => href(project=>undef, action=>"opml"),
+		              -class => $feed_class}, "OPML") . " ";
+		print $cgi->a({-href => href(project=>undef, action=>"project_index"),
+		              -class => $feed_class}, "TXT") . "\n";
+	}
+	print "</div>\n"; # class="page_footer"
+
+	if (defined $t0 && gitweb_check_feature('timed')) {
+		print "<div id=\"generating_info\">\n";
+		print 'This page took '.
+		      '<span id="generating_time" class="time_span">'.
+		      Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
+		      ' seconds </span>'.
+		      ' and '.
+		      '<span id="generating_cmd">'.
+		      $number_of_git_cmds.
+		      '</span> git commands '.
+		      " to generate.\n";
+		print "</div>\n"; # class="page_footer"
+	}
+
+	if (defined $site_footer && -f $site_footer) {
+		insert_file($site_footer);
+	}
+
+	print qq!<script type="text/javascript" src="$javascript"></script>\n!;
+	if (defined $action &&
+	    $action eq 'blame_incremental') {
+		print qq!<script type="text/javascript">\n!.
+		      qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
+		      qq!           "!. href() .qq!");\n!.
+		      qq!</script>\n!;
+	} elsif (gitweb_check_feature('javascript-actions')) {
+		print qq!<script type="text/javascript">\n!.
+		      qq!window.onload = fixLinks;\n!.
+		      qq!</script>\n!;
+	}
+
+	print "</body>\n" .
+	      "</html>";
+}
+
+# die_error(<http_status_code>, <error_message>[, <detailed_html_description>])
+# Example: die_error(404, 'Hash not found')
+# By convention, use the following status codes (as defined in RFC 2616):
+# 400: Invalid or missing CGI parameters, or
+#      requested object exists but has wrong type.
+# 403: Requested feature (like "pickaxe" or "snapshot") not enabled on
+#      this server or project.
+# 404: Requested object/revision/project doesn't exist.
+# 500: The server isn't configured properly, or
+#      an internal error occurred (e.g. failed assertions caused by bugs), or
+#      an unknown error occurred (e.g. the git binary died unexpectedly).
+# 503: The server is currently unavailable (because it is overloaded,
+#      or down for maintenance).  Generally, this is a temporary state.
+sub die_error {
+	my $status = shift || 500;
+	my $error = esc_html(shift) || "Internal Server Error";
+	my $extra = shift;
+	my %opts = @_;
+
+	my %http_responses = (
+		400 => '400 Bad Request',
+		403 => '403 Forbidden',
+		404 => '404 Not Found',
+		500 => '500 Internal Server Error',
+		503 => '503 Service Unavailable',
+	);
+	git_header_html($http_responses{$status}, undef, %opts);
+	print <<EOF;
+<div class="page_body">
+<br /><br />
+$status - $error
+<br />
+EOF
+	if (defined $extra) {
+		print "<hr />\n" .
+		      "$extra\n";
+	}
+	print "</div>\n";
+
+	git_footer_html();
+	goto DONE_GITWEB
+		unless ($opts{'-error_handler'});
+}
+
+## ----------------------------------------------------------------------
+## functions printing or outputting HTML: navigation
+
+sub git_print_page_nav {
+	my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_;
+	$extra = '' if !defined $extra; # pager or formats
+
+	my @navs = qw(summary shortlog log commit commitdiff tree);
+	if ($suppress) {
+		@navs = grep { $_ ne $suppress } @navs;
+	}
+
+	my %arg = map { $_ => {action=>$_} } @navs;
+	if (defined $head) {
+		for (qw(commit commitdiff)) {
+			$arg{$_}{'hash'} = $head;
+		}
+		if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
+			for (qw(shortlog log)) {
+				$arg{$_}{'hash'} = $head;
+			}
+		}
+	}
+
+	$arg{'tree'}{'hash'} = $treehead if defined $treehead;
+	$arg{'tree'}{'hash_base'} = $treebase if defined $treebase;
+
+	my @actions = gitweb_get_feature('actions');
+	my %repl = (
+		'%' => '%',
+		'n' => $project,         # project name
+		'f' => $git_dir,         # project path within filesystem
+		'h' => $treehead || '',  # current hash ('h' parameter)
+		'b' => $treebase || '',  # hash base ('hb' parameter)
+	);
+	while (@actions) {
+		my ($label, $link, $pos) = splice(@actions,0,3);
+		# insert
+		@navs = map { $_ eq $pos ? ($_, $label) : $_ } @navs;
+		# munch munch
+		$link =~ s/%([%nfhb])/$repl{$1}/g;
+		$arg{$label}{'_href'} = $link;
+	}
+
+	print "<div class=\"page_nav\">\n" .
+		(join " | ",
+		 map { $_ eq $current ?
+		       $_ : $cgi->a({-href => ($arg{$_}{_href} ? $arg{$_}{_href} : href(%{$arg{$_}}))}, "$_")
+		 } @navs);
+	print "<br/>\n$extra<br/>\n" .
+	      "</div>\n";
+}
+
+sub format_paging_nav {
+	my ($action, $page, $has_next_link) = @_;
+	my $paging_nav;
+
+
+	if ($page > 0) {
+		$paging_nav .=
+			$cgi->a({-href => href(-replay=>1, page=>undef)}, "first") .
+			" &sdot; " .
+			$cgi->a({-href => href(-replay=>1, page=>$page-1),
+			         -accesskey => "p", -title => "Alt-p"}, "prev");
+	} else {
+		$paging_nav .= "first &sdot; prev";
+	}
+
+	if ($has_next_link) {
+		$paging_nav .= " &sdot; " .
+			$cgi->a({-href => href(-replay=>1, page=>$page+1),
+			         -accesskey => "n", -title => "Alt-n"}, "next");
+	} else {
+		$paging_nav .= " &sdot; next";
+	}
+
+	return $paging_nav;
+}
+
+## ......................................................................
+## functions returning values to be passed, perhaps after some
+## transformation, to other functions; e.g. returning arguments to href()
+
+# returns hash to be passed to href to generate gitweb URL
+# in -title key it returns description of link
+sub get_feed_info {
+	my $format = shift || 'Atom';
+	my %res = (action => lc($format));
+
+	# feed links are possible only for project views
+	return unless (defined $project);
+	# some views should link to OPML, or to generic project feed,
+	# or don't have specific feed yet (so they should use generic)
+	return if ($action =~ /^(?:tags|heads|forks|tag|search)$/x);
+
+	my $branch;
+	# branches refs uses 'refs/heads/' prefix (fullname) to differentiate
+	# from tag links; this also makes possible to detect branch links
+	if ((defined $hash_base && $hash_base =~ m!^refs/heads/(.*)$!) ||
+	    (defined $hash      && $hash      =~ m!^refs/heads/(.*)$!)) {
+		$branch = $1;
+	}
+	# find log type for feed description (title)
+	my $type = 'log';
+	if (defined $file_name) {
+		$type  = "history of $file_name";
+		$type .= "/" if ($action eq 'tree');
+		$type .= " on '$branch'" if (defined $branch);
+	} else {
+		$type = "log of $branch" if (defined $branch);
+	}
+
+	$res{-title} = $type;
+	$res{'hash'} = (defined $branch ? "refs/heads/$branch" : undef);
+	$res{'file_name'} = $file_name;
+
+	return %res;
+}
+
+# assume that file exists
+sub insert_file {
+	my $filename = shift;
+
+	open my $fd, '<', $filename;
+	print map { to_utf8($_) } <$fd>;
+	close $fd;
+}
+
+## ......................................................................
+## functions printing or outputting HTML: div
+
+sub git_print_header_div {
+	my ($action, $title, $hash, $hash_base) = @_;
+	my %args = ();
+
+	$args{'action'} = $action;
+	$args{'hash'} = $hash if $hash;
+	$args{'hash_base'} = $hash_base if $hash_base;
+
+	print "<div class=\"header\">\n" .
+	      $cgi->a({-href => href(%args), -class => "title"},
+	      $title ? $title : $action) .
+	      "\n</div>\n";
+}
+
+sub print_local_time {
+	print format_local_time(@_);
+}
+
+sub format_local_time {
+	my $localtime = '';
+	my %date = @_;
+	if ($date{'hour_local'} < 6) {
+		$localtime .= sprintf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
+			$date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
+	} else {
+		$localtime .= sprintf(" (%02d:%02d %s)",
+			$date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
+	}
+
+	return $localtime;
+}
+
+sub git_print_page_path {
+	my $name = shift;
+	my $type = shift;
+	my $hb = shift;
+
+
+	print "<div class=\"page_path\">";
+	print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
+	              -title => 'tree root'}, to_utf8("[$project]"));
+	print " / ";
+	if (defined $name) {
+		my @dirname = split '/', $name;
+		my $basename = pop @dirname;
+		my $fullname = '';
+
+		foreach my $dir (@dirname) {
+			$fullname .= ($fullname ? '/' : '') . $dir;
+			print $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
+			                             hash_base=>$hb),
+			              -title => $fullname}, esc_path($dir));
+			print " / ";
+		}
+		if (defined $type && $type eq 'blob') {
+			print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name,
+			                             hash_base=>$hb),
+			              -title => $name}, esc_path($basename));
+		} elsif (defined $type && $type eq 'tree') {
+			print $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
+			                             hash_base=>$hb),
+			              -title => $name}, esc_path($basename));
+			print " / ";
+		} else {
+			print esc_path($basename);
+		}
+	}
+	print "<br/></div>\n";
+}
+
+# return link target (what link points to)
+sub git_get_link_target {
+	my $hash = shift;
+	my $link_target;
+
+	# read link
+	open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
+		or return;
+	{
+		local $/ = undef;
+		$link_target = <$fd>;
+	}
+	close $fd
+		or return;
+
+	return $link_target;
+}
+
+# given link target, and the directory (basedir) the link is in,
+# return target of link relative to top directory (top tree);
+# return undef if it is not possible (including absolute links).
+sub normalize_link_target {
+	my ($link_target, $basedir) = @_;
+
+	# absolute symlinks (beginning with '/') cannot be normalized
+	return if (substr($link_target, 0, 1) eq '/');
+
+	# normalize link target to path from top (root) tree (dir)
+	my $path;
+	if ($basedir) {
+		$path = $basedir . '/' . $link_target;
+	} else {
+		# we are in top (root) tree (dir)
+		$path = $link_target;
+	}
+
+	# remove //, /./, and /../
+	my @path_parts;
+	foreach my $part (split('/', $path)) {
+		# discard '.' and ''
+		next if (!$part || $part eq '.');
+		# handle '..'
+		if ($part eq '..') {
+			if (@path_parts) {
+				pop @path_parts;
+			} else {
+				# link leads outside repository (outside top dir)
+				return;
+			}
+		} else {
+			push @path_parts, $part;
+		}
+	}
+	$path = join('/', @path_parts);
+
+	return $path;
+}
+
+# print tree entry (row of git_tree), but without encompassing <tr> element
+sub git_print_tree_entry {
+	my ($t, $basedir, $hash_base, $have_blame) = @_;
+
+	my %base_key = ();
+	$base_key{'hash_base'} = $hash_base if defined $hash_base;
+
+	# The format of a table row is: mode list link.  Where mode is
+	# the mode of the entry, list is the name of the entry, an href,
+	# and link is the action links of the entry.
+
+	print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n";
+	if (exists $t->{'size'}) {
+		print "<td class=\"size\">$t->{'size'}</td>\n";
+	}
+	if ($t->{'type'} eq "blob") {
+		print "<td class=\"list\">" .
+			$cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
+			                       file_name=>"$basedir$t->{'name'}", %base_key),
+			        -class => "list"}, esc_path($t->{'name'}));
+		if (S_ISLNK(oct $t->{'mode'})) {
+			my $link_target = git_get_link_target($t->{'hash'});
+			if ($link_target) {
+				my $norm_target = normalize_link_target($link_target, $basedir);
+				if (defined $norm_target) {
+					print " -> " .
+					      $cgi->a({-href => href(action=>"object", hash_base=>$hash_base,
+					                             file_name=>$norm_target),
+					               -title => $norm_target}, esc_path($link_target));
+				} else {
+					print " -> " . esc_path($link_target);
+				}
+			}
+		}
+		print "</td>\n";
+		print "<td class=\"link\">";
+		print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
+		                             file_name=>"$basedir$t->{'name'}", %base_key)},
+		              "blob");
+		if ($have_blame) {
+			print " | " .
+			      $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
+			                             file_name=>"$basedir$t->{'name'}", %base_key)},
+			              "blame");
+		}
+		if (defined $hash_base) {
+			print " | " .
+			      $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+			                             hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
+			              "history");
+		}
+		print " | " .
+			$cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base,
+			                       file_name=>"$basedir$t->{'name'}")},
+			        "raw");
+		print "</td>\n";
+
+	} elsif ($t->{'type'} eq "tree") {
+		print "<td class=\"list\">";
+		print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
+		                             file_name=>"$basedir$t->{'name'}",
+		                             %base_key)},
+		              esc_path($t->{'name'}));
+		print "</td>\n";
+		print "<td class=\"link\">";
+		print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
+		                             file_name=>"$basedir$t->{'name'}",
+		                             %base_key)},
+		              "tree");
+		if (defined $hash_base) {
+			print " | " .
+			      $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+			                             file_name=>"$basedir$t->{'name'}")},
+			              "history");
+		}
+		print "</td>\n";
+	} else {
+		# unknown object: we can only present history for it
+		# (this includes 'commit' object, i.e. submodule support)
+		print "<td class=\"list\">" .
+		      esc_path($t->{'name'}) .
+		      "</td>\n";
+		print "<td class=\"link\">";
+		if (defined $hash_base) {
+			print $cgi->a({-href => href(action=>"history",
+			                             hash_base=>$hash_base,
+			                             file_name=>"$basedir$t->{'name'}")},
+			              "history");
+		}
+		print "</td>\n";
+	}
+}
+
+# print 'sort by' <th> element, generating 'sort by $name' replay link
+# if that order is not selected
+sub print_sort_th {
+	print format_sort_th(@_);
+}
+
+sub format_sort_th {
+	my ($name, $order, $header) = @_;
+	my $sort_th = "";
+	$header ||= ucfirst($name);
+
+	if ($order eq $name) {
+		$sort_th .= "<th>$header</th>\n";
+	} else {
+		$sort_th .= "<th>" .
+		            $cgi->a({-href => href(-replay=>1, order=>$name),
+		                     -class => "header"}, $header) .
+		            "</th>\n";
+	}
+
+	return $sort_th;
+}
+
+1;
-- 
1.7.1.455.g8f441

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCHv2 GSOC 09/11] gitweb: Create Gitweb::Util module
  2010-07-15  7:29 [PATCHv2 00/11] Splitting gitweb Pavan Kumar Sunkara
                   ` (7 preceding siblings ...)
  2010-07-15  7:29 ` [PATCHv2 GSOC 08/11] gitweb: Create Gitweb::View module Pavan Kumar Sunkara
@ 2010-07-15  7:29 ` Pavan Kumar Sunkara
  2010-07-18 17:45   ` Jakub Narebski
  2010-07-15  7:29 ` [PATCHv2 GSOC 10/11] gitweb: Create Gitweb::Format module Pavan Kumar Sunkara
                   ` (2 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: Pavan Kumar Sunkara @ 2010-07-15  7:29 UTC (permalink / raw)
  To: git, jnareb, chriscool, pasky; +Cc: Pavan Kumar Sunkara

Create Gitweb::Util module in 'gitweb/lib/Gitweb/Util.pm'
to store the git utility subroutines related to gitweb.

This module include subroutines in various categories
such as git utility subs invoking git commands, git
utility subs accessing git repository, mimetype related
subs and HTML output utility subs.

Subroutines moved:
	git_get_head_hash
	git_get_full_hash
	git_get_short_hash
	git_get_hash
	git_get_type
	git_get_hash_by_path
	git_get_path_by_hash
	git_get_last_activity
	git_get_references
	git_get_rev_name_tags
	git_get_heads_list
	git_get_tags_list
	mimetype_guess_file
	mimetype_guess
	blob_mimetype
	blob_contenttype
	guess_file_syntax
	run_highlighter
	fill_from_file_info
	is_deleted
	is_patch_split

Update 'gitweb/Makefile' to install Gitweb::Util alongside gitweb.

Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
---
 gitweb/Makefile           |    1 +
 gitweb/gitweb.perl        |  420 +------------------------------------------
 gitweb/lib/Gitweb/Util.pm |  447 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 449 insertions(+), 419 deletions(-)
 create mode 100644 gitweb/lib/Gitweb/Util.pm

diff --git a/gitweb/Makefile b/gitweb/Makefile
index ce9f372..1c37609 100644
--- a/gitweb/Makefile
+++ b/gitweb/Makefile
@@ -118,6 +118,7 @@ GITWEB_MODULES += lib/Gitweb/Request.pm
 GITWEB_MODULES += lib/Gitweb/Escape.pm
 GITWEB_MODULES += lib/Gitweb/RepoConfig.pm
 GITWEB_MODULES += lib/Gitweb/View.pm
+GITWEB_MODULES += lib/Gitweb/Util.pm
 
 GITWEB_REPLACE = \
 	-e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 80c4a40..5f9ca4c 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -31,6 +31,7 @@ use Gitweb::Request;
 use Gitweb::Escape;
 use Gitweb::RepoConfig;
 use Gitweb::View;
+use Gitweb::Util;
 
 BEGIN {
 	CGI->compile() if $ENV{'MOD_PERL'};
@@ -1114,164 +1115,6 @@ sub format_snapshot_links {
 }
 
 ## ----------------------------------------------------------------------
-## git utility subroutines, invoking git commands
-
-# get HEAD ref of given project as hash
-sub git_get_head_hash {
-	return git_get_full_hash(shift, 'HEAD');
-}
-
-sub git_get_full_hash {
-	return git_get_hash(@_);
-}
-
-sub git_get_short_hash {
-	return git_get_hash(@_, '--short=7');
-}
-
-sub git_get_hash {
-	my ($project, $hash, @options) = @_;
-	my $o_git_dir = $git_dir;
-	my $retval = undef;
-	$git_dir = "$projectroot/$project";
-	if (open my $fd, '-|', git_cmd(), 'rev-parse',
-	    '--verify', '-q', @options, $hash) {
-		$retval = <$fd>;
-		chomp $retval if defined $retval;
-		close $fd;
-	}
-	if (defined $o_git_dir) {
-		$git_dir = $o_git_dir;
-	}
-	return $retval;
-}
-
-# get type of given object
-sub git_get_type {
-	my $hash = shift;
-
-	open my $fd, "-|", git_cmd(), "cat-file", '-t', $hash or return;
-	my $type = <$fd>;
-	close $fd or return;
-	chomp $type;
-	return $type;
-}
-
-# get hash of given path at given ref
-sub git_get_hash_by_path {
-	my $base = shift;
-	my $path = shift || return undef;
-	my $type = shift;
-
-	$path =~ s,/+$,,;
-
-	open my $fd, "-|", git_cmd(), "ls-tree", $base, "--", $path
-		or die_error(500, "Open git-ls-tree failed");
-	my $line = <$fd>;
-	close $fd or return undef;
-
-	if (!defined $line) {
-		# there is no tree or hash given by $path at $base
-		return undef;
-	}
-
-	#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa	panic.c'
-	$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/;
-	if (defined $type && $type ne $2) {
-		# type doesn't match
-		return undef;
-	}
-	return $3;
-}
-
-# get path of entry with given hash at given tree-ish (ref)
-# used to get 'from' filename for combined diff (merge commit) for renames
-sub git_get_path_by_hash {
-	my $base = shift || return;
-	my $hash = shift || return;
-
-	local $/ = "\0";
-
-	open my $fd, "-|", git_cmd(), "ls-tree", '-r', '-t', '-z', $base
-		or return undef;
-	while (my $line = <$fd>) {
-		chomp $line;
-
-		#'040000 tree 595596a6a9117ddba9fe379b6b012b558bac8423	gitweb'
-		#'100644 blob e02e90f0429be0d2a69b76571101f20b8f75530f	gitweb/README'
-		if ($line =~ m/(?:[0-9]+) (?:.+) $hash\t(.+)$/) {
-			close $fd;
-			return $1;
-		}
-	}
-	close $fd;
-	return undef;
-}
-
-## ......................................................................
-## git utility functions, directly accessing git repository
-
-sub git_get_last_activity {
-	my ($path) = @_;
-	my $fd;
-
-	$git_dir = "$projectroot/$path";
-	open($fd, "-|", git_cmd(), 'for-each-ref',
-	     '--format=%(committer)',
-	     '--sort=-committerdate',
-	     '--count=1',
-	     'refs/heads') or return;
-	my $most_recent = <$fd>;
-	close $fd or return;
-	if (defined $most_recent &&
-	    $most_recent =~ / (\d+) [-+][01]\d\d\d$/) {
-		my $timestamp = $1;
-		my $age = time - $timestamp;
-		return ($age, age_string($age));
-	}
-	return (undef, undef);
-}
-
-sub git_get_references {
-	my $type = shift || "";
-	my %refs;
-	# 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
-	# c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
-	open my $fd, "-|", git_cmd(), "show-ref", "--dereference",
-		($type ? ("--", "refs/$type") : ()) # use -- <pattern> if $type
-		or return;
-
-	while (my $line = <$fd>) {
-		chomp $line;
-		if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type.*)$!) {
-			if (defined $refs{$1}) {
-				push @{$refs{$1}}, $2;
-			} else {
-				$refs{$1} = [ $2 ];
-			}
-		}
-	}
-	close $fd or return;
-	return \%refs;
-}
-
-sub git_get_rev_name_tags {
-	my $hash = shift || return undef;
-
-	open my $fd, "-|", git_cmd(), "name-rev", "--tags", $hash
-		or return;
-	my $name_rev = <$fd>;
-	close $fd;
-
-	if ($name_rev =~ m|^$hash tags/(.*)$|) {
-		return $1;
-	} else {
-		# catches also '$hash undefined' output
-		return undef;
-	}
-}
-
-## ----------------------------------------------------------------------
 ## parse to hash functions
 
 sub parse_date {
@@ -1627,231 +1470,6 @@ sub parse_from_to_diffinfo {
 }
 
 ## ......................................................................
-## parse to array of hashes functions
-
-sub git_get_heads_list {
-	my $limit = shift;
-	my @headslist;
-
-	open my $fd, '-|', git_cmd(), 'for-each-ref',
-		($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
-		'--format=%(objectname) %(refname) %(subject)%00%(committer)',
-		'refs/heads'
-		or return;
-	while (my $line = <$fd>) {
-		my %ref_item;
-
-		chomp $line;
-		my ($refinfo, $committerinfo) = split(/\0/, $line);
-		my ($hash, $name, $title) = split(' ', $refinfo, 3);
-		my ($committer, $epoch, $tz) =
-			($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
-		$ref_item{'fullname'}  = $name;
-		$name =~ s!^refs/heads/!!;
-
-		$ref_item{'name'}  = $name;
-		$ref_item{'id'}    = $hash;
-		$ref_item{'title'} = $title || '(no commit message)';
-		$ref_item{'epoch'} = $epoch;
-		if ($epoch) {
-			$ref_item{'age'} = age_string(time - $ref_item{'epoch'});
-		} else {
-			$ref_item{'age'} = "unknown";
-		}
-
-		push @headslist, \%ref_item;
-	}
-	close $fd;
-
-	return wantarray ? @headslist : \@headslist;
-}
-
-sub git_get_tags_list {
-	my $limit = shift;
-	my @tagslist;
-
-	open my $fd, '-|', git_cmd(), 'for-each-ref',
-		($limit ? '--count='.($limit+1) : ()), '--sort=-creatordate',
-		'--format=%(objectname) %(objecttype) %(refname) '.
-		'%(*objectname) %(*objecttype) %(subject)%00%(creator)',
-		'refs/tags'
-		or return;
-	while (my $line = <$fd>) {
-		my %ref_item;
-
-		chomp $line;
-		my ($refinfo, $creatorinfo) = split(/\0/, $line);
-		my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6);
-		my ($creator, $epoch, $tz) =
-			($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/);
-		$ref_item{'fullname'} = $name;
-		$name =~ s!^refs/tags/!!;
-
-		$ref_item{'type'} = $type;
-		$ref_item{'id'} = $id;
-		$ref_item{'name'} = $name;
-		if ($type eq "tag") {
-			$ref_item{'subject'} = $title;
-			$ref_item{'reftype'} = $reftype;
-			$ref_item{'refid'}   = $refid;
-		} else {
-			$ref_item{'reftype'} = $type;
-			$ref_item{'refid'}   = $id;
-		}
-
-		if ($type eq "tag" || $type eq "commit") {
-			$ref_item{'epoch'} = $epoch;
-			if ($epoch) {
-				$ref_item{'age'} = age_string(time - $ref_item{'epoch'});
-			} else {
-				$ref_item{'age'} = "unknown";
-			}
-		}
-
-		push @tagslist, \%ref_item;
-	}
-	close $fd;
-
-	return wantarray ? @tagslist : \@tagslist;
-}
-
-## ......................................................................
-## mimetype related functions
-
-sub mimetype_guess_file {
-	my $filename = shift;
-	my $mimemap = shift;
-	-r $mimemap or return undef;
-
-	my %mimemap;
-	open(my $mh, '<', $mimemap) or return undef;
-	while (<$mh>) {
-		next if m/^#/; # skip comments
-		my ($mimetype, $exts) = split(/\t+/);
-		if (defined $exts) {
-			my @exts = split(/\s+/, $exts);
-			foreach my $ext (@exts) {
-				$mimemap{$ext} = $mimetype;
-			}
-		}
-	}
-	close($mh);
-
-	$filename =~ /\.([^.]*)$/;
-	return $mimemap{$1};
-}
-
-sub mimetype_guess {
-	my $filename = shift;
-	my $mime;
-	$filename =~ /\./ or return undef;
-
-	if ($mimetypes_file) {
-		my $file = $mimetypes_file;
-		if ($file !~ m!^/!) { # if it is relative path
-			# it is relative to project
-			$file = "$projectroot/$project/$file";
-		}
-		$mime = mimetype_guess_file($filename, $file);
-	}
-	$mime ||= mimetype_guess_file($filename, '/etc/mime.types');
-	return $mime;
-}
-
-sub blob_mimetype {
-	my $fd = shift;
-	my $filename = shift;
-
-	if ($filename) {
-		my $mime = mimetype_guess($filename);
-		$mime and return $mime;
-	}
-
-	# just in case
-	return $default_blob_plain_mimetype unless $fd;
-
-	if (-T $fd) {
-		return 'text/plain';
-	} elsif (! $filename) {
-		return 'application/octet-stream';
-	} elsif ($filename =~ m/\.png$/i) {
-		return 'image/png';
-	} elsif ($filename =~ m/\.gif$/i) {
-		return 'image/gif';
-	} elsif ($filename =~ m/\.jpe?g$/i) {
-		return 'image/jpeg';
-	} else {
-		return 'application/octet-stream';
-	}
-}
-
-sub blob_contenttype {
-	my ($fd, $file_name, $type) = @_;
-
-	$type ||= blob_mimetype($fd, $file_name);
-	if ($type eq 'text/plain' && defined $default_text_plain_charset) {
-		$type .= "; charset=$default_text_plain_charset";
-	}
-
-	return $type;
-}
-
-# guess file syntax for syntax highlighting; return undef if no highlighting
-# the name of syntax can (in the future) depend on syntax highlighter used
-sub guess_file_syntax {
-	my ($highlight, $mimetype, $file_name) = @_;
-	return undef unless ($highlight && defined $file_name);
-
-	# configuration for 'highlight' (http://www.andre-simon.de/)
-	# match by basename
-	my %highlight_basename = (
-		#'Program' => 'py',
-		#'Library' => 'py',
-		'SConstruct' => 'py', # SCons equivalent of Makefile
-		'Makefile' => 'make',
-	);
-	# match by extension
-	my %highlight_ext = (
-		# main extensions, defining name of syntax;
-		# see files in /usr/share/highlight/langDefs/ directory
-		map { $_ => $_ }
-			qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl),
-		# alternate extensions, see /etc/highlight/filetypes.conf
-		'h' => 'c',
-		map { $_ => 'cpp' } qw(cxx c++ cc),
-		map { $_ => 'php' } qw(php3 php4),
-		map { $_ => 'pl'  } qw(perl pm), # perhaps also 'cgi'
-		'mak' => 'make',
-		map { $_ => 'xml' } qw(xhtml html htm),
-	);
-
-	my $basename = basename($file_name, '.in');
-	return $highlight_basename{$basename}
-		if exists $highlight_basename{$basename};
-
-	$basename =~ /\.([^.]*)$/;
-	my $ext = $1 or return undef;
-	return $highlight_ext{$ext}
-		if exists $highlight_ext{$ext};
-
-	return undef;
-}
-
-# run highlighter and return FD of its output,
-# or return original FD if no highlighting
-sub run_highlighter {
-	my ($fd, $highlight, $syntax) = @_;
-	return $fd unless ($highlight && defined $syntax);
-
-	close $fd
-		or die_error(404, "Reading blob failed");
-	open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
-	          "highlight --xhtml --fragment --syntax $syntax |"
-		or die_error(500, "Couldn't open file or run syntax highlighter");
-	return $fd;
-}
-
-## ......................................................................
 ## functions printing or outputting HTML: div
 
 # Outputs the author name and date in long form
@@ -1950,42 +1568,6 @@ sub git_print_log {
 ## ......................................................................
 ## functions printing large fragments of HTML
 
-# get pre-image filenames for merge (combined) diff
-sub fill_from_file_info {
-	my ($diff, @parents) = @_;
-
-	$diff->{'from_file'} = [ ];
-	$diff->{'from_file'}[$diff->{'nparents'} - 1] = undef;
-	for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
-		if ($diff->{'status'}[$i] eq 'R' ||
-		    $diff->{'status'}[$i] eq 'C') {
-			$diff->{'from_file'}[$i] =
-				git_get_path_by_hash($parents[$i], $diff->{'from_id'}[$i]);
-		}
-	}
-
-	return $diff;
-}
-
-# is current raw difftree line of file deletion
-sub is_deleted {
-	my $diffinfo = shift;
-
-	return $diffinfo->{'to_id'} eq ('0' x 40);
-}
-
-# does patch correspond to [previous] difftree raw line
-# $diffinfo  - hashref of parsed raw diff format
-# $patchinfo - hashref of parsed patch diff format
-#              (the same keys as in $diffinfo)
-sub is_patch_split {
-	my ($diffinfo, $patchinfo) = @_;
-
-	return defined $diffinfo && defined $patchinfo
-		&& $diffinfo->{'to_file'} eq $patchinfo->{'to_file'};
-}
-
-
 sub git_difftree_body {
 	my ($difftree, $hash, @parents) = @_;
 	my ($parent) = $parents[0];
diff --git a/gitweb/lib/Gitweb/Util.pm b/gitweb/lib/Gitweb/Util.pm
new file mode 100644
index 0000000..4d0f5d8
--- /dev/null
+++ b/gitweb/lib/Gitweb/Util.pm
@@ -0,0 +1,447 @@
+#!/usr/bin/perl
+#
+# Gitweb::Util -- gitweb's utility function subs package
+#
+# This program is licensed under the GPLv2
+
+package Gitweb::Util;
+
+use strict;
+use warnings;
+use Exporter qw(import);
+
+our @EXPORT = qw(guess_file_syntax run_highlighter git_get_head_hash git_get_hash
+                 git_get_full_hash git_get_short_hash git_get_type git_get_hash_by_path
+                 git_get_path_by_hash git_get_last_activity git_get_references
+                 git_get_rev_name_tags git_get_heads_list git_get_tags_list blob_mimetype
+                 blob_contenttype fill_from_file_info is_deleted is_patch_split);
+
+use File::Basename qw(basename);
+use Gitweb::Git qw(git_cmd $git_dir quote_command);
+use Gitweb::Config qw($projectroot $mimetypes_file $default_text_plain_charset
+                      $default_blob_plain_mimetype);
+use Gitweb::Request qw($project $hash);
+use Gitweb::View qw(die_error age_string);
+
+## ----------------------------------------------------------------------
+## git utility subroutines, invoking git commands
+
+# get HEAD ref of given project as hash
+sub git_get_head_hash {
+	return git_get_full_hash(shift, 'HEAD');
+}
+
+sub git_get_full_hash {
+	return git_get_hash(@_);
+}
+
+sub git_get_short_hash {
+	return git_get_hash(@_, '--short=7');
+}
+
+sub git_get_hash {
+	my ($project, $hash, @options) = @_;
+	my $o_git_dir = $git_dir;
+	my $retval = undef;
+	$git_dir = "$projectroot/$project";
+	if (open my $fd, '-|', git_cmd(), 'rev-parse',
+	    '--verify', '-q', @options, $hash) {
+		$retval = <$fd>;
+		chomp $retval if defined $retval;
+		close $fd;
+	}
+	if (defined $o_git_dir) {
+		$git_dir = $o_git_dir;
+	}
+	return $retval;
+}
+
+# get type of given object
+sub git_get_type {
+	my $hash = shift;
+
+	open my $fd, "-|", git_cmd(), "cat-file", '-t', $hash or return;
+	my $type = <$fd>;
+	close $fd or return;
+	chomp $type;
+	return $type;
+}
+
+# get hash of given path at given ref
+sub git_get_hash_by_path {
+	my $base = shift;
+	my $path = shift || return undef;
+	my $type = shift;
+
+	$path =~ s,/+$,,;
+
+	open my $fd, "-|", git_cmd(), "ls-tree", $base, "--", $path
+		or die_error(500, "Open git-ls-tree failed");
+	my $line = <$fd>;
+	close $fd or return undef;
+
+	if (!defined $line) {
+		# there is no tree or hash given by $path at $base
+		return undef;
+	}
+
+	#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa	panic.c'
+	$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/;
+	if (defined $type && $type ne $2) {
+		# type doesn't match
+		return undef;
+	}
+	return $3;
+}
+
+# get path of entry with given hash at given tree-ish (ref)
+# used to get 'from' filename for combined diff (merge commit) for renames
+sub git_get_path_by_hash {
+	my $base = shift || return;
+	my $hash = shift || return;
+
+	local $/ = "\0";
+
+	open my $fd, "-|", git_cmd(), "ls-tree", '-r', '-t', '-z', $base
+		or return undef;
+	while (my $line = <$fd>) {
+		chomp $line;
+
+		#'040000 tree 595596a6a9117ddba9fe379b6b012b558bac8423	gitweb'
+		#'100644 blob e02e90f0429be0d2a69b76571101f20b8f75530f	gitweb/README'
+		if ($line =~ m/(?:[0-9]+) (?:.+) $hash\t(.+)$/) {
+			close $fd;
+			return $1;
+		}
+	}
+	close $fd;
+	return undef;
+}
+
+## ......................................................................
+## git utility functions, directly accessing git repository
+
+sub git_get_last_activity {
+	my ($path) = @_;
+	my $fd;
+
+	$git_dir = "$projectroot/$path";
+	open($fd, "-|", git_cmd(), 'for-each-ref',
+	     '--format=%(committer)',
+	     '--sort=-committerdate',
+	     '--count=1',
+	     'refs/heads') or return;
+	my $most_recent = <$fd>;
+	close $fd or return;
+	if (defined $most_recent &&
+	    $most_recent =~ / (\d+) [-+][01]\d\d\d$/) {
+		my $timestamp = $1;
+		my $age = time - $timestamp;
+		return ($age, age_string($age));
+	}
+	return (undef, undef);
+}
+
+sub git_get_references {
+	my $type = shift || "";
+	my %refs;
+	# 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
+	# c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
+	open my $fd, "-|", git_cmd(), "show-ref", "--dereference",
+		($type ? ("--", "refs/$type") : ()) # use -- <pattern> if $type
+		or return;
+
+	while (my $line = <$fd>) {
+		chomp $line;
+		if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type.*)$!) {
+			if (defined $refs{$1}) {
+				push @{$refs{$1}}, $2;
+			} else {
+				$refs{$1} = [ $2 ];
+			}
+		}
+	}
+	close $fd or return;
+	return \%refs;
+}
+
+sub git_get_rev_name_tags {
+	my $hash = shift || return undef;
+
+	open my $fd, "-|", git_cmd(), "name-rev", "--tags", $hash
+		or return;
+	my $name_rev = <$fd>;
+	close $fd;
+
+	if ($name_rev =~ m|^$hash tags/(.*)$|) {
+		return $1;
+	} else {
+		# catches also '$hash undefined' output
+		return undef;
+	}
+}
+
+## ......................................................................
+## parse to array of hashes functions
+
+sub git_get_heads_list {
+	my $limit = shift;
+	my @headslist;
+
+	open my $fd, '-|', git_cmd(), 'for-each-ref',
+		($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
+		'--format=%(objectname) %(refname) %(subject)%00%(committer)',
+		'refs/heads'
+		or return;
+	while (my $line = <$fd>) {
+		my %ref_item;
+
+		chomp $line;
+		my ($refinfo, $committerinfo) = split(/\0/, $line);
+		my ($hash, $name, $title) = split(' ', $refinfo, 3);
+		my ($committer, $epoch, $tz) =
+			($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
+		$ref_item{'fullname'}  = $name;
+		$name =~ s!^refs/heads/!!;
+
+		$ref_item{'name'}  = $name;
+		$ref_item{'id'}    = $hash;
+		$ref_item{'title'} = $title || '(no commit message)';
+		$ref_item{'epoch'} = $epoch;
+		if ($epoch) {
+			$ref_item{'age'} = age_string(time - $ref_item{'epoch'});
+		} else {
+			$ref_item{'age'} = "unknown";
+		}
+
+		push @headslist, \%ref_item;
+	}
+	close $fd;
+
+	return wantarray ? @headslist : \@headslist;
+}
+
+sub git_get_tags_list {
+	my $limit = shift;
+	my @tagslist;
+
+	open my $fd, '-|', git_cmd(), 'for-each-ref',
+		($limit ? '--count='.($limit+1) : ()), '--sort=-creatordate',
+		'--format=%(objectname) %(objecttype) %(refname) '.
+		'%(*objectname) %(*objecttype) %(subject)%00%(creator)',
+		'refs/tags'
+		or return;
+	while (my $line = <$fd>) {
+		my %ref_item;
+
+		chomp $line;
+		my ($refinfo, $creatorinfo) = split(/\0/, $line);
+		my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6);
+		my ($creator, $epoch, $tz) =
+			($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/);
+		$ref_item{'fullname'} = $name;
+		$name =~ s!^refs/tags/!!;
+
+		$ref_item{'type'} = $type;
+		$ref_item{'id'} = $id;
+		$ref_item{'name'} = $name;
+		if ($type eq "tag") {
+			$ref_item{'subject'} = $title;
+			$ref_item{'reftype'} = $reftype;
+			$ref_item{'refid'}   = $refid;
+		} else {
+			$ref_item{'reftype'} = $type;
+			$ref_item{'refid'}   = $id;
+		}
+
+		if ($type eq "tag" || $type eq "commit") {
+			$ref_item{'epoch'} = $epoch;
+			if ($epoch) {
+				$ref_item{'age'} = age_string(time - $ref_item{'epoch'});
+			} else {
+				$ref_item{'age'} = "unknown";
+			}
+		}
+
+		push @tagslist, \%ref_item;
+	}
+	close $fd;
+
+	return wantarray ? @tagslist : \@tagslist;
+}
+
+## ......................................................................
+## mimetype related functions
+
+sub mimetype_guess_file {
+	my $filename = shift;
+	my $mimemap = shift;
+	-r $mimemap or return undef;
+
+	my %mimemap;
+	open(my $mh, '<', $mimemap) or return undef;
+	while (<$mh>) {
+		next if m/^#/; # skip comments
+		my ($mimetype, $exts) = split(/\t+/);
+		if (defined $exts) {
+			my @exts = split(/\s+/, $exts);
+			foreach my $ext (@exts) {
+				$mimemap{$ext} = $mimetype;
+			}
+		}
+	}
+	close($mh);
+
+	$filename =~ /\.([^.]*)$/;
+	return $mimemap{$1};
+}
+
+sub mimetype_guess {
+	my $filename = shift;
+	my $mime;
+	$filename =~ /\./ or return undef;
+
+	if ($mimetypes_file) {
+		my $file = $mimetypes_file;
+		if ($file !~ m!^/!) { # if it is relative path
+			# it is relative to project
+			$file = "$projectroot/$project/$file";
+		}
+		$mime = mimetype_guess_file($filename, $file);
+	}
+	$mime ||= mimetype_guess_file($filename, '/etc/mime.types');
+	return $mime;
+}
+
+sub blob_mimetype {
+	my $fd = shift;
+	my $filename = shift;
+
+	if ($filename) {
+		my $mime = mimetype_guess($filename);
+		$mime and return $mime;
+	}
+
+	# just in case
+	return $default_blob_plain_mimetype unless $fd;
+
+	if (-T $fd) {
+		return 'text/plain';
+	} elsif (! $filename) {
+		return 'application/octet-stream';
+	} elsif ($filename =~ m/\.png$/i) {
+		return 'image/png';
+	} elsif ($filename =~ m/\.gif$/i) {
+		return 'image/gif';
+	} elsif ($filename =~ m/\.jpe?g$/i) {
+		return 'image/jpeg';
+	} else {
+		return 'application/octet-stream';
+	}
+}
+
+sub blob_contenttype {
+	my ($fd, $file_name, $type) = @_;
+
+	$type ||= blob_mimetype($fd, $file_name);
+	if ($type eq 'text/plain' && defined $default_text_plain_charset) {
+		$type .= "; charset=$default_text_plain_charset";
+	}
+
+	return $type;
+}
+
+# guess file syntax for syntax highlighting; return undef if no highlighting
+# the name of syntax can (in the future) depend on syntax highlighter used
+sub guess_file_syntax {
+	my ($highlight, $mimetype, $file_name) = @_;
+	return undef unless ($highlight && defined $file_name);
+
+	# configuration for 'highlight' (http://www.andre-simon.de/)
+	# match by basename
+	my %highlight_basename = (
+		#'Program' => 'py',
+		#'Library' => 'py',
+		'SConstruct' => 'py', # SCons equivalent of Makefile
+		'Makefile' => 'make',
+	);
+	# match by extension
+	my %highlight_ext = (
+		# main extensions, defining name of syntax;
+		# see files in /usr/share/highlight/langDefs/ directory
+		map { $_ => $_ }
+			qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl),
+		# alternate extensions, see /etc/highlight/filetypes.conf
+		'h' => 'c',
+		map { $_ => 'cpp' } qw(cxx c++ cc),
+		map { $_ => 'php' } qw(php3 php4),
+		map { $_ => 'pl'  } qw(perl pm), # perhaps also 'cgi'
+		'mak' => 'make',
+		map { $_ => 'xml' } qw(xhtml html htm),
+	);
+
+	my $basename = basename($file_name, '.in');
+	return $highlight_basename{$basename}
+		if exists $highlight_basename{$basename};
+
+	$basename =~ /\.([^.]*)$/;
+	my $ext = $1 or return undef;
+	return $highlight_ext{$ext}
+		if exists $highlight_ext{$ext};
+
+	return undef;
+}
+
+# run highlighter and return FD of its output,
+# or return original FD if no highlighting
+sub run_highlighter {
+	my ($fd, $highlight, $syntax) = @_;
+	return $fd unless ($highlight && defined $syntax);
+
+	close $fd
+		or die_error(404, "Reading blob failed");
+	open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
+	          "highlight --xhtml --fragment --syntax $syntax |"
+		or die_error(500, "Couldn't open file or run syntax highlighter");
+	return $fd;
+}
+
+## ......................................................................
+## functions printing large fragments of HTML
+
+# get pre-image filenames for merge (combined) diff
+sub fill_from_file_info {
+	my ($diff, @parents) = @_;
+
+	$diff->{'from_file'} = [ ];
+	$diff->{'from_file'}[$diff->{'nparents'} - 1] = undef;
+	for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
+		if ($diff->{'status'}[$i] eq 'R' ||
+		    $diff->{'status'}[$i] eq 'C') {
+			$diff->{'from_file'}[$i] =
+				git_get_path_by_hash($parents[$i], $diff->{'from_id'}[$i]);
+		}
+	}
+
+	return $diff;
+}
+
+# is current raw difftree line of file deletion
+sub is_deleted {
+	my $diffinfo = shift;
+
+	return $diffinfo->{'to_id'} eq ('0' x 40);
+}
+
+# does patch correspond to [previous] difftree raw line
+# $diffinfo  - hashref of parsed raw diff format
+# $patchinfo - hashref of parsed patch diff format
+#              (the same keys as in $diffinfo)
+sub is_patch_split {
+	my ($diffinfo, $patchinfo) = @_;
+
+	return defined $diffinfo && defined $patchinfo
+		&& $diffinfo->{'to_file'} eq $patchinfo->{'to_file'};
+}
+
+1;
-- 
1.7.1.455.g8f441

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCHv2 GSOC 10/11] gitweb: Create Gitweb::Format module
  2010-07-15  7:29 [PATCHv2 00/11] Splitting gitweb Pavan Kumar Sunkara
                   ` (8 preceding siblings ...)
  2010-07-15  7:29 ` [PATCHv2 GSOC 09/11] gitweb: Create Gitweb::Util module Pavan Kumar Sunkara
@ 2010-07-15  7:29 ` Pavan Kumar Sunkara
  2010-07-18 20:16   ` Jakub Narebski
  2010-07-15  7:29 ` [PATCHv2 GSOC 11/11] gitweb: Create Gitweb::Parse module Pavan Kumar Sunkara
  2010-08-01 20:44 ` [PATCHv2 00/11] Splitting gitweb Sverre Rabbelier
  11 siblings, 1 reply; 27+ messages in thread
From: Pavan Kumar Sunkara @ 2010-07-15  7:29 UTC (permalink / raw)
  To: git, jnareb, chriscool, pasky; +Cc: Pavan Kumar Sunkara

Create Gitweb::Format module in 'gitweb/lib/Gitweb/Format.pm'
to store the subroutines related to formatting of HTML
fragments required for gitweb.

This module depends on Config.pm, View.pm, Escape.pm,
Util.pm and Request.pm. It mainly contain functions returning
short HTML fragments or transforming HTML fragments. Also
include subroutines regarding avatar formatting.

Subroutines moved:
	format_log_line_html
	format_ref_marker
	format_subject_html
	picon_url
	gravatar_url
	git_get_avatar
	format_search_author
	format_author_html
	format_git_diff_header_line
	format_extended_diff_header_line
	format_diff_from_to_header
	format_diff_cc_simplified
	format_diff_line
	format_snapshot_links

Update 'gitweb/Makefile' to install Gitweb::Format alongside gitweb.

Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
---
 gitweb/Makefile             |    1 +
 gitweb/gitweb.perl          |  512 +----------------------------------------
 gitweb/lib/Gitweb/Format.pm |  537 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 539 insertions(+), 511 deletions(-)
 create mode 100644 gitweb/lib/Gitweb/Format.pm

diff --git a/gitweb/Makefile b/gitweb/Makefile
index 1c37609..f1803f5 100644
--- a/gitweb/Makefile
+++ b/gitweb/Makefile
@@ -119,6 +119,7 @@ GITWEB_MODULES += lib/Gitweb/Escape.pm
 GITWEB_MODULES += lib/Gitweb/RepoConfig.pm
 GITWEB_MODULES += lib/Gitweb/View.pm
 GITWEB_MODULES += lib/Gitweb/Util.pm
+GITWEB_MODULES += lib/Gitweb/Format.pm
 
 GITWEB_REPLACE = \
 	-e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 5f9ca4c..3de3de5 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -32,6 +32,7 @@ use Gitweb::Escape;
 use Gitweb::RepoConfig;
 use Gitweb::View;
 use Gitweb::Util;
+use Gitweb::Format;
 
 BEGIN {
 	CGI->compile() if $ENV{'MOD_PERL'};
@@ -604,517 +605,6 @@ sub unquote {
 }
 
 ## ----------------------------------------------------------------------
-## functions returning short HTML fragments, or transforming HTML fragments
-## which don't belong to other sections
-
-# format line of commit message.
-sub format_log_line_html {
-	my $line = shift;
-
-	$line = esc_html($line, -nbsp=>1);
-	$line =~ s{\b([0-9a-fA-F]{8,40})\b}{
-		$cgi->a({-href => href(action=>"object", hash=>$1),
-					-class => "text"}, $1);
-	}eg;
-
-	return $line;
-}
-
-# format marker of refs pointing to given object
-
-# the destination action is chosen based on object type and current context:
-# - for annotated tags, we choose the tag view unless it's the current view
-#   already, in which case we go to shortlog view
-# - for other refs, we keep the current view if we're in history, shortlog or
-#   log view, and select shortlog otherwise
-sub format_ref_marker {
-	my ($refs, $id) = @_;
-	my $markers = '';
-
-	if (defined $refs->{$id}) {
-		foreach my $ref (@{$refs->{$id}}) {
-			# this code exploits the fact that non-lightweight tags are the
-			# only indirect objects, and that they are the only objects for which
-			# we want to use tag instead of shortlog as action
-			my ($type, $name) = qw();
-			my $indirect = ($ref =~ s/\^\{\}$//);
-			# e.g. tags/v2.6.11 or heads/next
-			if ($ref =~ m!^(.*?)s?/(.*)$!) {
-				$type = $1;
-				$name = $2;
-			} else {
-				$type = "ref";
-				$name = $ref;
-			}
-
-			my $class = $type;
-			$class .= " indirect" if $indirect;
-
-			my $dest_action = "shortlog";
-
-			if ($indirect) {
-				$dest_action = "tag" unless $action eq "tag";
-			} elsif ($action =~ /^(history|(short)?log)$/) {
-				$dest_action = $action;
-			}
-
-			my $dest = "";
-			$dest .= "refs/" unless $ref =~ m!^refs/!;
-			$dest .= $ref;
-
-			my $link = $cgi->a({
-				-href => href(
-					action=>$dest_action,
-					hash=>$dest
-				)}, $name);
-
-			$markers .= " <span class=\"$class\" title=\"$ref\">" .
-				$link . "</span>";
-		}
-	}
-
-	if ($markers) {
-		return ' <span class="refs">'. $markers . '</span>';
-	} else {
-		return "";
-	}
-}
-
-# format, perhaps shortened and with markers, title line
-sub format_subject_html {
-	my ($long, $short, $href, $extra) = @_;
-	$extra = '' unless defined($extra);
-
-	if (length($short) < length($long)) {
-		$long =~ s/[[:cntrl:]]/?/g;
-		return $cgi->a({-href => $href, -class => "list subject",
-		                -title => to_utf8($long)},
-		       esc_html($short)) . $extra;
-	} else {
-		return $cgi->a({-href => $href, -class => "list subject"},
-		       esc_html($long)) . $extra;
-	}
-}
-
-# Rather than recomputing the url for an email multiple times, we cache it
-# after the first hit. This gives a visible benefit in views where the avatar
-# for the same email is used repeatedly (e.g. shortlog).
-# The cache is shared by all avatar engines (currently gravatar only), which
-# are free to use it as preferred. Since only one avatar engine is used for any
-# given page, there's no risk for cache conflicts.
-our %avatar_cache = ();
-
-# Compute the picon url for a given email, by using the picon search service over at
-# http://www.cs.indiana.edu/picons/search.html
-sub picon_url {
-	my $email = lc shift;
-	if (!$avatar_cache{$email}) {
-		my ($user, $domain) = split('@', $email);
-		$avatar_cache{$email} =
-			"http://www.cs.indiana.edu/cgi-pub/kinzler/piconsearch.cgi/" .
-			"$domain/$user/" .
-			"users+domains+unknown/up/single";
-	}
-	return $avatar_cache{$email};
-}
-
-# Compute the gravatar url for a given email, if it's not in the cache already.
-# Gravatar stores only the part of the URL before the size, since that's the
-# one computationally more expensive. This also allows reuse of the cache for
-# different sizes (for this particular engine).
-sub gravatar_url {
-	my $email = lc shift;
-	my $size = shift;
-	$avatar_cache{$email} ||=
-		"http://www.gravatar.com/avatar/" .
-			Digest::MD5::md5_hex($email) . "?s=";
-	return $avatar_cache{$email} . $size;
-}
-
-# Insert an avatar for the given $email at the given $size if the feature
-# is enabled.
-sub git_get_avatar {
-	my ($email, %opts) = @_;
-	my $pre_white  = ($opts{-pad_before} ? "&nbsp;" : "");
-	my $post_white = ($opts{-pad_after}  ? "&nbsp;" : "");
-	$opts{-size} ||= 'default';
-	my $size = $avatar_size{$opts{-size}} || $avatar_size{'default'};
-	my $url = "";
-	if ($git_avatar eq 'gravatar') {
-		$url = gravatar_url($email, $size);
-	} elsif ($git_avatar eq 'picon') {
-		$url = picon_url($email);
-	}
-	# Other providers can be added by extending the if chain, defining $url
-	# as needed. If no variant puts something in $url, we assume avatars
-	# are completely disabled/unavailable.
-	if ($url) {
-		return $pre_white .
-		       "<img width=\"$size\" " .
-		            "class=\"avatar\" " .
-		            "src=\"$url\" " .
-			    "alt=\"\" " .
-		       "/>" . $post_white;
-	} else {
-		return "";
-	}
-}
-
-sub format_search_author {
-	my ($author, $searchtype, $displaytext) = @_;
-	my $have_search = gitweb_check_feature('search');
-
-	if ($have_search) {
-		my $performed = "";
-		if ($searchtype eq 'author') {
-			$performed = "authored";
-		} elsif ($searchtype eq 'committer') {
-			$performed = "committed";
-		}
-
-		return $cgi->a({-href => href(action=>"search", hash=>$hash,
-				searchtext=>$author,
-				searchtype=>$searchtype), class=>"list",
-				title=>"Search for commits $performed by $author"},
-				$displaytext);
-
-	} else {
-		return $displaytext;
-	}
-}
-
-# format the author name of the given commit with the given tag
-# the author name is chopped and escaped according to the other
-# optional parameters (see chop_str).
-sub format_author_html {
-	my $tag = shift;
-	my $co = shift;
-	my $author = chop_and_escape_str($co->{'author_name'}, @_);
-	return "<$tag class=\"author\">" .
-	       format_search_author($co->{'author_name'}, "author",
-		       git_get_avatar($co->{'author_email'}, -pad_after => 1) .
-		       $author) .
-	       "</$tag>";
-}
-
-# format git diff header line, i.e. "diff --(git|combined|cc) ..."
-sub format_git_diff_header_line {
-	my $line = shift;
-	my $diffinfo = shift;
-	my ($from, $to) = @_;
-
-	if ($diffinfo->{'nparents'}) {
-		# combined diff
-		$line =~ s!^(diff (.*?) )"?.*$!$1!;
-		if ($to->{'href'}) {
-			$line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
-			                 esc_path($to->{'file'}));
-		} else { # file was deleted (no href)
-			$line .= esc_path($to->{'file'});
-		}
-	} else {
-		# "ordinary" diff
-		$line =~ s!^(diff (.*?) )"?a/.*$!$1!;
-		if ($from->{'href'}) {
-			$line .= $cgi->a({-href => $from->{'href'}, -class => "path"},
-			                 'a/' . esc_path($from->{'file'}));
-		} else { # file was added (no href)
-			$line .= 'a/' . esc_path($from->{'file'});
-		}
-		$line .= ' ';
-		if ($to->{'href'}) {
-			$line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
-			                 'b/' . esc_path($to->{'file'}));
-		} else { # file was deleted
-			$line .= 'b/' . esc_path($to->{'file'});
-		}
-	}
-
-	return "<div class=\"diff header\">$line</div>\n";
-}
-
-# format extended diff header line, before patch itself
-sub format_extended_diff_header_line {
-	my $line = shift;
-	my $diffinfo = shift;
-	my ($from, $to) = @_;
-
-	# match <path>
-	if ($line =~ s!^((copy|rename) from ).*$!$1! && $from->{'href'}) {
-		$line .= $cgi->a({-href=>$from->{'href'}, -class=>"path"},
-		                       esc_path($from->{'file'}));
-	}
-	if ($line =~ s!^((copy|rename) to ).*$!$1! && $to->{'href'}) {
-		$line .= $cgi->a({-href=>$to->{'href'}, -class=>"path"},
-		                 esc_path($to->{'file'}));
-	}
-	# match single <mode>
-	if ($line =~ m/\s(\d{6})$/) {
-		$line .= '<span class="info"> (' .
-		         file_type_long($1) .
-		         ')</span>';
-	}
-	# match <hash>
-	if ($line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
-		# can match only for combined diff
-		$line = 'index ';
-		for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
-			if ($from->{'href'}[$i]) {
-				$line .= $cgi->a({-href=>$from->{'href'}[$i],
-				                  -class=>"hash"},
-				                 substr($diffinfo->{'from_id'}[$i],0,7));
-			} else {
-				$line .= '0' x 7;
-			}
-			# separator
-			$line .= ',' if ($i < $diffinfo->{'nparents'} - 1);
-		}
-		$line .= '..';
-		if ($to->{'href'}) {
-			$line .= $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
-			                 substr($diffinfo->{'to_id'},0,7));
-		} else {
-			$line .= '0' x 7;
-		}
-
-	} elsif ($line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
-		# can match only for ordinary diff
-		my ($from_link, $to_link);
-		if ($from->{'href'}) {
-			$from_link = $cgi->a({-href=>$from->{'href'}, -class=>"hash"},
-			                     substr($diffinfo->{'from_id'},0,7));
-		} else {
-			$from_link = '0' x 7;
-		}
-		if ($to->{'href'}) {
-			$to_link = $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
-			                   substr($diffinfo->{'to_id'},0,7));
-		} else {
-			$to_link = '0' x 7;
-		}
-		my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
-		$line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
-	}
-
-	return $line . "<br/>\n";
-}
-
-# format from-file/to-file diff header
-sub format_diff_from_to_header {
-	my ($from_line, $to_line, $diffinfo, $from, $to, @parents) = @_;
-	my $line;
-	my $result = '';
-
-	$line = $from_line;
-	#assert($line =~ m/^---/) if DEBUG;
-	# no extra formatting for "^--- /dev/null"
-	if (! $diffinfo->{'nparents'}) {
-		# ordinary (single parent) diff
-		if ($line =~ m!^--- "?a/!) {
-			if ($from->{'href'}) {
-				$line = '--- a/' .
-				        $cgi->a({-href=>$from->{'href'}, -class=>"path"},
-				                esc_path($from->{'file'}));
-			} else {
-				$line = '--- a/' .
-				        esc_path($from->{'file'});
-			}
-		}
-		$result .= qq!<div class="diff from_file">$line</div>\n!;
-
-	} else {
-		# combined diff (merge commit)
-		for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
-			if ($from->{'href'}[$i]) {
-				$line = '--- ' .
-				        $cgi->a({-href=>href(action=>"blobdiff",
-				                             hash_parent=>$diffinfo->{'from_id'}[$i],
-				                             hash_parent_base=>$parents[$i],
-				                             file_parent=>$from->{'file'}[$i],
-				                             hash=>$diffinfo->{'to_id'},
-				                             hash_base=>$hash,
-				                             file_name=>$to->{'file'}),
-				                 -class=>"path",
-				                 -title=>"diff" . ($i+1)},
-				                $i+1) .
-				        '/' .
-				        $cgi->a({-href=>$from->{'href'}[$i], -class=>"path"},
-				                esc_path($from->{'file'}[$i]));
-			} else {
-				$line = '--- /dev/null';
-			}
-			$result .= qq!<div class="diff from_file">$line</div>\n!;
-		}
-	}
-
-	$line = $to_line;
-	#assert($line =~ m/^\+\+\+/) if DEBUG;
-	# no extra formatting for "^+++ /dev/null"
-	if ($line =~ m!^\+\+\+ "?b/!) {
-		if ($to->{'href'}) {
-			$line = '+++ b/' .
-			        $cgi->a({-href=>$to->{'href'}, -class=>"path"},
-			                esc_path($to->{'file'}));
-		} else {
-			$line = '+++ b/' .
-			        esc_path($to->{'file'});
-		}
-	}
-	$result .= qq!<div class="diff to_file">$line</div>\n!;
-
-	return $result;
-}
-
-# create note for patch simplified by combined diff
-sub format_diff_cc_simplified {
-	my ($diffinfo, @parents) = @_;
-	my $result = '';
-
-	$result .= "<div class=\"diff header\">" .
-	           "diff --cc ";
-	if (!is_deleted($diffinfo)) {
-		$result .= $cgi->a({-href => href(action=>"blob",
-		                                  hash_base=>$hash,
-		                                  hash=>$diffinfo->{'to_id'},
-		                                  file_name=>$diffinfo->{'to_file'}),
-		                    -class => "path"},
-		                   esc_path($diffinfo->{'to_file'}));
-	} else {
-		$result .= esc_path($diffinfo->{'to_file'});
-	}
-	$result .= "</div>\n" . # class="diff header"
-	           "<div class=\"diff nodifferences\">" .
-	           "Simple merge" .
-	           "</div>\n"; # class="diff nodifferences"
-
-	return $result;
-}
-
-# format patch (diff) line (not to be used for diff headers)
-sub format_diff_line {
-	my $line = shift;
-	my ($from, $to) = @_;
-	my $diff_class = "";
-
-	chomp $line;
-
-	if ($from && $to && ref($from->{'href'}) eq "ARRAY") {
-		# combined diff
-		my $prefix = substr($line, 0, scalar @{$from->{'href'}});
-		if ($line =~ m/^\@{3}/) {
-			$diff_class = " chunk_header";
-		} elsif ($line =~ m/^\\/) {
-			$diff_class = " incomplete";
-		} elsif ($prefix =~ tr/+/+/) {
-			$diff_class = " add";
-		} elsif ($prefix =~ tr/-/-/) {
-			$diff_class = " rem";
-		}
-	} else {
-		# assume ordinary diff
-		my $char = substr($line, 0, 1);
-		if ($char eq '+') {
-			$diff_class = " add";
-		} elsif ($char eq '-') {
-			$diff_class = " rem";
-		} elsif ($char eq '@') {
-			$diff_class = " chunk_header";
-		} elsif ($char eq "\\") {
-			$diff_class = " incomplete";
-		}
-	}
-	$line = untabify($line);
-	if ($from && $to && $line =~ m/^\@{2} /) {
-		my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
-			$line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
-
-		$from_lines = 0 unless defined $from_lines;
-		$to_lines   = 0 unless defined $to_lines;
-
-		if ($from->{'href'}) {
-			$from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
-			                     -class=>"list"}, $from_text);
-		}
-		if ($to->{'href'}) {
-			$to_text   = $cgi->a({-href=>"$to->{'href'}#l$to_start",
-			                     -class=>"list"}, $to_text);
-		}
-		$line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
-		        "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
-		return "<div class=\"diff$diff_class\">$line</div>\n";
-	} elsif ($from && $to && $line =~ m/^\@{3}/) {
-		my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
-		my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
-
-		@from_text = split(' ', $ranges);
-		for (my $i = 0; $i < @from_text; ++$i) {
-			($from_start[$i], $from_nlines[$i]) =
-				(split(',', substr($from_text[$i], 1)), 0);
-		}
-
-		$to_text   = pop @from_text;
-		$to_start  = pop @from_start;
-		$to_nlines = pop @from_nlines;
-
-		$line = "<span class=\"chunk_info\">$prefix ";
-		for (my $i = 0; $i < @from_text; ++$i) {
-			if ($from->{'href'}[$i]) {
-				$line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
-				                  -class=>"list"}, $from_text[$i]);
-			} else {
-				$line .= $from_text[$i];
-			}
-			$line .= " ";
-		}
-		if ($to->{'href'}) {
-			$line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
-			                  -class=>"list"}, $to_text);
-		} else {
-			$line .= $to_text;
-		}
-		$line .= " $prefix</span>" .
-		         "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
-		return "<div class=\"diff$diff_class\">$line</div>\n";
-	}
-	return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
-}
-
-# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
-# linked.  Pass the hash of the tree/commit to snapshot.
-sub format_snapshot_links {
-	my ($hash) = @_;
-	my $num_fmts = @snapshot_fmts;
-	if ($num_fmts > 1) {
-		# A parenthesized list of links bearing format names.
-		# e.g. "snapshot (_tar.gz_ _zip_)"
-		return "snapshot (" . join(' ', map
-			$cgi->a({
-				-href => href(
-					action=>"snapshot",
-					hash=>$hash,
-					snapshot_format=>$_
-				)
-			}, $known_snapshot_formats{$_}{'display'})
-		, @snapshot_fmts) . ")";
-	} elsif ($num_fmts == 1) {
-		# A single "snapshot" link whose tooltip bears the format name.
-		# i.e. "_snapshot_"
-		my ($fmt) = @snapshot_fmts;
-		return
-			$cgi->a({
-				-href => href(
-					action=>"snapshot",
-					hash=>$hash,
-					snapshot_format=>$fmt
-				),
-				-title => "in format: $known_snapshot_formats{$fmt}{'display'}"
-			}, "snapshot");
-	} else { # $num_fmts == 0
-		return undef;
-	}
-}
-
-## ----------------------------------------------------------------------
 ## parse to hash functions
 
 sub parse_date {
diff --git a/gitweb/lib/Gitweb/Format.pm b/gitweb/lib/Gitweb/Format.pm
new file mode 100644
index 0000000..dc535bd
--- /dev/null
+++ b/gitweb/lib/Gitweb/Format.pm
@@ -0,0 +1,537 @@
+#!/usr/bin/perl
+#
+# Gitweb::Format -- gitweb's format_* subs package
+#
+# This program is licensed under the GPLv2
+
+package Gitweb::Format;
+
+use strict;
+use warnings;
+use Exporter qw(import);
+
+our @EXPORT = qw(format_log_line_html format_ref_marker format_subject_html
+                 git_get_avatar format_search_author format_author_html
+                 format_git_diff_header_line format_extended_diff_header_line
+                 format_diff_from_to_header format_diff_cc_simplified
+                 format_diff_line format_snapshot_links);
+
+use Gitweb::Config qw($git_avatar gitweb_check_feature @snapshot_fmts
+                      %known_snapshot_formats %avatar_size);
+use Gitweb::Request qw($cgi $action $hash);
+use Gitweb::Escape qw(to_utf8 esc_html esc_path untabify);
+use Gitweb::View qw(href chop_and_escape_str file_type_long);
+use Gitweb::Util qw(is_deleted);
+
+## ----------------------------------------------------------------------
+## functions returning short HTML fragments, or transforming HTML fragments
+## which don't belong to other sections
+
+# format line of commit message.
+sub format_log_line_html {
+	my $line = shift;
+
+	$line = esc_html($line, -nbsp=>1);
+	$line =~ s{\b([0-9a-fA-F]{8,40})\b}{
+		$cgi->a({-href => href(action=>"object", hash=>$1),
+					-class => "text"}, $1);
+	}eg;
+
+	return $line;
+}
+
+# format marker of refs pointing to given object
+
+# the destination action is chosen based on object type and current context:
+# - for annotated tags, we choose the tag view unless it's the current view
+#   already, in which case we go to shortlog view
+# - for other refs, we keep the current view if we're in history, shortlog or
+#   log view, and select shortlog otherwise
+sub format_ref_marker {
+	my ($refs, $id) = @_;
+	my $markers = '';
+
+	if (defined $refs->{$id}) {
+		foreach my $ref (@{$refs->{$id}}) {
+			# this code exploits the fact that non-lightweight tags are the
+			# only indirect objects, and that they are the only objects for which
+			# we want to use tag instead of shortlog as action
+			my ($type, $name) = qw();
+			my $indirect = ($ref =~ s/\^\{\}$//);
+			# e.g. tags/v2.6.11 or heads/next
+			if ($ref =~ m!^(.*?)s?/(.*)$!) {
+				$type = $1;
+				$name = $2;
+			} else {
+				$type = "ref";
+				$name = $ref;
+			}
+
+			my $class = $type;
+			$class .= " indirect" if $indirect;
+
+			my $dest_action = "shortlog";
+
+			if ($indirect) {
+				$dest_action = "tag" unless $action eq "tag";
+			} elsif ($action =~ /^(history|(short)?log)$/) {
+				$dest_action = $action;
+			}
+
+			my $dest = "";
+			$dest .= "refs/" unless $ref =~ m!^refs/!;
+			$dest .= $ref;
+
+			my $link = $cgi->a({
+				-href => href(
+					action=>$dest_action,
+					hash=>$dest
+				)}, $name);
+
+			$markers .= " <span class=\"$class\" title=\"$ref\">" .
+				$link . "</span>";
+		}
+	}
+
+	if ($markers) {
+		return ' <span class="refs">'. $markers . '</span>';
+	} else {
+		return "";
+	}
+}
+
+# format, perhaps shortened and with markers, title line
+sub format_subject_html {
+	my ($long, $short, $href, $extra) = @_;
+	$extra = '' unless defined($extra);
+
+	if (length($short) < length($long)) {
+		$long =~ s/[[:cntrl:]]/?/g;
+		return $cgi->a({-href => $href, -class => "list subject",
+		                -title => to_utf8($long)},
+		       esc_html($short)) . $extra;
+	} else {
+		return $cgi->a({-href => $href, -class => "list subject"},
+		       esc_html($long)) . $extra;
+	}
+}
+
+# Rather than recomputing the url for an email multiple times, we cache it
+# after the first hit. This gives a visible benefit in views where the avatar
+# for the same email is used repeatedly (e.g. shortlog).
+# The cache is shared by all avatar engines (currently gravatar only), which
+# are free to use it as preferred. Since only one avatar engine is used for any
+# given page, there's no risk for cache conflicts.
+our %avatar_cache = ();
+
+# Compute the picon url for a given email, by using the picon search service over at
+# http://www.cs.indiana.edu/picons/search.html
+sub picon_url {
+	my $email = lc shift;
+	if (!$avatar_cache{$email}) {
+		my ($user, $domain) = split('@', $email);
+		$avatar_cache{$email} =
+			"http://www.cs.indiana.edu/cgi-pub/kinzler/piconsearch.cgi/" .
+			"$domain/$user/" .
+			"users+domains+unknown/up/single";
+	}
+	return $avatar_cache{$email};
+}
+
+# Compute the gravatar url for a given email, if it's not in the cache already.
+# Gravatar stores only the part of the URL before the size, since that's the
+# one computationally more expensive. This also allows reuse of the cache for
+# different sizes (for this particular engine).
+sub gravatar_url {
+	my $email = lc shift;
+	my $size = shift;
+	$avatar_cache{$email} ||=
+		"http://www.gravatar.com/avatar/" .
+			Digest::MD5::md5_hex($email) . "?s=";
+	return $avatar_cache{$email} . $size;
+}
+
+# Insert an avatar for the given $email at the given $size if the feature
+# is enabled.
+sub git_get_avatar {
+	my ($email, %opts) = @_;
+	my $pre_white  = ($opts{-pad_before} ? "&nbsp;" : "");
+	my $post_white = ($opts{-pad_after}  ? "&nbsp;" : "");
+	$opts{-size} ||= 'default';
+	my $size = $avatar_size{$opts{-size}} || $avatar_size{'default'};
+	my $url = "";
+	if ($git_avatar eq 'gravatar') {
+		$url = gravatar_url($email, $size);
+	} elsif ($git_avatar eq 'picon') {
+		$url = picon_url($email);
+	}
+	# Other providers can be added by extending the if chain, defining $url
+	# as needed. If no variant puts something in $url, we assume avatars
+	# are completely disabled/unavailable.
+	if ($url) {
+		return $pre_white .
+		       "<img width=\"$size\" " .
+		            "class=\"avatar\" " .
+		            "src=\"$url\" " .
+			    "alt=\"\" " .
+		       "/>" . $post_white;
+	} else {
+		return "";
+	}
+}
+
+sub format_search_author {
+	my ($author, $searchtype, $displaytext) = @_;
+	my $have_search = gitweb_check_feature('search');
+
+	if ($have_search) {
+		my $performed = "";
+		if ($searchtype eq 'author') {
+			$performed = "authored";
+		} elsif ($searchtype eq 'committer') {
+			$performed = "committed";
+		}
+
+		return $cgi->a({-href => href(action=>"search", hash=>$hash,
+				searchtext=>$author,
+				searchtype=>$searchtype), class=>"list",
+				title=>"Search for commits $performed by $author"},
+				$displaytext);
+
+	} else {
+		return $displaytext;
+	}
+}
+
+# format the author name of the given commit with the given tag
+# the author name is chopped and escaped according to the other
+# optional parameters (see chop_str).
+sub format_author_html {
+	my $tag = shift;
+	my $co = shift;
+	my $author = chop_and_escape_str($co->{'author_name'}, @_);
+	return "<$tag class=\"author\">" .
+	       format_search_author($co->{'author_name'}, "author",
+		       git_get_avatar($co->{'author_email'}, -pad_after => 1) .
+		       $author) .
+	       "</$tag>";
+}
+
+# format git diff header line, i.e. "diff --(git|combined|cc) ..."
+sub format_git_diff_header_line {
+	my $line = shift;
+	my $diffinfo = shift;
+	my ($from, $to) = @_;
+
+	if ($diffinfo->{'nparents'}) {
+		# combined diff
+		$line =~ s!^(diff (.*?) )"?.*$!$1!;
+		if ($to->{'href'}) {
+			$line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
+			                 esc_path($to->{'file'}));
+		} else { # file was deleted (no href)
+			$line .= esc_path($to->{'file'});
+		}
+	} else {
+		# "ordinary" diff
+		$line =~ s!^(diff (.*?) )"?a/.*$!$1!;
+		if ($from->{'href'}) {
+			$line .= $cgi->a({-href => $from->{'href'}, -class => "path"},
+			                 'a/' . esc_path($from->{'file'}));
+		} else { # file was added (no href)
+			$line .= 'a/' . esc_path($from->{'file'});
+		}
+		$line .= ' ';
+		if ($to->{'href'}) {
+			$line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
+			                 'b/' . esc_path($to->{'file'}));
+		} else { # file was deleted
+			$line .= 'b/' . esc_path($to->{'file'});
+		}
+	}
+
+	return "<div class=\"diff header\">$line</div>\n";
+}
+
+# format extended diff header line, before patch itself
+sub format_extended_diff_header_line {
+	my $line = shift;
+	my $diffinfo = shift;
+	my ($from, $to) = @_;
+
+	# match <path>
+	if ($line =~ s!^((copy|rename) from ).*$!$1! && $from->{'href'}) {
+		$line .= $cgi->a({-href=>$from->{'href'}, -class=>"path"},
+		                       esc_path($from->{'file'}));
+	}
+	if ($line =~ s!^((copy|rename) to ).*$!$1! && $to->{'href'}) {
+		$line .= $cgi->a({-href=>$to->{'href'}, -class=>"path"},
+		                 esc_path($to->{'file'}));
+	}
+	# match single <mode>
+	if ($line =~ m/\s(\d{6})$/) {
+		$line .= '<span class="info"> (' .
+		         file_type_long($1) .
+		         ')</span>';
+	}
+	# match <hash>
+	if ($line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
+		# can match only for combined diff
+		$line = 'index ';
+		for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
+			if ($from->{'href'}[$i]) {
+				$line .= $cgi->a({-href=>$from->{'href'}[$i],
+				                  -class=>"hash"},
+				                 substr($diffinfo->{'from_id'}[$i],0,7));
+			} else {
+				$line .= '0' x 7;
+			}
+			# separator
+			$line .= ',' if ($i < $diffinfo->{'nparents'} - 1);
+		}
+		$line .= '..';
+		if ($to->{'href'}) {
+			$line .= $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
+			                 substr($diffinfo->{'to_id'},0,7));
+		} else {
+			$line .= '0' x 7;
+		}
+
+	} elsif ($line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
+		# can match only for ordinary diff
+		my ($from_link, $to_link);
+		if ($from->{'href'}) {
+			$from_link = $cgi->a({-href=>$from->{'href'}, -class=>"hash"},
+			                     substr($diffinfo->{'from_id'},0,7));
+		} else {
+			$from_link = '0' x 7;
+		}
+		if ($to->{'href'}) {
+			$to_link = $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
+			                   substr($diffinfo->{'to_id'},0,7));
+		} else {
+			$to_link = '0' x 7;
+		}
+		my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
+		$line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
+	}
+
+	return $line . "<br/>\n";
+}
+
+# format from-file/to-file diff header
+sub format_diff_from_to_header {
+	my ($from_line, $to_line, $diffinfo, $from, $to, @parents) = @_;
+	my $line;
+	my $result = '';
+
+	$line = $from_line;
+	#assert($line =~ m/^---/) if DEBUG;
+	# no extra formatting for "^--- /dev/null"
+	if (! $diffinfo->{'nparents'}) {
+		# ordinary (single parent) diff
+		if ($line =~ m!^--- "?a/!) {
+			if ($from->{'href'}) {
+				$line = '--- a/' .
+				        $cgi->a({-href=>$from->{'href'}, -class=>"path"},
+				                esc_path($from->{'file'}));
+			} else {
+				$line = '--- a/' .
+				        esc_path($from->{'file'});
+			}
+		}
+		$result .= qq!<div class="diff from_file">$line</div>\n!;
+
+	} else {
+		# combined diff (merge commit)
+		for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
+			if ($from->{'href'}[$i]) {
+				$line = '--- ' .
+				        $cgi->a({-href=>href(action=>"blobdiff",
+				                             hash_parent=>$diffinfo->{'from_id'}[$i],
+				                             hash_parent_base=>$parents[$i],
+				                             file_parent=>$from->{'file'}[$i],
+				                             hash=>$diffinfo->{'to_id'},
+				                             hash_base=>$hash,
+				                             file_name=>$to->{'file'}),
+				                 -class=>"path",
+				                 -title=>"diff" . ($i+1)},
+				                $i+1) .
+				        '/' .
+				        $cgi->a({-href=>$from->{'href'}[$i], -class=>"path"},
+				                esc_path($from->{'file'}[$i]));
+			} else {
+				$line = '--- /dev/null';
+			}
+			$result .= qq!<div class="diff from_file">$line</div>\n!;
+		}
+	}
+
+	$line = $to_line;
+	#assert($line =~ m/^\+\+\+/) if DEBUG;
+	# no extra formatting for "^+++ /dev/null"
+	if ($line =~ m!^\+\+\+ "?b/!) {
+		if ($to->{'href'}) {
+			$line = '+++ b/' .
+			        $cgi->a({-href=>$to->{'href'}, -class=>"path"},
+			                esc_path($to->{'file'}));
+		} else {
+			$line = '+++ b/' .
+			        esc_path($to->{'file'});
+		}
+	}
+	$result .= qq!<div class="diff to_file">$line</div>\n!;
+
+	return $result;
+}
+
+# create note for patch simplified by combined diff
+sub format_diff_cc_simplified {
+	my ($diffinfo, @parents) = @_;
+	my $result = '';
+
+	$result .= "<div class=\"diff header\">" .
+	           "diff --cc ";
+	if (!is_deleted($diffinfo)) {
+		$result .= $cgi->a({-href => href(action=>"blob",
+		                                  hash_base=>$hash,
+		                                  hash=>$diffinfo->{'to_id'},
+		                                  file_name=>$diffinfo->{'to_file'}),
+		                    -class => "path"},
+		                   esc_path($diffinfo->{'to_file'}));
+	} else {
+		$result .= esc_path($diffinfo->{'to_file'});
+	}
+	$result .= "</div>\n" . # class="diff header"
+	           "<div class=\"diff nodifferences\">" .
+	           "Simple merge" .
+	           "</div>\n"; # class="diff nodifferences"
+
+	return $result;
+}
+
+# format patch (diff) line (not to be used for diff headers)
+sub format_diff_line {
+	my $line = shift;
+	my ($from, $to) = @_;
+	my $diff_class = "";
+
+	chomp $line;
+
+	if ($from && $to && ref($from->{'href'}) eq "ARRAY") {
+		# combined diff
+		my $prefix = substr($line, 0, scalar @{$from->{'href'}});
+		if ($line =~ m/^\@{3}/) {
+			$diff_class = " chunk_header";
+		} elsif ($line =~ m/^\\/) {
+			$diff_class = " incomplete";
+		} elsif ($prefix =~ tr/+/+/) {
+			$diff_class = " add";
+		} elsif ($prefix =~ tr/-/-/) {
+			$diff_class = " rem";
+		}
+	} else {
+		# assume ordinary diff
+		my $char = substr($line, 0, 1);
+		if ($char eq '+') {
+			$diff_class = " add";
+		} elsif ($char eq '-') {
+			$diff_class = " rem";
+		} elsif ($char eq '@') {
+			$diff_class = " chunk_header";
+		} elsif ($char eq "\\") {
+			$diff_class = " incomplete";
+		}
+	}
+	$line = untabify($line);
+	if ($from && $to && $line =~ m/^\@{2} /) {
+		my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
+			$line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
+
+		$from_lines = 0 unless defined $from_lines;
+		$to_lines   = 0 unless defined $to_lines;
+
+		if ($from->{'href'}) {
+			$from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
+			                     -class=>"list"}, $from_text);
+		}
+		if ($to->{'href'}) {
+			$to_text   = $cgi->a({-href=>"$to->{'href'}#l$to_start",
+			                     -class=>"list"}, $to_text);
+		}
+		$line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
+		        "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
+		return "<div class=\"diff$diff_class\">$line</div>\n";
+	} elsif ($from && $to && $line =~ m/^\@{3}/) {
+		my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
+		my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
+
+		@from_text = split(' ', $ranges);
+		for (my $i = 0; $i < @from_text; ++$i) {
+			($from_start[$i], $from_nlines[$i]) =
+				(split(',', substr($from_text[$i], 1)), 0);
+		}
+
+		$to_text   = pop @from_text;
+		$to_start  = pop @from_start;
+		$to_nlines = pop @from_nlines;
+
+		$line = "<span class=\"chunk_info\">$prefix ";
+		for (my $i = 0; $i < @from_text; ++$i) {
+			if ($from->{'href'}[$i]) {
+				$line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
+				                  -class=>"list"}, $from_text[$i]);
+			} else {
+				$line .= $from_text[$i];
+			}
+			$line .= " ";
+		}
+		if ($to->{'href'}) {
+			$line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
+			                  -class=>"list"}, $to_text);
+		} else {
+			$line .= $to_text;
+		}
+		$line .= " $prefix</span>" .
+		         "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
+		return "<div class=\"diff$diff_class\">$line</div>\n";
+	}
+	return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
+}
+
+# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
+# linked.  Pass the hash of the tree/commit to snapshot.
+sub format_snapshot_links {
+	my ($hash) = @_;
+	my $num_fmts = @snapshot_fmts;
+	if ($num_fmts > 1) {
+		# A parenthesized list of links bearing format names.
+		# e.g. "snapshot (_tar.gz_ _zip_)"
+		return "snapshot (" . join(' ', map
+			$cgi->a({
+				-href => href(
+					action=>"snapshot",
+					hash=>$hash,
+					snapshot_format=>$_
+				)
+			}, $known_snapshot_formats{$_}{'display'})
+		, @snapshot_fmts) . ")";
+	} elsif ($num_fmts == 1) {
+		# A single "snapshot" link whose tooltip bears the format name.
+		# i.e. "_snapshot_"
+		my ($fmt) = @snapshot_fmts;
+		return
+			$cgi->a({
+				-href => href(
+					action=>"snapshot",
+					hash=>$hash,
+					snapshot_format=>$fmt
+				),
+				-title => "in format: $known_snapshot_formats{$fmt}{'display'}"
+			}, "snapshot");
+	} else { # $num_fmts == 0
+		return undef;
+	}
+}
+
+1;
-- 
1.7.1.455.g8f441

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCHv2 GSOC 11/11] gitweb: Create Gitweb::Parse module
  2010-07-15  7:29 [PATCHv2 00/11] Splitting gitweb Pavan Kumar Sunkara
                   ` (9 preceding siblings ...)
  2010-07-15  7:29 ` [PATCHv2 GSOC 10/11] gitweb: Create Gitweb::Format module Pavan Kumar Sunkara
@ 2010-07-15  7:29 ` Pavan Kumar Sunkara
  2010-07-19 14:55   ` Jakub Narebski
  2010-08-01 20:44 ` [PATCHv2 00/11] Splitting gitweb Sverre Rabbelier
  11 siblings, 1 reply; 27+ messages in thread
From: Pavan Kumar Sunkara @ 2010-07-15  7:29 UTC (permalink / raw)
  To: git, jnareb, chriscool, pasky; +Cc: Pavan Kumar Sunkara

Create Gitweb::Parse module in 'gitweb/lib/Gitweb/Parse.pm'
to store the subroutines which related to parsing functions
required for gitweb.

This module depends on Git.pm, Escape.pm, View.pm and Util.pm.

Subroutines moved:
	unquote
	parse_date
	parse_tag
	parse_commit_text
	parse_commit
	parse_commits
	parse_difftree_raw_line
	parsed_difftree_line
	parse_ls_tree_line
	parse_from_to_diffinfo

Update 'gitweb/Makefile' to install Gitweb::Parse alongside gitweb.

Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
---
 gitweb/Makefile            |    1 +
 gitweb/gitweb.perl         |  392 +-----------------------------------------
 gitweb/lib/Gitweb/Parse.pm |  414 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 416 insertions(+), 391 deletions(-)
 create mode 100644 gitweb/lib/Gitweb/Parse.pm

diff --git a/gitweb/Makefile b/gitweb/Makefile
index f1803f5..1c1bdca 100644
--- a/gitweb/Makefile
+++ b/gitweb/Makefile
@@ -120,6 +120,7 @@ GITWEB_MODULES += lib/Gitweb/RepoConfig.pm
 GITWEB_MODULES += lib/Gitweb/View.pm
 GITWEB_MODULES += lib/Gitweb/Util.pm
 GITWEB_MODULES += lib/Gitweb/Format.pm
+GITWEB_MODULES += lib/Gitweb/Parse.pm
 
 GITWEB_REPLACE = \
 	-e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 3de3de5..efa9bdb 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -33,6 +33,7 @@ use Gitweb::RepoConfig;
 use Gitweb::View;
 use Gitweb::Util;
 use Gitweb::Format;
+use Gitweb::Parse;
 
 BEGIN {
 	CGI->compile() if $ENV{'MOD_PERL'};
@@ -568,397 +569,6 @@ sub validate_refname {
 	return $input;
 }
 
-# git may return quoted and escaped filenames
-sub unquote {
-	my $str = shift;
-
-	sub unq {
-		my $seq = shift;
-		my %es = ( # character escape codes, aka escape sequences
-			't' => "\t",   # tab            (HT, TAB)
-			'n' => "\n",   # newline        (NL)
-			'r' => "\r",   # return         (CR)
-			'f' => "\f",   # form feed      (FF)
-			'b' => "\b",   # backspace      (BS)
-			'a' => "\a",   # alarm (bell)   (BEL)
-			'e' => "\e",   # escape         (ESC)
-			'v' => "\013", # vertical tab   (VT)
-		);
-
-		if ($seq =~ m/^[0-7]{1,3}$/) {
-			# octal char sequence
-			return chr(oct($seq));
-		} elsif (exists $es{$seq}) {
-			# C escape sequence, aka character escape code
-			return $es{$seq};
-		}
-		# quoted ordinary character
-		return $seq;
-	}
-
-	if ($str =~ m/^"(.*)"$/) {
-		# needs unquoting
-		$str = $1;
-		$str =~ s/\\([^0-7]|[0-7]{1,3})/unq($1)/eg;
-	}
-	return $str;
-}
-
-## ----------------------------------------------------------------------
-## parse to hash functions
-
-sub parse_date {
-	my $epoch = shift;
-	my $tz = shift || "-0000";
-
-	my %date;
-	my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
-	my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
-	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
-	$date{'hour'} = $hour;
-	$date{'minute'} = $min;
-	$date{'mday'} = $mday;
-	$date{'day'} = $days[$wday];
-	$date{'month'} = $months[$mon];
-	$date{'rfc2822'}   = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
-	                     $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
-	$date{'mday-time'} = sprintf "%d %s %02d:%02d",
-	                     $mday, $months[$mon], $hour ,$min;
-	$date{'iso-8601'}  = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
-	                     1900+$year, 1+$mon, $mday, $hour ,$min, $sec;
-
-	$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
-	my $local = $epoch + ((int $1 + ($2/60)) * 3600);
-	($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
-	$date{'hour_local'} = $hour;
-	$date{'minute_local'} = $min;
-	$date{'tz_local'} = $tz;
-	$date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
-	                          1900+$year, $mon+1, $mday,
-	                          $hour, $min, $sec, $tz);
-	return %date;
-}
-
-sub parse_tag {
-	my $tag_id = shift;
-	my %tag;
-	my @comment;
-
-	open my $fd, "-|", git_cmd(), "cat-file", "tag", $tag_id or return;
-	$tag{'id'} = $tag_id;
-	while (my $line = <$fd>) {
-		chomp $line;
-		if ($line =~ m/^object ([0-9a-fA-F]{40})$/) {
-			$tag{'object'} = $1;
-		} elsif ($line =~ m/^type (.+)$/) {
-			$tag{'type'} = $1;
-		} elsif ($line =~ m/^tag (.+)$/) {
-			$tag{'name'} = $1;
-		} elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
-			$tag{'author'} = $1;
-			$tag{'author_epoch'} = $2;
-			$tag{'author_tz'} = $3;
-			if ($tag{'author'} =~ m/^([^<]+) <([^>]*)>/) {
-				$tag{'author_name'}  = $1;
-				$tag{'author_email'} = $2;
-			} else {
-				$tag{'author_name'} = $tag{'author'};
-			}
-		} elsif ($line =~ m/--BEGIN/) {
-			push @comment, $line;
-			last;
-		} elsif ($line eq "") {
-			last;
-		}
-	}
-	push @comment, <$fd>;
-	$tag{'comment'} = \@comment;
-	close $fd or return;
-	if (!defined $tag{'name'}) {
-		return
-	};
-	return %tag
-}
-
-sub parse_commit_text {
-	my ($commit_text, $withparents) = @_;
-	my @commit_lines = split '\n', $commit_text;
-	my %co;
-
-	pop @commit_lines; # Remove '\0'
-
-	if (! @commit_lines) {
-		return;
-	}
-
-	my $header = shift @commit_lines;
-	if ($header !~ m/^[0-9a-fA-F]{40}/) {
-		return;
-	}
-	($co{'id'}, my @parents) = split ' ', $header;
-	while (my $line = shift @commit_lines) {
-		last if $line eq "\n";
-		if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
-			$co{'tree'} = $1;
-		} elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) {
-			push @parents, $1;
-		} elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
-			$co{'author'} = to_utf8($1);
-			$co{'author_epoch'} = $2;
-			$co{'author_tz'} = $3;
-			if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
-				$co{'author_name'}  = $1;
-				$co{'author_email'} = $2;
-			} else {
-				$co{'author_name'} = $co{'author'};
-			}
-		} elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
-			$co{'committer'} = to_utf8($1);
-			$co{'committer_epoch'} = $2;
-			$co{'committer_tz'} = $3;
-			if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
-				$co{'committer_name'}  = $1;
-				$co{'committer_email'} = $2;
-			} else {
-				$co{'committer_name'} = $co{'committer'};
-			}
-		}
-	}
-	if (!defined $co{'tree'}) {
-		return;
-	};
-	$co{'parents'} = \@parents;
-	$co{'parent'} = $parents[0];
-
-	foreach my $title (@commit_lines) {
-		$title =~ s/^    //;
-		if ($title ne "") {
-			$co{'title'} = chop_str($title, 80, 5);
-			# remove leading stuff of merges to make the interesting part visible
-			if (length($title) > 50) {
-				$title =~ s/^Automatic //;
-				$title =~ s/^merge (of|with) /Merge ... /i;
-				if (length($title) > 50) {
-					$title =~ s/(http|rsync):\/\///;
-				}
-				if (length($title) > 50) {
-					$title =~ s/(master|www|rsync)\.//;
-				}
-				if (length($title) > 50) {
-					$title =~ s/kernel.org:?//;
-				}
-				if (length($title) > 50) {
-					$title =~ s/\/pub\/scm//;
-				}
-			}
-			$co{'title_short'} = chop_str($title, 50, 5);
-			last;
-		}
-	}
-	if (! defined $co{'title'} || $co{'title'} eq "") {
-		$co{'title'} = $co{'title_short'} = '(no commit message)';
-	}
-	# remove added spaces
-	foreach my $line (@commit_lines) {
-		$line =~ s/^    //;
-	}
-	$co{'comment'} = \@commit_lines;
-
-	my $age = time - $co{'committer_epoch'};
-	$co{'age'} = $age;
-	$co{'age_string'} = age_string($age);
-	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'});
-	if ($age > 60*60*24*7*2) {
-		$co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
-		$co{'age_string_age'} = $co{'age_string'};
-	} else {
-		$co{'age_string_date'} = $co{'age_string'};
-		$co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
-	}
-	return %co;
-}
-
-sub parse_commit {
-	my ($commit_id) = @_;
-	my %co;
-
-	local $/ = "\0";
-
-	open my $fd, "-|", git_cmd(), "rev-list",
-		"--parents",
-		"--header",
-		"--max-count=1",
-		$commit_id,
-		"--",
-		or die_error(500, "Open git-rev-list failed");
-	%co = parse_commit_text(<$fd>, 1);
-	close $fd;
-
-	return %co;
-}
-
-sub parse_commits {
-	my ($commit_id, $maxcount, $skip, $filename, @args) = @_;
-	my @cos;
-
-	$maxcount ||= 1;
-	$skip ||= 0;
-
-	local $/ = "\0";
-
-	open my $fd, "-|", git_cmd(), "rev-list",
-		"--header",
-		@args,
-		("--max-count=" . $maxcount),
-		("--skip=" . $skip),
-		@extra_options,
-		$commit_id,
-		"--",
-		($filename ? ($filename) : ())
-		or die_error(500, "Open git-rev-list failed");
-	while (my $line = <$fd>) {
-		my %co = parse_commit_text($line);
-		push @cos, \%co;
-	}
-	close $fd;
-
-	return wantarray ? @cos : \@cos;
-}
-
-# parse line of git-diff-tree "raw" output
-sub parse_difftree_raw_line {
-	my $line = shift;
-	my %res;
-
-	# ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M	ls-files.c'
-	# ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M	rev-tree.c'
-	if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) {
-		$res{'from_mode'} = $1;
-		$res{'to_mode'} = $2;
-		$res{'from_id'} = $3;
-		$res{'to_id'} = $4;
-		$res{'status'} = $5;
-		$res{'similarity'} = $6;
-		if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied
-			($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7);
-		} else {
-			$res{'from_file'} = $res{'to_file'} = $res{'file'} = unquote($7);
-		}
-	}
-	# '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR	git-gui/git-gui.sh'
-	# combined diff (for merge commit)
-	elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) {
-		$res{'nparents'}  = length($1);
-		$res{'from_mode'} = [ split(' ', $2) ];
-		$res{'to_mode'} = pop @{$res{'from_mode'}};
-		$res{'from_id'} = [ split(' ', $3) ];
-		$res{'to_id'} = pop @{$res{'from_id'}};
-		$res{'status'} = [ split('', $4) ];
-		$res{'to_file'} = unquote($5);
-	}
-	# 'c512b523472485aef4fff9e57b229d9d243c967f'
-	elsif ($line =~ m/^([0-9a-fA-F]{40})$/) {
-		$res{'commit'} = $1;
-	}
-
-	return wantarray ? %res : \%res;
-}
-
-# wrapper: return parsed line of git-diff-tree "raw" output
-# (the argument might be raw line, or parsed info)
-sub parsed_difftree_line {
-	my $line_or_ref = shift;
-
-	if (ref($line_or_ref) eq "HASH") {
-		# pre-parsed (or generated by hand)
-		return $line_or_ref;
-	} else {
-		return parse_difftree_raw_line($line_or_ref);
-	}
-}
-
-# parse line of git-ls-tree output
-sub parse_ls_tree_line {
-	my $line = shift;
-	my %opts = @_;
-	my %res;
-
-	if ($opts{'-l'}) {
-		#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa   16717	panic.c'
-		$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40}) +(-|[0-9]+)\t(.+)$/s;
-
-		$res{'mode'} = $1;
-		$res{'type'} = $2;
-		$res{'hash'} = $3;
-		$res{'size'} = $4;
-		if ($opts{'-z'}) {
-			$res{'name'} = $5;
-		} else {
-			$res{'name'} = unquote($5);
-		}
-	} else {
-		#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa	panic.c'
-		$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
-
-		$res{'mode'} = $1;
-		$res{'type'} = $2;
-		$res{'hash'} = $3;
-		if ($opts{'-z'}) {
-			$res{'name'} = $4;
-		} else {
-			$res{'name'} = unquote($4);
-		}
-	}
-
-	return wantarray ? %res : \%res;
-}
-
-# generates _two_ hashes, references to which are passed as 2 and 3 argument
-sub parse_from_to_diffinfo {
-	my ($diffinfo, $from, $to, @parents) = @_;
-
-	if ($diffinfo->{'nparents'}) {
-		# combined diff
-		$from->{'file'} = [];
-		$from->{'href'} = [];
-		fill_from_file_info($diffinfo, @parents)
-			unless exists $diffinfo->{'from_file'};
-		for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
-			$from->{'file'}[$i] =
-				defined $diffinfo->{'from_file'}[$i] ?
-				        $diffinfo->{'from_file'}[$i] :
-				        $diffinfo->{'to_file'};
-			if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
-				$from->{'href'}[$i] = href(action=>"blob",
-				                           hash_base=>$parents[$i],
-				                           hash=>$diffinfo->{'from_id'}[$i],
-				                           file_name=>$from->{'file'}[$i]);
-			} else {
-				$from->{'href'}[$i] = undef;
-			}
-		}
-	} else {
-		# ordinary (not combined) diff
-		$from->{'file'} = $diffinfo->{'from_file'};
-		if ($diffinfo->{'status'} ne "A") { # not new (added) file
-			$from->{'href'} = href(action=>"blob", hash_base=>$hash_parent,
-			                       hash=>$diffinfo->{'from_id'},
-			                       file_name=>$from->{'file'});
-		} else {
-			delete $from->{'href'};
-		}
-	}
-
-	$to->{'file'} = $diffinfo->{'to_file'};
-	if (!is_deleted($diffinfo)) { # file exists in result
-		$to->{'href'} = href(action=>"blob", hash_base=>$hash,
-		                     hash=>$diffinfo->{'to_id'},
-		                     file_name=>$to->{'file'});
-	} else {
-		delete $to->{'href'};
-	}
-}
-
 ## ......................................................................
 ## functions printing or outputting HTML: div
 
diff --git a/gitweb/lib/Gitweb/Parse.pm b/gitweb/lib/Gitweb/Parse.pm
new file mode 100644
index 0000000..6eeef2c
--- /dev/null
+++ b/gitweb/lib/Gitweb/Parse.pm
@@ -0,0 +1,414 @@
+#!/usr/bin/perl
+#
+# Gitweb::Parse -- gitweb's parse_* subs package
+#
+# This program is licensed under the GPLv2
+
+package Gitweb::Parse;
+
+use strict;
+use warnings;
+use Exporter qw(import);
+
+our @EXPORT = qw(parse_date parse_tag parse_commit_text parse_commit
+                 parse_commits parse_difftree_raw_line parsed_difftree_line
+                 parse_ls_tree_line parse_from_to_diffinfo unquote);
+
+use Gitweb::Git qw(git_cmd);
+use Gitweb::Request qw(@extra_options $hash $hash_parent);
+use Gitweb::Escape qw(to_utf8);
+use Gitweb::View qw(href chop_str age_string die_error);
+use Gitweb::Util qw(fill_from_file_info is_deleted);
+
+# git may return quoted and escaped filenames
+sub unquote {
+	my $str = shift;
+
+	sub unq {
+		my $seq = shift;
+		my %es = ( # character escape codes, aka escape sequences
+			't' => "\t",   # tab            (HT, TAB)
+			'n' => "\n",   # newline        (NL)
+			'r' => "\r",   # return         (CR)
+			'f' => "\f",   # form feed      (FF)
+			'b' => "\b",   # backspace      (BS)
+			'a' => "\a",   # alarm (bell)   (BEL)
+			'e' => "\e",   # escape         (ESC)
+			'v' => "\013", # vertical tab   (VT)
+		);
+
+		if ($seq =~ m/^[0-7]{1,3}$/) {
+			# octal char sequence
+			return chr(oct($seq));
+		} elsif (exists $es{$seq}) {
+			# C escape sequence, aka character escape code
+			return $es{$seq};
+		}
+		# quoted ordinary character
+		return $seq;
+	}
+
+	if ($str =~ m/^"(.*)"$/) {
+		# needs unquoting
+		$str = $1;
+		$str =~ s/\\([^0-7]|[0-7]{1,3})/unq($1)/eg;
+	}
+	return $str;
+}
+
+## ----------------------------------------------------------------------
+## parse to hash functions
+
+sub parse_date {
+	my $epoch = shift;
+	my $tz = shift || "-0000";
+
+	my %date;
+	my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
+	my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
+	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
+	$date{'hour'} = $hour;
+	$date{'minute'} = $min;
+	$date{'mday'} = $mday;
+	$date{'day'} = $days[$wday];
+	$date{'month'} = $months[$mon];
+	$date{'rfc2822'}   = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
+	                     $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
+	$date{'mday-time'} = sprintf "%d %s %02d:%02d",
+	                     $mday, $months[$mon], $hour ,$min;
+	$date{'iso-8601'}  = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
+	                     1900+$year, 1+$mon, $mday, $hour ,$min, $sec;
+
+	$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
+	my $local = $epoch + ((int $1 + ($2/60)) * 3600);
+	($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
+	$date{'hour_local'} = $hour;
+	$date{'minute_local'} = $min;
+	$date{'tz_local'} = $tz;
+	$date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
+	                          1900+$year, $mon+1, $mday,
+	                          $hour, $min, $sec, $tz);
+	return %date;
+}
+
+sub parse_tag {
+	my $tag_id = shift;
+	my %tag;
+	my @comment;
+
+	open my $fd, "-|", git_cmd(), "cat-file", "tag", $tag_id or return;
+	$tag{'id'} = $tag_id;
+	while (my $line = <$fd>) {
+		chomp $line;
+		if ($line =~ m/^object ([0-9a-fA-F]{40})$/) {
+			$tag{'object'} = $1;
+		} elsif ($line =~ m/^type (.+)$/) {
+			$tag{'type'} = $1;
+		} elsif ($line =~ m/^tag (.+)$/) {
+			$tag{'name'} = $1;
+		} elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
+			$tag{'author'} = $1;
+			$tag{'author_epoch'} = $2;
+			$tag{'author_tz'} = $3;
+			if ($tag{'author'} =~ m/^([^<]+) <([^>]*)>/) {
+				$tag{'author_name'}  = $1;
+				$tag{'author_email'} = $2;
+			} else {
+				$tag{'author_name'} = $tag{'author'};
+			}
+		} elsif ($line =~ m/--BEGIN/) {
+			push @comment, $line;
+			last;
+		} elsif ($line eq "") {
+			last;
+		}
+	}
+	push @comment, <$fd>;
+	$tag{'comment'} = \@comment;
+	close $fd or return;
+	if (!defined $tag{'name'}) {
+		return
+	};
+	return %tag
+}
+
+sub parse_commit_text {
+	my ($commit_text, $withparents) = @_;
+	my @commit_lines = split '\n', $commit_text;
+	my %co;
+
+	pop @commit_lines; # Remove '\0'
+
+	if (! @commit_lines) {
+		return;
+	}
+
+	my $header = shift @commit_lines;
+	if ($header !~ m/^[0-9a-fA-F]{40}/) {
+		return;
+	}
+	($co{'id'}, my @parents) = split ' ', $header;
+	while (my $line = shift @commit_lines) {
+		last if $line eq "\n";
+		if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
+			$co{'tree'} = $1;
+		} elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) {
+			push @parents, $1;
+		} elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
+			$co{'author'} = to_utf8($1);
+			$co{'author_epoch'} = $2;
+			$co{'author_tz'} = $3;
+			if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
+				$co{'author_name'}  = $1;
+				$co{'author_email'} = $2;
+			} else {
+				$co{'author_name'} = $co{'author'};
+			}
+		} elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
+			$co{'committer'} = to_utf8($1);
+			$co{'committer_epoch'} = $2;
+			$co{'committer_tz'} = $3;
+			if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
+				$co{'committer_name'}  = $1;
+				$co{'committer_email'} = $2;
+			} else {
+				$co{'committer_name'} = $co{'committer'};
+			}
+		}
+	}
+	if (!defined $co{'tree'}) {
+		return;
+	};
+	$co{'parents'} = \@parents;
+	$co{'parent'} = $parents[0];
+
+	foreach my $title (@commit_lines) {
+		$title =~ s/^    //;
+		if ($title ne "") {
+			$co{'title'} = chop_str($title, 80, 5);
+			# remove leading stuff of merges to make the interesting part visible
+			if (length($title) > 50) {
+				$title =~ s/^Automatic //;
+				$title =~ s/^merge (of|with) /Merge ... /i;
+				if (length($title) > 50) {
+					$title =~ s/(http|rsync):\/\///;
+				}
+				if (length($title) > 50) {
+					$title =~ s/(master|www|rsync)\.//;
+				}
+				if (length($title) > 50) {
+					$title =~ s/kernel.org:?//;
+				}
+				if (length($title) > 50) {
+					$title =~ s/\/pub\/scm//;
+				}
+			}
+			$co{'title_short'} = chop_str($title, 50, 5);
+			last;
+		}
+	}
+	if (! defined $co{'title'} || $co{'title'} eq "") {
+		$co{'title'} = $co{'title_short'} = '(no commit message)';
+	}
+	# remove added spaces
+	foreach my $line (@commit_lines) {
+		$line =~ s/^    //;
+	}
+	$co{'comment'} = \@commit_lines;
+
+	my $age = time - $co{'committer_epoch'};
+	$co{'age'} = $age;
+	$co{'age_string'} = age_string($age);
+	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'});
+	if ($age > 60*60*24*7*2) {
+		$co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
+		$co{'age_string_age'} = $co{'age_string'};
+	} else {
+		$co{'age_string_date'} = $co{'age_string'};
+		$co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
+	}
+	return %co;
+}
+
+sub parse_commit {
+	my ($commit_id) = @_;
+	my %co;
+
+	local $/ = "\0";
+
+	open my $fd, "-|", git_cmd(), "rev-list",
+		"--parents",
+		"--header",
+		"--max-count=1",
+		$commit_id,
+		"--",
+		or die_error(500, "Open git-rev-list failed");
+	%co = parse_commit_text(<$fd>, 1);
+	close $fd;
+
+	return %co;
+}
+
+sub parse_commits {
+	my ($commit_id, $maxcount, $skip, $filename, @args) = @_;
+	my @cos;
+
+	$maxcount ||= 1;
+	$skip ||= 0;
+
+	local $/ = "\0";
+
+	open my $fd, "-|", git_cmd(), "rev-list",
+		"--header",
+		@args,
+		("--max-count=" . $maxcount),
+		("--skip=" . $skip),
+		@extra_options,
+		$commit_id,
+		"--",
+		($filename ? ($filename) : ())
+		or die_error(500, "Open git-rev-list failed");
+	while (my $line = <$fd>) {
+		my %co = parse_commit_text($line);
+		push @cos, \%co;
+	}
+	close $fd;
+
+	return wantarray ? @cos : \@cos;
+}
+
+# parse line of git-diff-tree "raw" output
+sub parse_difftree_raw_line {
+	my $line = shift;
+	my %res;
+
+	# ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M	ls-files.c'
+	# ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M	rev-tree.c'
+	if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) {
+		$res{'from_mode'} = $1;
+		$res{'to_mode'} = $2;
+		$res{'from_id'} = $3;
+		$res{'to_id'} = $4;
+		$res{'status'} = $5;
+		$res{'similarity'} = $6;
+		if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied
+			($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7);
+		} else {
+			$res{'from_file'} = $res{'to_file'} = $res{'file'} = unquote($7);
+		}
+	}
+	# '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR	git-gui/git-gui.sh'
+	# combined diff (for merge commit)
+	elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) {
+		$res{'nparents'}  = length($1);
+		$res{'from_mode'} = [ split(' ', $2) ];
+		$res{'to_mode'} = pop @{$res{'from_mode'}};
+		$res{'from_id'} = [ split(' ', $3) ];
+		$res{'to_id'} = pop @{$res{'from_id'}};
+		$res{'status'} = [ split('', $4) ];
+		$res{'to_file'} = unquote($5);
+	}
+	# 'c512b523472485aef4fff9e57b229d9d243c967f'
+	elsif ($line =~ m/^([0-9a-fA-F]{40})$/) {
+		$res{'commit'} = $1;
+	}
+
+	return wantarray ? %res : \%res;
+}
+
+# wrapper: return parsed line of git-diff-tree "raw" output
+# (the argument might be raw line, or parsed info)
+sub parsed_difftree_line {
+	my $line_or_ref = shift;
+
+	if (ref($line_or_ref) eq "HASH") {
+		# pre-parsed (or generated by hand)
+		return $line_or_ref;
+	} else {
+		return parse_difftree_raw_line($line_or_ref);
+	}
+}
+
+# parse line of git-ls-tree output
+sub parse_ls_tree_line {
+	my $line = shift;
+	my %opts = @_;
+	my %res;
+
+	if ($opts{'-l'}) {
+		#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa   16717	panic.c'
+		$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40}) +(-|[0-9]+)\t(.+)$/s;
+
+		$res{'mode'} = $1;
+		$res{'type'} = $2;
+		$res{'hash'} = $3;
+		$res{'size'} = $4;
+		if ($opts{'-z'}) {
+			$res{'name'} = $5;
+		} else {
+			$res{'name'} = unquote($5);
+		}
+	} else {
+		#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa	panic.c'
+		$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
+
+		$res{'mode'} = $1;
+		$res{'type'} = $2;
+		$res{'hash'} = $3;
+		if ($opts{'-z'}) {
+			$res{'name'} = $4;
+		} else {
+			$res{'name'} = unquote($4);
+		}
+	}
+
+	return wantarray ? %res : \%res;
+}
+
+# generates _two_ hashes, references to which are passed as 2 and 3 argument
+sub parse_from_to_diffinfo {
+	my ($diffinfo, $from, $to, @parents) = @_;
+
+	if ($diffinfo->{'nparents'}) {
+		# combined diff
+		$from->{'file'} = [];
+		$from->{'href'} = [];
+		fill_from_file_info($diffinfo, @parents)
+			unless exists $diffinfo->{'from_file'};
+		for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
+			$from->{'file'}[$i] =
+				defined $diffinfo->{'from_file'}[$i] ?
+				        $diffinfo->{'from_file'}[$i] :
+				        $diffinfo->{'to_file'};
+			if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
+				$from->{'href'}[$i] = href(action=>"blob",
+				                           hash_base=>$parents[$i],
+				                           hash=>$diffinfo->{'from_id'}[$i],
+				                           file_name=>$from->{'file'}[$i]);
+			} else {
+				$from->{'href'}[$i] = undef;
+			}
+		}
+	} else {
+		# ordinary (not combined) diff
+		$from->{'file'} = $diffinfo->{'from_file'};
+		if ($diffinfo->{'status'} ne "A") { # not new (added) file
+			$from->{'href'} = href(action=>"blob", hash_base=>$hash_parent,
+			                       hash=>$diffinfo->{'from_id'},
+			                       file_name=>$from->{'file'});
+		} else {
+			delete $from->{'href'};
+		}
+	}
+
+	$to->{'file'} = $diffinfo->{'to_file'};
+	if (!is_deleted($diffinfo)) { # file exists in result
+		$to->{'href'} = href(action=>"blob", hash_base=>$hash,
+		                     hash=>$diffinfo->{'to_id'},
+		                     file_name=>$to->{'file'});
+	} else {
+		delete $to->{'href'};
+	}
+}
+
+1;
-- 
1.7.1.455.g8f441

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCHv2 GSOC 01/11] gitweb: fix esc_url
  2010-07-15  7:29 ` [PATCHv2 GSOC 01/11] gitweb: fix esc_url Pavan Kumar Sunkara
@ 2010-07-15 13:52   ` Jakub Narebski
  2010-07-15 18:57     ` Junio C Hamano
  0 siblings, 1 reply; 27+ messages in thread
From: Jakub Narebski @ 2010-07-15 13:52 UTC (permalink / raw)
  To: Pavan Kumar Sunkara; +Cc: git, Christian Couder, Petr Baudis, Giuseppe Bilotta

On Thu, 15 Jul 2010, Pavan Kumar Sunkara wrote:
> The custom CGI escaping done in esc_url failed to escape UTF-8
> properly. Fix by using CGI::escape on each sequence of matched
> characters instead of sprintf()ing a custom escaping for each byte.
> 
> Additionally, the space -> + escape was being escaped due to greedy
> matching on the first substitution. Fix by adding space to the
> list of characters not handled on the first substitution.
> 
> Finally, remove an unnecessary escaping of the + sign.
> 
> commit 452e225 has missed fixing esc_url.
> 
> Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>

First, as this patch is independent and unrelated to either splitting
gitweb, or write support, it would be better if this patch was sent
individually to git mailing list, and not only as a part of a large
patch series.  It would likely to be applied, as it is pure bugfix.

Second, I would probably write commit message differently, to emphasize
that it is just finishing work of commit 452e225 (gitweb: fix esc_param,
2009-10-13) by fixing esc_url like it fixed esc_params.  But it is not
something very important.

For what it is worth, ACK from me for this patch.

> ---
>  gitweb/gitweb.perl |    3 +--
>  1 files changed, 1 insertions(+), 2 deletions(-)
> 
> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
> index 9446376..518328f 100755
> --- a/gitweb/gitweb.perl
> +++ b/gitweb/gitweb.perl
> @@ -1322,8 +1322,7 @@ sub esc_param {
>  sub esc_url {
>  	my $str = shift;
>  	return undef unless defined $str;
> -	$str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
> -	$str =~ s/\+/%2B/g;
> +	$str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&= ]+)/CGI::escape($1)/eg;
>  	$str =~ s/ /\+/g;
>  	return $str;
>  }
> -- 
> 1.7.1.455.g8f441
> 
> 

-- 
Jakub Narebski
Poland

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCHv2 GSOC 02/11] gitweb: Prepare for splitting gitweb
  2010-07-15  7:29 ` [PATCHv2 GSOC 02/11] gitweb: Prepare for splitting gitweb Pavan Kumar Sunkara
@ 2010-07-15 18:05   ` Jakub Narebski
  0 siblings, 0 replies; 27+ messages in thread
From: Jakub Narebski @ 2010-07-15 18:05 UTC (permalink / raw)
  To: Pavan Kumar Sunkara; +Cc: git, Christian Couder, Petr Baudis

On Thu, 15 Jul 2010, Pavan Kumar Sunkara wrote:

> Prepare gitweb for having been split into modules that are to be
> installed alongside gitweb in 'lib/' subdirectory, by adding
> 
>   use lib __DIR__.'/lib';
> 
> to gitweb.perl (to main gitweb script), and preparing for putting
> modules (relative path) in $(GITWEB_MODULES) in gitweb/Makefile.
> 
> Signed-off-by: Jakub Narebski <jnareb@gmail.com>
> Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
> ---

First, it would be better if the comment from the cover letter about
this patch (quoted below) was [also] put as the comment for this 
patch (i.e. in this area).

> The second patch is produced by my commit ammend to Jakub Narębski's
> initial commit to prepare splitting of gitweb in the message-id:
> http://mid.gmane.org/1276531710-22945-4-git-send-email-jnareb@gmail.com
>
> There is a small ammendment to that patch.
> $(INSTALL) -m 644 $(mod) '$(DESTDIR_SQ)$(gitwebdir_SQ)/$(mod)'
> is changed to
> $(INSTALL) -m 644 $(mod) '$(DESTDIR_SQ)$(gitwebdir_SQ)/$(dir
> $(mod))';) because it is better if we gave 'dir $(mod)' as the target
> for install command rather than '$(mod)' because it may cause
> problems in the future when gitweblibdir is used and the modules are
> installed elsewhere rather than in /use/share/gitweb.

Second, I don't quite understand the reason for your amendment.  The
difference is between two argument

  install [OPTION]... SOURCE DEST

in my original version of this patch, and two argument

  install [OPTION]... SOURCE DIRECTORY

after your change.  In both cases we install _single_ file at once.
What problems first version may cause if $(gitweblibdir) is used
and it points elsewhere than $(gitwebdir)/lib?

>  gitweb/Makefile    |    3 +++
>  gitweb/gitweb.perl |    9 +++++++++
>  2 files changed, 12 insertions(+), 0 deletions(-)
> 
> diff --git a/gitweb/Makefile b/gitweb/Makefile
> index d2584fe..c7610b3 100644
> --- a/gitweb/Makefile
> +++ b/gitweb/Makefile
> @@ -55,6 +55,7 @@ PERL_PATH  ?= /usr/bin/perl
>  bindir_SQ = $(subst ','\'',$(bindir))#'
>  gitwebdir_SQ = $(subst ','\'',$(gitwebdir))#'
>  gitwebstaticdir_SQ = $(subst ','\'',$(gitwebdir)/static)#'
> +gitweblibdir_SQ = $(subst ','\'',$(gitwebdir)/lib)#'
>  SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))#'
>  PERL_PATH_SQ  = $(subst ','\'',$(PERL_PATH))#'
>  DESTDIR_SQ    = $(subst ','\'',$(DESTDIR))#'
> @@ -150,6 +151,8 @@ install: all
>  	$(INSTALL) -m 755 $(GITWEB_PROGRAMS) '$(DESTDIR_SQ)$(gitwebdir_SQ)'
>  	$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)'
>  	$(INSTALL) -m 644 $(GITWEB_FILES) '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)'
> +	$(foreach dir,$(sort $(dir $(GITWEB_MODULES))),test -d '$(DESTDIR_SQ)$(gitwebdir_SQ)/$(dir)' || $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitwebdir_SQ)/$(dir)';)
> +	$(foreach mod,$(GITWEB_MODULES),$(INSTALL) -m 644 $(mod) '$(DESTDIR_SQ)$(gitwebdir_SQ)/$(dir $(mod))';)

Third, if you were folloring git mailing list (or at least patches for
gitweb on it), you would notice new version of my "gitweb: Prepare for
splitting gitweb" patch:

  "[PATCHv3/RFC] gitweb: Prepare for splitting gitweb"
  Message-ID: <201007080920.38724.jnareb@gmail.com>
  http://thread.gmane.org/gmane.comp.version-control.git/150463/focus=150544

which uses shell for loop, instead of make's $(foreach ...) function to
avoid _possible_ problems with generating a command line that exceeded
the maximum argument list length, as explained in comment section of

  http://thread.gmane.org/gmane.comp.version-control.git/150463/focus=150463

Note that this version uses SOURCE DIRECTORY version rather than
SOURCE DEST, like in your patch... probably unnecessary.

>  
>  ### Cleaning rules
>  
> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
> index 518328f..bda7da3 100755
> --- a/gitweb/gitweb.perl
> +++ b/gitweb/gitweb.perl
> @@ -9,6 +9,14 @@
>  
>  use strict;
>  use warnings;
> +
> +use File::Spec;
> +# __DIR__ is taken from Dir::Self __DIR__ fragment
> +sub __DIR__ () {
> +	File::Spec->rel2abs(join '', (File::Spec->splitpath(__FILE__))[0, 1]);
> +}
> +use lib __DIR__ . '/lib';
> +
>  use CGI qw(:standard :escapeHTML -nosticky);
>  use CGI::Util qw(unescape);
>  use CGI::Carp qw(fatalsToBrowser set_message);
> @@ -16,6 +24,7 @@ use Encode;
>  use Fcntl ':mode';
>  use File::Find qw();
>  use File::Basename qw(basename);
> +
>  binmode STDOUT, ':utf8';
>  
>  our $t0;

In "[PATCHv3/RFC] gitweb: Prepare for splitting gitweb" this spurious
change was removed from patch.


Anyway, eventual replacing of this patch by other version should be
fairly easy, so it shouldn't hold this series.

-- 
Jakub Narebski
Poland

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCHv2 GSOC 01/11] gitweb: fix esc_url
  2010-07-15 13:52   ` Jakub Narebski
@ 2010-07-15 18:57     ` Junio C Hamano
  2010-07-15 19:32       ` Jakub Narebski
  0 siblings, 1 reply; 27+ messages in thread
From: Junio C Hamano @ 2010-07-15 18:57 UTC (permalink / raw)
  To: Jakub Narebski
  Cc: Pavan Kumar Sunkara, git, Christian Couder, Petr Baudis,
	Giuseppe Bilotta

Jakub Narebski <jnareb@gmail.com> writes:

> On Thu, 15 Jul 2010, Pavan Kumar Sunkara wrote:
>> The custom CGI escaping done in esc_url failed to escape UTF-8
>> properly. Fix by using CGI::escape on each sequence of matched
>> characters instead of sprintf()ing a custom escaping for each byte.
>> 
>> Additionally, the space -> + escape was being escaped due to greedy
>> matching on the first substitution. Fix by adding space to the
>> list of characters not handled on the first substitution.
>> 
>> Finally, remove an unnecessary escaping of the + sign.
>> 
>> commit 452e225 has missed fixing esc_url.
>> 
>> Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
>
> First, as this patch is independent and unrelated to either splitting
> gitweb, or write support, it would be better if this patch was sent
> individually to git mailing list, and not only as a part of a large
> patch series.  It would likely to be applied, as it is pure bugfix.
>
> Second, I would probably write commit message differently, to emphasize
> that it is just finishing work of commit 452e225 (gitweb: fix esc_param,
> 2009-10-13) by fixing esc_url like it fixed esc_params.  But it is not
> something very important.

I tentatively rewrote the message like so:

    Earlier, 452e225 (gitweb: fix esc_param, 2009-10-13) fixed CGI
    escaping rules used in esc_url.  A very similar logic exists in
    esc_param and needs to be fixed the same way.

It makes one wonder why they have to be separate functions, doesn't it,
though?

> For what it is worth, ACK from me for this patch.

Thanks

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCHv2 GSOC 01/11] gitweb: fix esc_url
  2010-07-15 18:57     ` Junio C Hamano
@ 2010-07-15 19:32       ` Jakub Narebski
  0 siblings, 0 replies; 27+ messages in thread
From: Jakub Narebski @ 2010-07-15 19:32 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Pavan Kumar Sunkara, git, Christian Couder, Petr Baudis,
	Giuseppe Bilotta

Dnia czwartek 15. lipca 2010 20:57, Junio C Hamano napisał:
> Jakub Narebski <jnareb@gmail.com> writes:
> 
>> On Thu, 15 Jul 2010, Pavan Kumar Sunkara wrote:
>>> The custom CGI escaping done in esc_url failed to escape UTF-8
>>> properly. Fix by using CGI::escape on each sequence of matched
>>> characters instead of sprintf()ing a custom escaping for each byte.
>>> 
>>> Additionally, the space -> + escape was being escaped due to greedy
>>> matching on the first substitution. Fix by adding space to the
>>> list of characters not handled on the first substitution.
>>> 
>>> Finally, remove an unnecessary escaping of the + sign.
>>> 
>>> commit 452e225 has missed fixing esc_url.
>>> 
>>> Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
[...]
>> Second, I would probably write commit message differently, to emphasize
>> that it is just finishing work of commit 452e225 (gitweb: fix esc_param,
>> 2009-10-13) by fixing esc_url like it fixed esc_params.  But it is not
>> something very important.
> 
> I tentatively rewrote the message like so:
> 
>     Earlier, 452e225 (gitweb: fix esc_param, 2009-10-13) fixed CGI
>     escaping rules used in esc_url.  A very similar logic exists in
>     esc_param and needs to be fixed the same way.

Thanks.

> It makes one wonder why they have to be separate functions, doesn't it,
> though?

They need to be separate because you have to escape params-related
special characters ('?', ';', '=') when quoting params, but you
shouldn't when escaping (external) URL as a whole.

-- 
Jakub Narebski
Poland

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCHv2 GSOC 03/11] gitweb: Create Gitweb::Git module
  2010-07-15  7:29 ` [PATCHv2 GSOC 03/11] gitweb: Create Gitweb::Git module Pavan Kumar Sunkara
@ 2010-07-15 20:13   ` Jakub Narebski
  0 siblings, 0 replies; 27+ messages in thread
From: Jakub Narebski @ 2010-07-15 20:13 UTC (permalink / raw)
  To: Pavan Kumar Sunkara; +Cc: git, Christian Couder, Petr Baudis

On Thu, 15 Jul 2010, Pavan Kumar Sunkara wrote:

> Create a Gitweb::Git module in  'gitweb/lib/Gitweb/Git.pm'
> to deal with running git commands (and also processing output
> of git commands with external programs) from gitweb.
> 
> This module is intended as standalone module, which does not require
> (include) other gitweb' modules to avoid circular dependencies.  That
> is why it includes $GIT variable, even though this variable is
> configured during building gitweb.  On the other hand $GIT is more
> about git configuration, than gitweb configuration.
> 
> Subroutines moved:
> 	evaluate_git_version
> 	git_cmd
> 	quote_command
> 
> Update gitweb/Makefile to install Gitweb::Git module alongside gitweb
> 
> Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
> ---

This change (this refactoring) is fairly uncontroversial...

> --- a/gitweb/gitweb.perl
> +++ b/gitweb/gitweb.perl
[...]
> -# core git executable to use
> -# this can just be "git" if your webserver has a sensible PATH
> -our $GIT = "++GIT_BINDIR++/git";
> +# $GIT is from Gitweb::Git
> +$GIT = "++GIT_BINDIR++/git";

[...]
> +# core git executable to use
> +# this can just be "git" if your webserver has a sensible PATH
> +our $GIT;

...but for addressing the discussion about whether to put (or rather
leave) description of $GIT variable in main gitweb/gitweb.perl script,
to have description of all build-time configuration variables in one
place.  You should at least say something about this issue either in
commit message, or in the comments for this patch, in my opinion.

-- 
Jakub Narebski
Poland

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCHv2 GSOC 04/11] gitweb: Create Gitweb::Config module
  2010-07-15  7:29 ` [PATCHv2 GSOC 04/11] gitweb: Create Gitweb::Config module Pavan Kumar Sunkara
@ 2010-07-15 21:21   ` Jakub Narebski
  0 siblings, 0 replies; 27+ messages in thread
From: Jakub Narebski @ 2010-07-15 21:21 UTC (permalink / raw)
  To: Pavan Kumar Sunkara; +Cc: git, Christian Couder, Petr Baudis

On Thu, 15 Jul 2010, Pavan Kumar Sunkara wrote:

> Create Gitweb::Config module in 'gitweb/lib/Gitweb/Config.pm'
> to store all the configuration variables and subroutines
> regarding the gitweb.perl script.
> 
> This module depends only on $git_dir from Gitweb::Git and
> includes the configuration related variables and
> subroutines (Including those required for configuration
> of gitweb features).

Here it might be worth noting that subroutines related to per-repository
config (feature_* subroutines) are not in this module, I guess because
of (inter)dependencies.

> 
> Subroutines moved:
> 	evaluate_gitweb_config
> 	configure_gitweb_features
> 	filter_snapshot_fmts
> 	gitweb_get_feature
> 	gitweb_check_feature
> 
> Update gitweb/Makefile to install Gitweb::Config module alongside gitweb
> 
> Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
> ---
>  gitweb/Makefile             |    1 +
>  gitweb/gitweb.perl          |  495 ++----------------------------------------
>  gitweb/lib/Gitweb/Config.pm |  498 +++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 522 insertions(+), 472 deletions(-)
>  create mode 100644 gitweb/lib/Gitweb/Config.pm
[...]

> -our $version = "++GIT_VERSION++";
> +# Only configuration variables with build-time overridable
> +# defaults are listed below. The complete set of variables
> +# with their descriptions is listed in Gitweb::Config.
> +$version = "++GIT_VERSION++";

Ah, I see that here you address (somewhat) the issue of moving
descriptions of build-time configuration variables out of gitweb.perl
into individual module(s).
  
[...]

Nice straight refactoring.   It's a pity that git-diff cannot show this
as code movement + some changes, like it would in the case of wholesame
file rename.

-- 
Jakub Narebski
Poland

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCHv2 GSOC 05/11] gitweb: Create Gitweb::Request module
  2010-07-15  7:29 ` [PATCHv2 GSOC 05/11] gitweb: Create Gitweb::Request module Pavan Kumar Sunkara
@ 2010-07-16  0:11   ` Jakub Narebski
  0 siblings, 0 replies; 27+ messages in thread
From: Jakub Narebski @ 2010-07-16  0:11 UTC (permalink / raw)
  To: Pavan Kumar Sunkara; +Cc: git, Christian Couder, Petr Baudis

On Thu, 15 Jul 2010, Pavan Kumar Sunkara wrote:

> Create a Gitweb::Request module in 'gitweb/lib/Gitweb/Request.pm'
> to store and handle all the cgi params and related variables
> regarding the gitweb.perl script.
> 
> This module is intended as standalone module, which does not require
> (include) other gitweb' modules to avoid circular dependencies.
> 
> Subroutines moved:
> 	evaluate_uri
> 	evaluate_query_params

Here you would really need to explain why Gitweb::Request does not
include evaluate_path_info, the companion to evaluate_query_params.
It is strange to me that query_params is in it, but path_info is not.

Would it be possible, perhaps, to modify evaluate_path_info so it can
be moved to Gitweb::Request without adding additional dependencies to
it?
 
> Update gitweb/Makefile to install Gitweb::Request module alongside gitweb
> 
> Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>

[...]
> --- /dev/null
> +++ b/gitweb/lib/Gitweb/Request.pm
[...]
> +our @EXPORT = qw($cgi $my_url $my_uri $base_url $path_info $home_link $action $project $file_name
> +                 $file_parent $hash $hash_parent $hash_base $hash_parent_base @extra_options $page
> +                 $searchtype $search_use_regexp $searchtext $search_regexp %input_params %allowed_options
> +                 @cgi_param_mapping %cgi_param_mapping $t0 evaluate_query_params evaluate_uri);

You are working off older base, which did not include reset_timer
subroutine, added in 869d588 (gitweb: Move evaluate_gitweb_config
out of run_request, 2010-07-05).

-- 
Jakub Narebski
Poland

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCHv2 GSOC 06/11] gitweb: Create Gitweb::Escape module
  2010-07-15  7:29 ` [PATCHv2 GSOC 06/11] gitweb: Create Gitweb::Escape module Pavan Kumar Sunkara
@ 2010-07-16  9:01   ` Jakub Narebski
  0 siblings, 0 replies; 27+ messages in thread
From: Jakub Narebski @ 2010-07-16  9:01 UTC (permalink / raw)
  To: Pavan Kumar Sunkara; +Cc: git, Christian Couder, Petr Baudis

On Thu, 15 Jul 2010, Pavan Kumar Sunkara wrote:

> Create a Gitweb::Escape module in 'gitweb/lib/Gitweb/Escape.pm'
> to store all the quoting/unquoting and escaping subroutines
> regarding the gitweb.perl script.
> 
> This module imports $fallback_encoding variable from
> Gitweb::Config module to use it in sub 'to_utf8'
> 
> Subroutines moved:
> 	to_utf8
> 	esc_param
> 	esc_url
> 	esc_html
> 	esc_path
> 	quot_cec
> 	quot_upr
> 	untabify
> 
> Update gitweb/Makefile to install Gitweb::Escape module alongside gitweb

Nice and straightforward refactoring.

For what it is worth, ACK from me.

[...]
> +# quote unsafe chars, but keep the slash, even when it's not
> +# correct, but quoted slashes look too horrible in bookmarks
> +sub esc_param {
> +	my $str = shift;
> +	return undef unless defined $str;
> +	$str =~ s/([^A-Za-z0-9\-_.~()\/:@ ]+)/CGI::escape($1)/eg;
> +	$str =~ s/ /\+/g;
> +	return $str;
> +}
> +
> +# quote unsafe chars in whole URL, so some charactrs cannot be quoted
> +sub esc_url {
> +	my $str = shift;
> +	return undef unless defined $str;
> +	$str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&= ]+)/CGI::escape($1)/eg;
> +	$str =~ s/ /\+/g;
> +	return $str;
> +}

I see that here (or rather in corresponding preimage) you have dependency
on first patch in series, i.e. "gitweb: fix esc_url".

If you had send first patch as a separate email, not as part of series,
you would have need to mention in cover letter that the series (without
first patch) is based on / requires "gitweb: fix esc_url".

-- 
Jakub Narebski
Poland

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCHv2 GSOC 07/11] gitweb: Create Gitweb::RepoConfig module
  2010-07-15  7:29 ` [PATCHv2 GSOC 07/11] gitweb: Create Gitweb::RepoConfig module Pavan Kumar Sunkara
@ 2010-07-16 12:11   ` Jakub Narebski
  0 siblings, 0 replies; 27+ messages in thread
From: Jakub Narebski @ 2010-07-16 12:11 UTC (permalink / raw)
  To: Pavan Kumar Sunkara; +Cc: git, Christian Couder, Petr Baudis

On Thu, 15 Jul 2010, Pavan Kumar Sunkara wrote:

> Create a Gitweb::RepoConfig module in 'gitweb/lib/Gitweb/RepoConfig.pm'
> to store and handle all the configuration and subroutines
> related to a single repository regarding the gitweb.perl script.

The above sentence is hard to parse (hard to understand).  Please try
to be more clear.  I guess that you wanted to say that this module
is about checking repositories and repository parameters, and about
per-repository configuration.

> 
> This module depend on several other modules like Git.pm,
> Config.pm, Request.pm and Escape.pm.

Nitpick: the names of those modules are Gitweb::Git, Gitweb::Config,
Gitweb::Request and Gitweb::Escape.

> 
> It also include subroutines regarding project_list and
> it's handling.
> 
> Subroutines moved:
> 	check_head_link
> 	check_export_ok
> 	hash_set_multi
> 	git_parse_project_config
> 	config_to_bool
> 	config_to_int
> 	config_to_multi
> 	feature_bool
> 	feature_snapshot
> 	feature_patches
> 	feature_avatar
> 	git_get_project_config
> 	git_get_project_description
> 	git_get_project_ctags
> 	git_populate_project_tagcloud
> 	git_show_project_tagcloud
> 	git_get_project_url_list
> 	git_get_projects_list
> 	git_get_project_list_from_file
> 	git_get_project_owner
> 	get_file_owner
> 	project_in_list

In the future we might want to separate handling of per-repository
overriding of features in a separae module.

> 
> Update gitweb/Makefile to install Gitweb::RepoConfig module
> alongside gitweb
> 
> Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>

Other than those minor issues it seems all right.


P.S. I'm sorry, but I wouldn't be able to examine the rest of series
before this Sunday at earliest.

-- 
Jakub Narebski
Poland

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCHv2 GSOC 08/11] gitweb: Create Gitweb::View module
  2010-07-15  7:29 ` [PATCHv2 GSOC 08/11] gitweb: Create Gitweb::View module Pavan Kumar Sunkara
@ 2010-07-18 15:10   ` Jakub Narebski
  0 siblings, 0 replies; 27+ messages in thread
From: Jakub Narebski @ 2010-07-18 15:10 UTC (permalink / raw)
  To: Pavan Kumar Sunkara; +Cc: git, Christian Couder, Petr Baudis

On Thu, 15 Jul 2010, Pavan Kumar Sunkara wrote:

> Create Gitweb::View module in 'gitweb/lib/Gitweb/View.pm'
> to store the subroutines related to the HTML output
> for gitweb.

If it contains subroutines related only to HTML output, why isn't it
called Gitweb::HTML then?  If it contains some subroutines which are
not strictly about HTML, please state it in the commit message.

> 
> This module depends on Git.pm, Config.pm, Request.pm,
> Escape.pm and RepoConfig.pm. Action specific HTML div
> subroutines are not included in this module due to
> unmet dependencies of Gitweb::Parse and Gitweb::Format.

Whether you use Gitweb::Git or Git.pm, you should be consistent in the
naming you use: either Git.pm, or Gitweb::Parse.

Do I understand correctly trhat Gitweb::Parse and Gitweb::Format are to
be added in subsequent patches?  Please state this fact in the commit
message.

> 
> Subroutines moved:
> 	*href
> 	 get_feed_info
> 	*chop_str
> 	 chop_and_escape_str
> 	*age_class
> 	 age_string
> 	*S_ISGITLINK
> 	 mode_str
> 	 file_type
> 	 file_type_long
> 	*get_page_title
> 	 git_header_html
> 	 git_footer_html
> 	 die_error
> 	*git_print_page_nav
> 	 format_paging_nav
> 	 git_print_header_div
> 	 git_print_page_path
> 	*print_local_time
> 	 format_local_time
> 	*insert_file
> 	*git_get_link_target
> 	 normalize_link_target
> 	 git_print_tree_entry
> 	*print_sort_th
> 	 format_sort_th

Nitpick: my first thought was "why some of those subroutines are marked
with a star?".  If you want to divide this list of subroutines into
groups by function, you should leave a space between '*' and name of
subroutine (i.e. '* href'), and perhaps also leave empty lines between
groups, though this might make commit this list overly long.

I guess that this separation of subroutines into Gitweb::View module 
was a matter of some compromise: between having too large modules
(containing only barely related subroutines) and having too many
modules.

> 
> Update 'gitweb/Makefile' to install Gitweb::View alongside gitweb.
> 
> Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>

I have not checked the diff itself...

-- 
Jakub Narebski
Poland

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCHv2 GSOC 09/11] gitweb: Create Gitweb::Util module
  2010-07-15  7:29 ` [PATCHv2 GSOC 09/11] gitweb: Create Gitweb::Util module Pavan Kumar Sunkara
@ 2010-07-18 17:45   ` Jakub Narebski
  0 siblings, 0 replies; 27+ messages in thread
From: Jakub Narebski @ 2010-07-18 17:45 UTC (permalink / raw)
  To: Pavan Kumar Sunkara; +Cc: git, Christian Couder, Petr Baudis

On Thu, 15 Jul 2010, Pavan Kumar Sunkara wrote:

> Create Gitweb::Util module in 'gitweb/lib/Gitweb/Util.pm'
> to store the git utility subroutines related to gitweb.
> 
> This module include subroutines in various categories
> such as git utility subs invoking git commands, git
> utility subs accessing git repository, mimetype related
> subs and HTML output utility subs.

O.K. so the theme is small helper subroutines, isn't it?

> 
> Subroutines moved:
> 	git_get_head_hash
> 	git_get_full_hash
> 	git_get_short_hash
> 	git_get_hash
> 	git_get_type
> 	git_get_hash_by_path
> 	git_get_path_by_hash
> 	git_get_last_activity
> 	git_get_references
> 	git_get_rev_name_tags
> 	git_get_heads_list
> 	git_get_tags_list
> 	mimetype_guess_file
> 	mimetype_guess
> 	blob_mimetype
> 	blob_contenttype
> 	guess_file_syntax
> 	run_highlighter
> 	fill_from_file_info
> 	is_deleted
> 	is_patch_split

Just a question: shouldn't git_get_last_activity subroutine be in
Gitweb::RepoConfig module?  Or is Gitweb::RepoConfig only about "static"
properties of a repository?

>
> Update 'gitweb/Makefile' to install Gitweb::Util alongside gitweb.
> 
> Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
> ---

I have not checked the patch, but is should be mainly straightforward
code movement plus some boilerplate.

-- 
Jakub Narebski
Poland

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCHv2 GSOC 10/11] gitweb: Create Gitweb::Format module
  2010-07-15  7:29 ` [PATCHv2 GSOC 10/11] gitweb: Create Gitweb::Format module Pavan Kumar Sunkara
@ 2010-07-18 20:16   ` Jakub Narebski
  0 siblings, 0 replies; 27+ messages in thread
From: Jakub Narebski @ 2010-07-18 20:16 UTC (permalink / raw)
  To: Pavan Kumar Sunkara; +Cc: git, Christian Couder, Petr Baudis

On Thu, 15 Jul 2010, Pavan Kumar Sunkara wrote:

> Create Gitweb::Format module in 'gitweb/lib/Gitweb/Format.pm'
> to store the subroutines related to formatting of HTML
> fragments required for gitweb.

What is the criterion of choosing whether to put given subroutine in
Gitweb::View / Gitweb::HTML and in Gitweb::Format (this module)?

> 
> This module depends on Config.pm, View.pm, Escape.pm,
> Util.pm and Request.pm.

O.K., although we should decide consistently through the series if we
want to use the Config.pm form for describing dependencies in the commit
message, or would it be better to use Gitweb::Config, i.e. the module
name.

>                         It mainly contain functions returning 
> short HTML fragments or transforming HTML fragments. Also
> include subroutines regarding avatar formatting.

I think you should say aomething about _why_ did you pur avatar
formatting in this module; I guess that is because git_get_avatar
returns also short HTML fragment.

> 
> Subroutines moved:
> 	format_log_line_html
> 	format_ref_marker
> 	format_subject_html
> 	picon_url
> 	gravatar_url
> 	git_get_avatar
> 	format_search_author
> 	format_author_html
> 	format_git_diff_header_line
> 	format_extended_diff_header_line
> 	format_diff_from_to_header
> 	format_diff_cc_simplified
> 	format_diff_line
> 	format_snapshot_links
> 
> Update 'gitweb/Makefile' to install Gitweb::Format alongside gitweb.
> 
> Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
> ---

I have not checked patch itself.

-- 
Jakub Narebski
Poland

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCHv2 GSOC 11/11] gitweb: Create Gitweb::Parse module
  2010-07-15  7:29 ` [PATCHv2 GSOC 11/11] gitweb: Create Gitweb::Parse module Pavan Kumar Sunkara
@ 2010-07-19 14:55   ` Jakub Narebski
  0 siblings, 0 replies; 27+ messages in thread
From: Jakub Narebski @ 2010-07-19 14:55 UTC (permalink / raw)
  To: Pavan Kumar Sunkara; +Cc: git, Petr Baudis, Christian Couder

On Thu, 15 Jul 2010, Pavan Kumar Sunkara wrote:

> Create Gitweb::Parse module in 'gitweb/lib/Gitweb/Parse.pm'
> to store the subroutines which related to parsing functions
> required for gitweb.

O.K., that is nicely defined module, although you would probably
want to add _what_ those functions parse: most of them (or all
of them) parse or help parsing output of git commands.

By the way, in the future we might want to move those subroutines
to Git::Parse (or individual Git::Commit, Git::Tag, etc. modules).
By "in the future" I mean here when we move to using Git.pm in 
gitweb... which currently would require some functionality that is
just not present in Git.pm.

> 
> This module depends on Git.pm, Escape.pm, View.pm and Util.pm.

Usual notice: Git.pm or Gitweb::Git, etc.?

> 
> Subroutines moved:
> 	unquote
> 	parse_date
> 	parse_tag
> 	parse_commit_text
> 	parse_commit
> 	parse_commits
> 	parse_difftree_raw_line
> 	parsed_difftree_line
> 	parse_ls_tree_line
> 	parse_from_to_diffinfo

One of those is not like the other.  parsed_difftree_line is really an
utility (helper) function, but I guess it is here and not in Gitweb::Util
because of its dependency on parse_difftree_raw_line, and therefore it
would add circular dependency of Gitweb::Parse to Gitweb::Util.

> 
> Update 'gitweb/Makefile' to install Gitweb::Parse alongside gitweb.
> 
> Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
> ---
>  gitweb/Makefile            |    1 +
>  gitweb/gitweb.perl         |  392 +-----------------------------------------
>  gitweb/lib/Gitweb/Parse.pm |  414 ++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 416 insertions(+), 391 deletions(-)
>  create mode 100644 gitweb/lib/Gitweb/Parse.pm

[...]
> diff --git a/gitweb/lib/Gitweb/Parse.pm b/gitweb/lib/Gitweb/Parse.pm
> new file mode 100644
> index 0000000..6eeef2c
> --- /dev/null
> +++ b/gitweb/lib/Gitweb/Parse.pm
> @@ -0,0 +1,414 @@
> +#!/usr/bin/perl
> +#
> +# Gitweb::Parse -- gitweb's parse_* subs package

Wouldn't "subroutines related to parsing output of git commands, required
by gitweb" be a better description here?

> +#
> +# This program is licensed under the GPLv2

Sidenote: do we want "GPLv2" or "GPLv2 or later", or even "GPLv2 or later,
at discretion of Junio C Hamano"?

> +
> +package Gitweb::Parse;
> +
> +use strict;
> +use warnings;
> +use Exporter qw(import);
> +
> +our @EXPORT = qw(parse_date parse_tag parse_commit_text parse_commit
> +                 parse_commits parse_difftree_raw_line parsed_difftree_line
> +                 parse_ls_tree_line parse_from_to_diffinfo unquote);

Sidenote: in the future commit we might want to not export 'unquote' by
default (but still be able to export it / export it with ':all' tag).
I don't think we use it outside subroutines defined in this module.
But I guess it is better left for a later commit.

> +
> +use Gitweb::Git qw(git_cmd);
> +use Gitweb::Request qw(@extra_options $hash $hash_parent);
> +use Gitweb::Escape qw(to_utf8);
> +use Gitweb::View qw(href chop_str age_string die_error);
> +use Gitweb::Util qw(fill_from_file_info is_deleted);

While at it, lets review those subroutines.

All those comments are meant for a future commit, and are not in any
way requirement for Pavan.  Splitting should in my opinion be just simple
code movement / code reorganization, without any changes.

> +# git may return quoted and escaped filenames
> +sub unquote {
> +	my $str = shift;
> +
> +	sub unq {
> +		my $seq = shift;
> +		my %es = ( # character escape codes, aka escape sequences
> +			't' => "\t",   # tab            (HT, TAB)
> +			'n' => "\n",   # newline        (NL)
> +			'r' => "\r",   # return         (CR)
> +			'f' => "\f",   # form feed      (FF)
> +			'b' => "\b",   # backspace      (BS)
> +			'a' => "\a",   # alarm (bell)   (BEL)
> +			'e' => "\e",   # escape         (ESC)
> +			'v' => "\013", # vertical tab   (VT)
> +		);
> +
> +		if ($seq =~ m/^[0-7]{1,3}$/) {
> +			# octal char sequence
> +			return chr(oct($seq));
> +		} elsif (exists $es{$seq}) {
> +			# C escape sequence, aka character escape code
> +			return $es{$seq};
> +		}
> +		# quoted ordinary character
> +		return $seq;
> +	}

Sidenote: I wonder if it wouldn't be better to move definition of 'unq'
subroutine to outside of 'unquote', i.e. do not nest subroutines.

> +
> +	if ($str =~ m/^"(.*)"$/) {
> +		# needs unquoting
> +		$str = $1;
> +		$str =~ s/\\([^0-7]|[0-7]{1,3})/unq($1)/eg;

Sidenote: hmmm... aren't digit escape sequences always three digits long,
with exception of '\0' for NUL byte?  I mean, wouldn't it be better to
use

  +		$str =~ s/\\([^0-7]|0|[0-7]{3})/unq($1)/eg;

> +	}
> +	return $str;
> +}
> +
> +## ----------------------------------------------------------------------
> +## parse to hash functions
> +

Sidenote: we would probably want to add description of this subroutine
later (in subsequent commits).  This subroutine takes e.g. authortime
as returned by git (epoch + numerical timezone), parses its and 
_formats_ to rfc2822, mday-time, ISO 8601 and other formats... hmmm...
perhaps we should separate parsing from formatting...

But all of this should be left for later commit.

> +sub parse_date {
> +	my $epoch = shift;
> +	my $tz = shift || "-0000";
> +
> +	my %date;
> +	my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
> +	my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
> +	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
> +	$date{'hour'} = $hour;
> +	$date{'minute'} = $min;
> +	$date{'mday'} = $mday;
> +	$date{'day'} = $days[$wday];
> +	$date{'month'} = $months[$mon];
> +	$date{'rfc2822'}   = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
> +	                     $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
> +	$date{'mday-time'} = sprintf "%d %s %02d:%02d",
> +	                     $mday, $months[$mon], $hour ,$min;
> +	$date{'iso-8601'}  = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
> +	                     1900+$year, 1+$mon, $mday, $hour ,$min, $sec;
> +
> +	$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
> +	my $local = $epoch + ((int $1 + ($2/60)) * 3600);
> +	($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
> +	$date{'hour_local'} = $hour;
> +	$date{'minute_local'} = $min;
> +	$date{'tz_local'} = $tz;
> +	$date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
> +	                          1900+$year, $mon+1, $mday,
> +	                          $hour, $min, $sec, $tz);
> +	return %date;
> +}
> +
> +sub parse_tag {
> +	my $tag_id = shift;
> +	my %tag;
> +	my @comment;
> +
> +	open my $fd, "-|", git_cmd(), "cat-file", "tag", $tag_id or return;

In the future, when we move to using Git.pm (the "use Git", not 
"use Gitweb::Git"), it could be

  +	$repo->get_object_with_type("tag", $tag_id) or return;

Or not.  Or something like that.

> +	$tag{'id'} = $tag_id;
> +	while (my $line = <$fd>) {
> +		chomp $line;
> +		if ($line =~ m/^object ([0-9a-fA-F]{40})$/) {
> +			$tag{'object'} = $1;
> +		} elsif ($line =~ m/^type (.+)$/) {
> +			$tag{'type'} = $1;
> +		} elsif ($line =~ m/^tag (.+)$/) {
> +			$tag{'name'} = $1;
> +		} elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
> +			$tag{'author'} = $1;
> +			$tag{'author_epoch'} = $2;
> +			$tag{'author_tz'} = $3;
> +			if ($tag{'author'} =~ m/^([^<]+) <([^>]*)>/) {
> +				$tag{'author_name'}  = $1;
> +				$tag{'author_email'} = $2;
> +			} else {
> +				$tag{'author_name'} = $tag{'author'};
> +			}

Sidenote: I wonder if parsing name + email out of author / comitter /
/ tagger info shouldn't be moved to a separate subroutine, or is it not
worth it...

Again, better left for later commit.

> +		} elsif ($line =~ m/--BEGIN/) {
> +			push @comment, $line;
> +			last;

Sidenote: WTF is that?  The (ASCII armored) signature block can occur
only in the tag message (tag "payload"), and coun't be in heder, 
could it?

But fix of this issue should be left for later commit.

> +		} elsif ($line eq "") {
> +			last;
> +		}
> +	}
> +	push @comment, <$fd>;

  +	my @comment = <$fd>;

Again, this is better left for later commit.

> +	$tag{'comment'} = \@comment;
> +	close $fd or return;
> +	if (!defined $tag{'name'}) {
> +		return

  +		return;

Fix (I guess), better left for later commit.

> +	};
> +	return %tag

  +	return %tag;

Same.

> +}
> +

Sidenote: we should probably add description of this subroutine in
a later commit.

> +sub parse_commit_text {
> +	my ($commit_text, $withparents) = @_;

Sidenote: I wonder if using "named parameters" here wouldn't make
for a better API, i.e.

  +	my ($commit_text, %opts) = @_;
  +	my $withparents = $opts{'-with-parents'};

> +	my @commit_lines = split '\n', $commit_text;
> +	my %co;
> +
> +	pop @commit_lines; # Remove '\0'
> +
> +	if (! @commit_lines) {
> +		return;
> +	}
> +
> +	my $header = shift @commit_lines;
> +	if ($header !~ m/^[0-9a-fA-F]{40}/) {
> +		return;
> +	}
> +	($co{'id'}, my @parents) = split ' ', $header;

Those five lines above very much depend that the output is generated
by 'git rev-list --parents ...", i.e. that there is line with SHA-1
of commit, and then line with _effective_ parents of a commit.

> +	while (my $line = shift @commit_lines) {

Sidenote: parse_tag 'chomp's each line first.

> +		last if $line eq "\n";
> +		if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
> +			$co{'tree'} = $1;
> +		} elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) {
> +			push @parents, $1;

Sidenote: we would probably want to distinguish between real / original
parents (as recorded in the object) and effective parents (history 
simplification e.g. in 'history' view, and grafts).

> +		} elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
> +			$co{'author'} = to_utf8($1);
> +			$co{'author_epoch'} = $2;
> +			$co{'author_tz'} = $3;
> +			if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
> +				$co{'author_name'}  = $1;
> +				$co{'author_email'} = $2;
> +			} else {
> +				$co{'author_name'} = $co{'author'};
> +			}

Sidenote: again, perhaps parsing of aithor info should be refactored
into a separate subroutine, to avoid code duplication.

> +		} elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
> +			$co{'committer'} = to_utf8($1);
> +			$co{'committer_epoch'} = $2;
> +			$co{'committer_tz'} = $3;
> +			if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
> +				$co{'committer_name'}  = $1;
> +				$co{'committer_email'} = $2;
> +			} else {
> +				$co{'committer_name'} = $co{'committer'};
> +			}
> +		}

Same.

> +	}
> +	if (!defined $co{'tree'}) {
> +		return;
> +	};
> +	$co{'parents'} = \@parents;
> +	$co{'parent'} = $parents[0];
> +

This foreach skips initial (leading) empty lines.

> +	foreach my $title (@commit_lines) {
> +		$title =~ s/^    //;
> +		if ($title ne "") {
> +			$co{'title'} = chop_str($title, 80, 5);
> +			# remove leading stuff of merges to make the interesting part visible
> +			if (length($title) > 50) {
> +				$title =~ s/^Automatic //;
> +				$title =~ s/^merge (of|with) /Merge ... /i;
> +				if (length($title) > 50) {
> +					$title =~ s/(http|rsync):\/\///;
> +				}
> +				if (length($title) > 50) {
> +					$title =~ s/(master|www|rsync)\.//;
> +				}
> +				if (length($title) > 50) {
> +					$title =~ s/kernel.org:?//;
> +				}
> +				if (length($title) > 50) {
> +					$title =~ s/\/pub\/scm//;
> +				}
> +			}
> +			$co{'title_short'} = chop_str($title, 50, 5);

Sidenote: this simplification for 'title_short' should probably be
refactored into separate subroutine, and improved.  But that's a
matter for a later commit, if any.

> +			last;
> +		}
> +	}
> +	if (! defined $co{'title'} || $co{'title'} eq "") {
> +		$co{'title'} = $co{'title_short'} = '(no commit message)';
> +	}
> +	# remove added spaces
> +	foreach my $line (@commit_lines) {
> +		$line =~ s/^    //;
> +	}

Using 'map' would be probably more idiomatic:

  +	# remove added spaces
  +	map { s/^[ ]{4}// } @commit_lines;

> +	$co{'comment'} = \@commit_lines;
> +
> +	my $age = time - $co{'committer_epoch'};
> +	$co{'age'} = $age;
> +	$co{'age_string'} = age_string($age);

> +	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'});
> +	if ($age > 60*60*24*7*2) {
> +		$co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
> +		$co{'age_string_age'} = $co{'age_string'};
> +	} else {
> +		$co{'age_string_date'} = $co{'age_string'};
> +		$co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
> +	}

Sidenote: the above block should be probably refactored in a later commit.

> +	return %co;
> +}
> +
> +sub parse_commit {
> +	my ($commit_id) = @_;
> +	my %co;
> +
> +	local $/ = "\0";
> +
> +	open my $fd, "-|", git_cmd(), "rev-list",
> +		"--parents",
> +		"--header",
> +		"--max-count=1",
> +		$commit_id,
> +		"--",
> +		or die_error(500, "Open git-rev-list failed");
> +	%co = parse_commit_text(<$fd>, 1);

With new API (see above) it would be

  +	%co = parse_commit_text(<$fd>, -with-parents => 1);

> +	close $fd;
> +
> +	return %co;
> +}
> +
> +sub parse_commits {
> +	my ($commit_id, $maxcount, $skip, $filename, @args) = @_;

Here a weakness (I think) of Perl way of passing subroutine arguments
is shown: it is impossible to have both slurpy @args and named parameters
i.e. %opts, unless we pass optional / named parameters in hashref, i.e.

  +	my ($commit_id, @args) = @_;
  +	my %opts = ('-max-count' => 1, '-skip' => 0);
  +	%opts = (%opts, %{ shift @args }) if ref($args[ 0]) eq 'HASH';
  +	%opts = (%opts, %{ pop   @args }) if ref($args[-1]) eq 'HASH';

or something like that, and then using $opts{'-max-count'} and 
$opts{'-skip'} in place of $maxcount and $skip, etc.

> +	my @cos;

Errrr... @commits or @commit_list.

> +
> +	$maxcount ||= 1;
> +	$skip ||= 0;
> +
> +	local $/ = "\0";
> +
> +	open my $fd, "-|", git_cmd(), "rev-list",
> +		"--header",
> +		@args,
> +		("--max-count=" . $maxcount),
> +		("--skip=" . $skip),
> +		@extra_options,
> +		$commit_id,
> +		"--",
> +		($filename ? ($filename) : ())
> +		or die_error(500, "Open git-rev-list failed");
> +	while (my $line = <$fd>) {

Hmmm... should it be called '$line' if it is "\0" separated chunk, 
i.e. single commit?  $commit_text could be better name, and if we
'chomp'ed it, we wouldn't have to 'pop' trailing "\0" in
parse_commit_text.

> +		my %co = parse_commit_text($line);
> +		push @cos, \%co;
> +	}
> +	close $fd;
> +
> +	return wantarray ? @cos : \@cos;

Nowadays I really wonder if it is such a good idea, and if it wouldn't
be better to just always return @cos (i.e. @commits / @commit_list).

We are a bit inconsistent WRT. context sensitivity.

> +}
> +
> +# parse line of git-diff-tree "raw" output
> +sub parse_difftree_raw_line {
> +	my $line = shift;
> +	my %res;
> +
> +	# ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M	ls-files.c'
> +	# ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M	rev-tree.c'
> +	if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) {
> +		$res{'from_mode'} = $1;
> +		$res{'to_mode'} = $2;
> +		$res{'from_id'} = $3;
> +		$res{'to_id'} = $4;
> +		$res{'status'} = $5;
> +		$res{'similarity'} = $6;
> +		if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied
> +			($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7);
> +		} else {
> +			$res{'from_file'} = $res{'to_file'} = $res{'file'} = unquote($7);
> +		}
> +	}
> +	# '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR	git-gui/git-gui.sh'
> +	# combined diff (for merge commit)
> +	elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) {
> +		$res{'nparents'}  = length($1);
> +		$res{'from_mode'} = [ split(' ', $2) ];
> +		$res{'to_mode'} = pop @{$res{'from_mode'}};
> +		$res{'from_id'} = [ split(' ', $3) ];
> +		$res{'to_id'} = pop @{$res{'from_id'}};
> +		$res{'status'} = [ split('', $4) ];
> +		$res{'to_file'} = unquote($5);
> +	}
> +	# 'c512b523472485aef4fff9e57b229d9d243c967f'
> +	elsif ($line =~ m/^([0-9a-fA-F]{40})$/) {
> +		$res{'commit'} = $1;
> +	}
> +
> +	return wantarray ? %res : \%res;

Same here.

> +}
> +
> +# wrapper: return parsed line of git-diff-tree "raw" output
> +# (the argument might be raw line, or parsed info)
> +sub parsed_difftree_line {
> +	my $line_or_ref = shift;
> +
> +	if (ref($line_or_ref) eq "HASH") {
> +		# pre-parsed (or generated by hand)
> +		return $line_or_ref;
> +	} else {
> +		return parse_difftree_raw_line($line_or_ref);
> +	}
> +}
> +
> +# parse line of git-ls-tree output
> +sub parse_ls_tree_line {
> +	my $line = shift;
> +	my %opts = @_;
> +	my %res;
> +
> +	if ($opts{'-l'}) {
> +		#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa   16717	panic.c'
> +		$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40}) +(-|[0-9]+)\t(.+)$/s;
> +
> +		$res{'mode'} = $1;
> +		$res{'type'} = $2;
> +		$res{'hash'} = $3;
> +		$res{'size'} = $4;
> +		if ($opts{'-z'}) {
> +			$res{'name'} = $5;
> +		} else {
> +			$res{'name'} = unquote($5);
> +		}
> +	} else {
> +		#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa	panic.c'
> +		$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
> +
> +		$res{'mode'} = $1;
> +		$res{'type'} = $2;
> +		$res{'hash'} = $3;
> +		if ($opts{'-z'}) {
> +			$res{'name'} = $4;
> +		} else {
> +			$res{'name'} = unquote($4);
> +		}

Hmmm... not all parse_* subroutines that can support '-z' named
option do it.

> +	}
> +
> +	return wantarray ? %res : \%res;

Same issue about context sensitivity here.

> +}
> +
> +# generates _two_ hashes, references to which are passed as 2 and 3 argument
> +sub parse_from_to_diffinfo {
> +	my ($diffinfo, $from, $to, @parents) = @_;
> +
> +	if ($diffinfo->{'nparents'}) {
> +		# combined diff
> +		$from->{'file'} = [];
> +		$from->{'href'} = [];
> +		fill_from_file_info($diffinfo, @parents)
> +			unless exists $diffinfo->{'from_file'};
> +		for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
> +			$from->{'file'}[$i] =
> +				defined $diffinfo->{'from_file'}[$i] ?
> +				        $diffinfo->{'from_file'}[$i] :
> +				        $diffinfo->{'to_file'};
> +			if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
> +				$from->{'href'}[$i] = href(action=>"blob",
> +				                           hash_base=>$parents[$i],
> +				                           hash=>$diffinfo->{'from_id'}[$i],
> +				                           file_name=>$from->{'file'}[$i]);
> +			} else {
> +				$from->{'href'}[$i] = undef;
> +			}
> +		}
> +	} else {
> +		# ordinary (not combined) diff
> +		$from->{'file'} = $diffinfo->{'from_file'};
> +		if ($diffinfo->{'status'} ne "A") { # not new (added) file
> +			$from->{'href'} = href(action=>"blob", hash_base=>$hash_parent,
> +			                       hash=>$diffinfo->{'from_id'},
> +			                       file_name=>$from->{'file'});
> +		} else {
> +			delete $from->{'href'};
> +		}
> +	}
> +
> +	$to->{'file'} = $diffinfo->{'to_file'};
> +	if (!is_deleted($diffinfo)) { # file exists in result
> +		$to->{'href'} = href(action=>"blob", hash_base=>$hash,
> +		                     hash=>$diffinfo->{'to_id'},
> +		                     file_name=>$to->{'file'});
> +	} else {
> +		delete $to->{'href'};
> +	}
> +}

Hmmm... this subroutine is not exactly like the other.  I wonder if it
wouldn't be better to leave it together with git_patchset_body.  Or
with fill_from_file_info.

> +
> +1;
> -- 
> 1.7.1.455.g8f441

-- 
Jakub Narebski
Poland

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCHv2 00/11] Splitting gitweb
  2010-07-15  7:29 [PATCHv2 00/11] Splitting gitweb Pavan Kumar Sunkara
                   ` (10 preceding siblings ...)
  2010-07-15  7:29 ` [PATCHv2 GSOC 11/11] gitweb: Create Gitweb::Parse module Pavan Kumar Sunkara
@ 2010-08-01 20:44 ` Sverre Rabbelier
  2010-08-02 15:03   ` Jakub Narebski
  11 siblings, 1 reply; 27+ messages in thread
From: Sverre Rabbelier @ 2010-08-01 20:44 UTC (permalink / raw)
  To: Jakub Narebski, Christian Couder, Petr Baudis; +Cc: git, Pavan Kumar Sunkara

Heya,

On Thu, Jul 15, 2010 at 02:29, Pavan Kumar Sunkara
<pavan.sss1991@gmail.com> wrote:
> 10 patches out of the 11 patches in this patch series split gitweb into
> several small sized modules

What is the status of this series? Is anyone going to carry it
forward? I remember multiple people wishing for gitweb to be more
modular to make it easier to hack on? What shape is the series in, is
it almost done, or will it need a lot more work?

-- 
Cheers,

Sverre Rabbelier

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCHv2 00/11] Splitting gitweb
  2010-08-01 20:44 ` [PATCHv2 00/11] Splitting gitweb Sverre Rabbelier
@ 2010-08-02 15:03   ` Jakub Narebski
  0 siblings, 0 replies; 27+ messages in thread
From: Jakub Narebski @ 2010-08-02 15:03 UTC (permalink / raw)
  To: Sverre Rabbelier; +Cc: Christian Couder, Petr Baudis, git, Pavan Kumar Sunkara

On Sun, Aug 1, 2010, Sverre Rabbelier wrote:
> On Thu, Jul 15, 2010 at 02:29, Pavan Kumar Sunkara <pavan.sss1991@gmail.com> wrote:

> > 10 patches out of the 11 patches in this patch series split gitweb into
> > several small sized modules
> 
> What is the status of this series? Is anyone going to carry it
> forward? I remember multiple people wishing for gitweb to be more
> modular to make it easier to hack on? What shape is the series in, is
> it almost done, or will it need a lot more work?

It is almost done.  The only major thing seems to be the "Prepare for
splitting gitweb" patch, which should be I think replaced by updated
version that uses shell loop in place of make's $(foreach ...) function
to avoid possibility with generating a command line that exceeded the
maximum argument list length.  Might be unnecessary.

Most if not all comments that I have to this version of series was
about commit messages, not the content of the patch itself.

If Pavan would not do re-roll of this series in a few days time, I will
pick it up myself, and resubmit.

> Pavan Kumar Sunkara (11):
>   gitweb: fix esc_url

Comitted: 109988f (gitweb: fix esc_url, 2010-07-15)

>   gitweb: Prepare for splitting gitweb

To be replaced by new version of my "gitweb: Prepare for splitting
gitweb" patch:

  "[PATCHv3/RFC] gitweb: Prepare for splitting gitweb"
  Message-ID: <201007080920.38724.jnareb@gmail.com>
  http://thread.gmane.org/gmane.comp.version-control.git/150463/focus=150544

Also it would need explanation that moving from

  install SOURCE DEST

to

  install SOURCE DIRECTORY

was done for security reason (I think), and because we cannot use
portably  `-T' / `--no-target-directory' option to install, as
1.) it is GNU-ism, 2.) it is not present in older GNU install.

>   gitweb: Create Gitweb::Git module

There was an issue about where to put descriptions of build-time
variables: in gitweb.perl or in individual modules.  I think the
issue was addressed somewhat in later commit, but I think it should
be addressed here too.

>   gitweb: Create Gitweb::Config module

Here it would be nice to have description which related subroutines were
not included, and why.

>   gitweb: Create Gitweb::Request module

It is quite strange for me that evaluate_query_params is in this module,
but evaluate_path_info is not.

Also this module needs to be updated to newer codebase (lacks reset_timer
subroutine).

>   gitweb: Create Gitweb::Escape module

ACK-ed.

>   gitweb: Create Gitweb::RepoConfig module

I'd like to have here better description of the intent behind this module,
i.e. what kinds of subroutines it is to contain.

Here, and in later commits, the modules it depends on are named like
Git.pm instead of Gitweb::Git in the commit message.  This is a minor
issue.

>   gitweb: Create Gitweb::View module

If it contains subroutines related only to HTML output, why isn't it
called Gitweb::HTML then?  If it contains some subroutines which are
not strictly about HTML, it should be stated so in the commit message.

Also in the commit messages dividing moved subroutines into groups
should be done better.

>   gitweb: Create Gitweb::Util module

Just a question: shouldn't git_get_last_activity subroutine be in
Gitweb::RepoConfig module?  Or is Gitweb::RepoConfig only about "static"
properties of a repository?

This is result of not well enough described goal of Gitweb::RepoConfig
module, see above.

>   gitweb: Create Gitweb::Format module

Straighforward moving of format_* subroutines and avatar formatting.
The only thing that could be improved is describing why avatar related
subroutines were put there.

>   gitweb: Create Gitweb::Parse module

The commit message might be improved by stating that it is output of
git commands that gets parsed.  There is also odd duck of 
parsed_difftree_line subroutine.

-- 
Jakub Narebski
Poland

^ permalink raw reply	[flat|nested] 27+ messages in thread

end of thread, other threads:[~2010-08-02 15:04 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-07-15  7:29 [PATCHv2 00/11] Splitting gitweb Pavan Kumar Sunkara
2010-07-15  7:29 ` [PATCHv2 GSOC 01/11] gitweb: fix esc_url Pavan Kumar Sunkara
2010-07-15 13:52   ` Jakub Narebski
2010-07-15 18:57     ` Junio C Hamano
2010-07-15 19:32       ` Jakub Narebski
2010-07-15  7:29 ` [PATCHv2 GSOC 02/11] gitweb: Prepare for splitting gitweb Pavan Kumar Sunkara
2010-07-15 18:05   ` Jakub Narebski
2010-07-15  7:29 ` [PATCHv2 GSOC 03/11] gitweb: Create Gitweb::Git module Pavan Kumar Sunkara
2010-07-15 20:13   ` Jakub Narebski
2010-07-15  7:29 ` [PATCHv2 GSOC 04/11] gitweb: Create Gitweb::Config module Pavan Kumar Sunkara
2010-07-15 21:21   ` Jakub Narebski
2010-07-15  7:29 ` [PATCHv2 GSOC 05/11] gitweb: Create Gitweb::Request module Pavan Kumar Sunkara
2010-07-16  0:11   ` Jakub Narebski
2010-07-15  7:29 ` [PATCHv2 GSOC 06/11] gitweb: Create Gitweb::Escape module Pavan Kumar Sunkara
2010-07-16  9:01   ` Jakub Narebski
2010-07-15  7:29 ` [PATCHv2 GSOC 07/11] gitweb: Create Gitweb::RepoConfig module Pavan Kumar Sunkara
2010-07-16 12:11   ` Jakub Narebski
2010-07-15  7:29 ` [PATCHv2 GSOC 08/11] gitweb: Create Gitweb::View module Pavan Kumar Sunkara
2010-07-18 15:10   ` Jakub Narebski
2010-07-15  7:29 ` [PATCHv2 GSOC 09/11] gitweb: Create Gitweb::Util module Pavan Kumar Sunkara
2010-07-18 17:45   ` Jakub Narebski
2010-07-15  7:29 ` [PATCHv2 GSOC 10/11] gitweb: Create Gitweb::Format module Pavan Kumar Sunkara
2010-07-18 20:16   ` Jakub Narebski
2010-07-15  7:29 ` [PATCHv2 GSOC 11/11] gitweb: Create Gitweb::Parse module Pavan Kumar Sunkara
2010-07-19 14:55   ` Jakub Narebski
2010-08-01 20:44 ` [PATCHv2 00/11] Splitting gitweb Sverre Rabbelier
2010-08-02 15:03   ` Jakub Narebski

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.