git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 00/11 GSoC] gitweb: Split gitweb into modules
@ 2010-06-21 22:00 Pavan Kumar Sunkara
  2010-06-21 22:00 ` [PATCH 01/11 GSoC] gitweb: fix esc_url Pavan Kumar Sunkara
                   ` (12 more replies)
  0 siblings, 13 replies; 21+ messages in thread
From: Pavan Kumar Sunkara @ 2010-06-21 22:00 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

shortlog:
=======

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 425e225 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 most of 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.

validate_project and validate_action can't be included in this
module due to unmet dependencies

Subroutines moved:
	evaluate_uri
	evaluate_query_params
	validate_pathname
	validate_refname

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
	unquote
	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
	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. Some subroutines which
output HTML but are not included in this module due
to unmet dependencies

Subroutines moved:
	href
	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
	get_feed_info
	insert_file
	git_print_header_div
	print_local_time
	format_local_time
	git_print_page_path
	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:
	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              | 3855 +++------------------------------------
 gitweb/lib/Gitweb/Config.pm     |  498 +++++
 gitweb/lib/Gitweb/Escape.pm     |  175 ++
 gitweb/lib/Gitweb/Format.pm     |  537 ++++++
 gitweb/lib/Gitweb/Git.pm        |   48 +
 gitweb/lib/Gitweb/Parse.pm      |  378 ++++
 gitweb/lib/Gitweb/RepoConfig.pm |  424 +++++
 gitweb/lib/Gitweb/Request.pm    |  153 ++
 gitweb/lib/Gitweb/Util.pm       |  447 +++++
 gitweb/lib/Gitweb/View.pm       | 1022 +++++++++++
 11 files changed, 3909 insertions(+), 3642 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] 21+ messages in thread

* [PATCH 01/11 GSoC] gitweb: fix esc_url
  2010-06-21 22:00 [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Pavan Kumar Sunkara
@ 2010-06-21 22:00 ` Pavan Kumar Sunkara
  2010-06-21 22:00 ` [PATCH 02/11 GSoC] gitweb: Prepare for splitting gitweb Pavan Kumar Sunkara
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 21+ messages in thread
From: Pavan Kumar Sunkara @ 2010-06-21 22:00 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 425e225 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.454.g276eb9.dirty

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

* [PATCH 02/11 GSoC] gitweb: Prepare for splitting gitweb
  2010-06-21 22:00 [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Pavan Kumar Sunkara
  2010-06-21 22:00 ` [PATCH 01/11 GSoC] gitweb: fix esc_url Pavan Kumar Sunkara
@ 2010-06-21 22:00 ` Pavan Kumar Sunkara
  2010-06-21 22:00 ` [PATCH 03/11 GSoC] gitweb: Create Gitweb::Git module Pavan Kumar Sunkara
                   ` (10 subsequent siblings)
  12 siblings, 0 replies; 21+ messages in thread
From: Pavan Kumar Sunkara @ 2010-06-21 22:00 UTC (permalink / raw)
  To: git, jnareb, chriscool, pasky; +Cc: Pavan Kumar Sunkara

From: Jakub Narebski <jnareb@gmail.com>

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..a8464a9 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.454.g276eb9.dirty

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

* [PATCH 03/11 GSoC] gitweb: Create Gitweb::Git module
  2010-06-21 22:00 [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Pavan Kumar Sunkara
  2010-06-21 22:00 ` [PATCH 01/11 GSoC] gitweb: fix esc_url Pavan Kumar Sunkara
  2010-06-21 22:00 ` [PATCH 02/11 GSoC] gitweb: Prepare for splitting gitweb Pavan Kumar Sunkara
@ 2010-06-21 22:00 ` Pavan Kumar Sunkara
  2010-06-21 22:00 ` [PATCH 04/11 GSoC] gitweb: Create Gitweb::Config module Pavan Kumar Sunkara
                   ` (9 subsequent siblings)
  12 siblings, 0 replies; 21+ messages in thread
From: Pavan Kumar Sunkara @ 2010-06-21 22:00 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 a8464a9..229aead 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.454.g276eb9.dirty

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

* [PATCH 04/11 GSoC] gitweb: Create Gitweb::Config module
  2010-06-21 22:00 [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Pavan Kumar Sunkara
                   ` (2 preceding siblings ...)
  2010-06-21 22:00 ` [PATCH 03/11 GSoC] gitweb: Create Gitweb::Git module Pavan Kumar Sunkara
@ 2010-06-21 22:00 ` Pavan Kumar Sunkara
  2010-06-21 22:00 ` [PATCH 05/11 GSoC] gitweb: Create Gitweb::Request module Pavan Kumar Sunkara
                   ` (8 subsequent siblings)
  12 siblings, 0 replies; 21+ messages in thread
From: Pavan Kumar Sunkara @ 2010-06-21 22:00 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 most of 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 229aead..ea59972 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.454.g276eb9.dirty

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

* [PATCH 05/11 GSoC] gitweb: Create Gitweb::Request module
  2010-06-21 22:00 [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Pavan Kumar Sunkara
                   ` (3 preceding siblings ...)
  2010-06-21 22:00 ` [PATCH 04/11 GSoC] gitweb: Create Gitweb::Config module Pavan Kumar Sunkara
@ 2010-06-21 22:00 ` Pavan Kumar Sunkara
  2010-06-21 22:00 ` [PATCH 06/11 GSoC] gitweb: Create Gitweb::Escape module Pavan Kumar Sunkara
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 21+ messages in thread
From: Pavan Kumar Sunkara @ 2010-06-21 22:00 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.

validate_project and validate_action can't be included in this
module due to unmet dependencies

Subroutines moved:
	evaluate_uri
	evaluate_query_params
	validate_pathname
	validate_refname

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           |  163 ++++--------------------------------------
 gitweb/lib/Gitweb/Request.pm |  153 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 168 insertions(+), 149 deletions(-)
 create mode 100644 gitweb/lib/Gitweb/Request.pm

diff --git a/gitweb/Makefile b/gitweb/Makefile
index ea59972..8c42724 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..db2f682 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';
@@ -814,39 +712,6 @@ sub validate_project {
 	}
 }
 
-sub validate_pathname {
-	my $input = shift || return undef;
-
-	# no '.' or '..' as elements of path, i.e. no '.' nor '..'
-	# at the beginning, at the end, and between slashes.
-	# also this catches doubled slashes
-	if ($input =~ m!(^|/)(|\.|\.\.)(/|$)!) {
-		return undef;
-	}
-	# no null characters
-	if ($input =~ m!\0!) {
-		return undef;
-	}
-	return $input;
-}
-
-sub validate_refname {
-	my $input = shift || return undef;
-
-	# textual hashes are O.K.
-	if ($input =~ m/^[0-9a-fA-F]{40}$/) {
-		return $input;
-	}
-	# it must be correct pathname
-	$input = validate_pathname($input)
-		or return undef;
-	# restrictions on ref name according to git-check-ref-format
-	if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) {
-		return undef;
-	}
-	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
diff --git a/gitweb/lib/Gitweb/Request.pm b/gitweb/lib/Gitweb/Request.pm
new file mode 100644
index 0000000..f2f3cfb
--- /dev/null
+++ b/gitweb/lib/Gitweb/Request.pm
@@ -0,0 +1,153 @@
+#!/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 
+                 validate_pathname validate_refname);
+
+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 || "/";
+}
+
+sub validate_pathname {
+	my $input = shift || return undef;
+
+	# no '.' or '..' as elements of path, i.e. no '.' nor '..'
+	# at the beginning, at the end, and between slashes.
+	# also this catches doubled slashes
+	if ($input =~ m!(^|/)(|\.|\.\.)(/|$)!) {
+		return undef;
+	}
+	# no null characters
+	if ($input =~ m!\0!) {
+		return undef;
+	}
+	return $input;
+}
+
+sub validate_refname {
+	my $input = shift || return undef;
+
+	# textual hashes are O.K.
+	if ($input =~ m/^[0-9a-fA-F]{40}$/) {
+		return $input;
+	}
+	# it must be correct pathname
+	$input = validate_pathname($input)
+		or return undef;
+	# restrictions on ref name according to git-check-ref-format
+	if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) {
+		return undef;
+	}
+	return $input;
+}
+
+1;
-- 
1.7.1.454.g276eb9.dirty

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

* [PATCH 06/11 GSoC] gitweb: Create Gitweb::Escape module
  2010-06-21 22:00 [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Pavan Kumar Sunkara
                   ` (4 preceding siblings ...)
  2010-06-21 22:00 ` [PATCH 05/11 GSoC] gitweb: Create Gitweb::Request module Pavan Kumar Sunkara
@ 2010-06-21 22:00 ` Pavan Kumar Sunkara
  2010-06-21 22:00 ` [PATCH 07/11 GSoC] gitweb: Create Gitweb::RepoConfig module Pavan Kumar Sunkara
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 21+ messages in thread
From: Pavan Kumar Sunkara @ 2010-06-21 22:00 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
	unquote
	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          |  156 +--------------------------------------
 gitweb/lib/Gitweb/Escape.pm |  175 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 177 insertions(+), 155 deletions(-)
 create mode 100644 gitweb/lib/Gitweb/Escape.pm

diff --git a/gitweb/Makefile b/gitweb/Makefile
index 8c42724..c77188d 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 db2f682..87281d0 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'};
@@ -712,160 +712,6 @@ sub validate_project {
 	}
 }
 
-# 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;
-
-	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;
-}
-
-# 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..d0ac715
--- /dev/null
+++ b/gitweb/lib/Gitweb/Escape.pm
@@ -0,0 +1,175 @@
+#!/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 unquote 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\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
+	$str =~ s/\+/%2B/g;
+	$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;
+
+	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;
+}
+
+# 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.454.g276eb9.dirty

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

* [PATCH 07/11 GSoC] gitweb: Create Gitweb::RepoConfig module
  2010-06-21 22:00 [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Pavan Kumar Sunkara
                   ` (5 preceding siblings ...)
  2010-06-21 22:00 ` [PATCH 06/11 GSoC] gitweb: Create Gitweb::Escape module Pavan Kumar Sunkara
@ 2010-06-21 22:00 ` Pavan Kumar Sunkara
  2010-06-21 22:00 ` [PATCH 08/11 GSoC] gitweb: Create Gitweb::View module Pavan Kumar Sunkara
                   ` (5 subsequent siblings)
  12 siblings, 0 replies; 21+ messages in thread
From: Pavan Kumar Sunkara @ 2010-06-21 22:00 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
	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              |  400 +------------------------------------
 gitweb/lib/Gitweb/RepoConfig.pm |  424 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 426 insertions(+), 399 deletions(-)
 create mode 100644 gitweb/lib/Gitweb/RepoConfig.pm

diff --git a/gitweb/Makefile b/gitweb/Makefile
index c77188d..3ae6745 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 87281d0..87f52d2 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'};
@@ -110,23 +110,6 @@ sub feature_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.
@@ -712,12 +695,6 @@ sub validate_project {
 	}
 }
 
-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
 
@@ -1526,129 +1503,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;
@@ -1703,245 +1557,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;
@@ -2449,19 +2064,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..fa7d909
--- /dev/null
+++ b/gitweb/lib/Gitweb/RepoConfig.pm
@@ -0,0 +1,424 @@
+#!/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);
+
+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 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.454.g276eb9.dirty

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

* [PATCH 08/11 GSoC] gitweb: Create Gitweb::View module
  2010-06-21 22:00 [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Pavan Kumar Sunkara
                   ` (6 preceding siblings ...)
  2010-06-21 22:00 ` [PATCH 07/11 GSoC] gitweb: Create Gitweb::RepoConfig module Pavan Kumar Sunkara
@ 2010-06-21 22:00 ` Pavan Kumar Sunkara
  2010-06-21 22:00 ` [PATCH 09/11 GSoC] gitweb: Create Gitweb::Util module Pavan Kumar Sunkara
                   ` (4 subsequent siblings)
  12 siblings, 0 replies; 21+ messages in thread
From: Pavan Kumar Sunkara @ 2010-06-21 22:00 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. Some subroutines which
output HTML but are not included in this module due
to unmet dependencies

Subroutines moved:
	href
	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
	get_feed_info
	insert_file
	git_print_header_div
	print_local_time
	format_local_time
	git_print_page_path
	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 3ae6745..cd2555d 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 87f52d2..909dd81 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'};
@@ -553,128 +554,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 {
@@ -696,219 +575,6 @@ sub validate_project {
 }
 
 ## ----------------------------------------------------------------------
-## 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
 
@@ -1419,46 +1085,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
 
@@ -2061,18 +1687,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
 
@@ -2209,405 +1823,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;
@@ -2652,44 +1870,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 = @_;
@@ -2739,157 +1919,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
 
@@ -3426,29 +2455,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.454.g276eb9.dirty

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

* [PATCH 09/11 GSoC] gitweb: Create Gitweb::Util module
  2010-06-21 22:00 [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Pavan Kumar Sunkara
                   ` (7 preceding siblings ...)
  2010-06-21 22:00 ` [PATCH 08/11 GSoC] gitweb: Create Gitweb::View module Pavan Kumar Sunkara
@ 2010-06-21 22:00 ` Pavan Kumar Sunkara
  2010-06-21 22:00 ` [PATCH 10/11 GSoC] gitweb: Create Gitweb::Format module Pavan Kumar Sunkara
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 21+ messages in thread
From: Pavan Kumar Sunkara @ 2010-06-21 22:00 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 cd2555d..280ff6d 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 909dd81..a3de63e 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'};
@@ -1086,164 +1087,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 {
@@ -1599,231 +1442,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
@@ -1922,42 +1540,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.454.g276eb9.dirty

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

* [PATCH 10/11 GSoC] gitweb: Create Gitweb::Format module
  2010-06-21 22:00 [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Pavan Kumar Sunkara
                   ` (8 preceding siblings ...)
  2010-06-21 22:00 ` [PATCH 09/11 GSoC] gitweb: Create Gitweb::Util module Pavan Kumar Sunkara
@ 2010-06-21 22:00 ` Pavan Kumar Sunkara
  2010-06-21 22:00 ` [PATCH 11/11 GSoC] gitweb: Create Gitweb::Parse module Pavan Kumar Sunkara
                   ` (2 subsequent siblings)
  12 siblings, 0 replies; 21+ messages in thread
From: Pavan Kumar Sunkara @ 2010-06-21 22:00 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 280ff6d..1ef0b7a 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 a3de63e..e5f3cc6 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'};
@@ -576,517 +577,6 @@ sub validate_project {
 }
 
 ## ----------------------------------------------------------------------
-## 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.454.g276eb9.dirty

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

* [PATCH 11/11 GSoC] gitweb: Create Gitweb::Parse module
  2010-06-21 22:00 [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Pavan Kumar Sunkara
                   ` (9 preceding siblings ...)
  2010-06-21 22:00 ` [PATCH 10/11 GSoC] gitweb: Create Gitweb::Format module Pavan Kumar Sunkara
@ 2010-06-21 22:00 ` Pavan Kumar Sunkara
  2010-06-21 22:30 ` [PATCH GSoC] gitweb: Add support for enabling 'write' feature Pavan Kumar Sunkara
  2010-06-22 11:11 ` [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Jakub Narebski
  12 siblings, 0 replies; 21+ messages in thread
From: Pavan Kumar Sunkara @ 2010-06-21 22:00 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:
	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         |  356 +-----------------------------------------
 gitweb/lib/Gitweb/Parse.pm |  378 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 380 insertions(+), 355 deletions(-)
 create mode 100644 gitweb/lib/Gitweb/Parse.pm

diff --git a/gitweb/Makefile b/gitweb/Makefile
index 1ef0b7a..6eadc37 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 e5f3cc6..70b7e43 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'};
@@ -576,361 +577,6 @@ sub validate_project {
 	}
 }
 
-## ----------------------------------------------------------------------
-## 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..a765ea6
--- /dev/null
+++ b/gitweb/lib/Gitweb/Parse.pm
@@ -0,0 +1,378 @@
+#!/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);
+
+use Gitweb::Git qw(git_cmd);
+use Gitweb::Request qw(@extra_options $hash $hash_parent);
+use Gitweb::Escape qw(unquote to_utf8);
+use Gitweb::View qw(href chop_str age_string die_error);
+use Gitweb::Util qw(fill_from_file_info is_deleted);
+
+## ----------------------------------------------------------------------
+## 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.454.g276eb9.dirty

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

* [PATCH GSoC] gitweb: Add support for enabling 'write' feature
  2010-06-21 22:00 [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Pavan Kumar Sunkara
                   ` (10 preceding siblings ...)
  2010-06-21 22:00 ` [PATCH 11/11 GSoC] gitweb: Create Gitweb::Parse module Pavan Kumar Sunkara
@ 2010-06-21 22:30 ` Pavan Kumar Sunkara
  2010-06-22 11:12   ` Jakub Narebski
  2010-06-22 11:11 ` [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Jakub Narebski
  12 siblings, 1 reply; 21+ messages in thread
From: Pavan Kumar Sunkara @ 2010-06-21 22:30 UTC (permalink / raw)
  To: git, jnareb, chriscool, pasky; +Cc: Pavan Kumar Sunkara

Provide $feature{'write'} to enable the 'write'
functionalities for gitweb. While outputting HTML
it checks for gitweb_check_feature('write') and if
it's enabled, proper links will appear along with
the HTML divs.

Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
---
 This patch is based on my previous patch series.
 http://mid.gmane.org/1277157648-6029-1-git-send-email-pavan.sss1991@gmail.com
 
 And this is my first patch in the preparation for integration
 of write functionality into gitweb.

 gitweb/lib/Gitweb/Config.pm |   11 +++++++++++
 1 files changed, 11 insertions(+), 0 deletions(-)

diff --git a/gitweb/lib/Gitweb/Config.pm b/gitweb/lib/Gitweb/Config.pm
index c528595..87f0ba6 100644
--- a/gitweb/lib/Gitweb/Config.pm
+++ b/gitweb/lib/Gitweb/Config.pm
@@ -203,6 +203,17 @@ our %feature = (
 	# (an array) or gitweb_check_feature(<feature>) to check if <feature>
 	# is enabled
 
+	# Enable the 'write' functionalities for gitweb. While outputting HTML
+	# it checks for gitweb_check_feature('write') and if it's enabled,
+	# proper links will appear along with the HTML divs.
+	
+	# To enable system wide have in $GITWEB_CONFIG
+	# $feature{'write'}{'default'} = [1];
+	# Project specific override is not supported.
+	'write' => {
+		'override' => 0,
+		'default' => [0]},
+
 	# Enable the 'blame' blob view, showing the last commit that modified
 	# each line in the file. This can be very CPU-intensive.
 
-- 
1.7.1.454.g6bbe9.dirty

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

* Re: [PATCH 00/11 GSoC] gitweb: Split gitweb into modules
  2010-06-21 22:00 [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Pavan Kumar Sunkara
                   ` (11 preceding siblings ...)
  2010-06-21 22:30 ` [PATCH GSoC] gitweb: Add support for enabling 'write' feature Pavan Kumar Sunkara
@ 2010-06-22 11:11 ` Jakub Narebski
  2010-06-22 18:34   ` Pavan Kumar Sunkara
  12 siblings, 1 reply; 21+ messages in thread
From: Jakub Narebski @ 2010-06-22 11:11 UTC (permalink / raw)
  To: Pavan Kumar Sunkara; +Cc: git, Petr Baudis, Christian Couder

Thank you very much for providing such detailed cover letter.  It makes
commenting on this series much easier.


On Tue, 22 June 2010 at 03:30:36 +0530, Pavan Kumar Sunkara wrote:

> The patch series is based on 'pu' branch.

You should probably say also which commit (which branch) this depends
on, rather than blanket 'pu'.

> 
> 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.

I think it would be better to send this patch separately, standalone
(not in series), based on 'master' or even 'maint' to have it applied,
and not being held back by being in this series.  This is pure bugfix
for a (rare) bug.

> 
> The second patch is produced by my commit amend to Jakub Narebski'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.

Thanks for finding it, and for improving it.

Actually, as long as we have only individual files in $(GITWEB_MODULES),
it should not matter.  The version before changes used

  install [OPTION]... SOURCE DEST

form, and after changes it uses

  install [OPTION]... SOURCE... DIRECTORY

with a single SOURCE.  So I don't think it is strictly necessary, but
it might be a good improvement anyway.
> 
> 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

What I would like to see here is a dependency graph, i.e. which modules
include (use) which modules, and perhaps also how much this split
reduces size of main gitweb.perl file.

> 
> shortlog:
> =========

Nitpick: I would say "Table of contents", because shortlog it is not ;-)))

> 
> 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 425e225 has missed fixing esc_url.

I would probably write this commit message differently, saying that
original author (Giuseppe Bilotta) fixed esc_param, but didn't finish
fixing this bug by not fixing esc_url, and that this commit finishes
it.  Probably also use "fix esc_url, use CGI::escape" in subject.
But that's me, not you.

Note: the commit is 452e225, not 425e225 !!!
                     ^^           ^^

> 
> 
> 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.

It makes adding modules to install very easy, isn't it?
 
> 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.

The "processing output of git commands with external programs"
looks a bit strange to me.  If it is about quote_command()
subroutine, it is more about shell escaping of commands, or
about running other programs... but it might be O.K., if what
you want to say that quote_command() is used in pipe-ing output
of git commands (e.g. git-archive) to other programs (e.g. gzip).

So take the above comment with a bit of salt...

> 
> 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

O.K.

> 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 most of the configuration related variables and
> subroutines (Including those required for configuration
> of gitweb features).

"Most" meaning what?  Which subroutines weren't moved, and why (they
should not be in Gitweb::Config, they cannot be moved for technical
reasons)?

> 
> Subroutines moved:
> 	evaluate_gitweb_config
> 	configure_gitweb_features

I'm not sure if configure_gitweb_features() should be there, as it
is run-once subroutine, about flow of the main program... but perhaps
it is O.K. to have it here.

> 	filter_snapshot_fmts
> 	gitweb_get_feature
> 	gitweb_check_feature
> 
> Update gitweb/Makefile to install Gitweb::Config module alongside gitweb

O.K.

> 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.
> 
> validate_project and validate_action can't be included in this
> module due to unmet dependencies.

What unment dependencies?  Can it be worked around by changing their
signatures to use additional parameters, e.g.:

  sub validate_action {
  	my $input = shift || return undef;
  	my $actions = shift || return undef;
  
  	return undef unless exists $actions->{$input};
  	return $input;
  }

But I guess it would be very hard to do the same with validate_project.
Also such change might be out of scope for _this_ series.

> 
> Subroutines moved:
> 	evaluate_uri
> 	evaluate_query_params
> 	validate_pathname
> 	validate_refname

I'm not completly sure if validate_* subroutines should be not separate,
as they do require more knowledge about setup, and about git repositories
served, than the rest of Gitweb::Request subroutines...

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

O.K.

> 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

O.K.

> 	unquote

Shouldn't unquote be in Gitweb::Parse, as contrary to the rest of 
subroutines is is not about gitweb output escaping/quoting, but
about processing (parsing) output of git commands?  Perhaps it
could even be provate to Gitweb::Parse (i.e. not exported by
default)...

> 	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.

Why?  Is it to not have too many tiny modules, or is simply there no
single good place where to put this subroutine?

> 
> Subroutines moved:
> 	check_head_link
> 	check_export_ok

O.K.

> 	hash_set_multi
> 	config_to_bool
> 	config_to_int
> 	config_to_multi
> 	git_parse_project_config

Hmmm... I think that in the future all the subroutines dealing with
reading of gitweb config and making its values available should be
encapsulated in a separate module... Git::Config perhaps?

But I guess that is for the future commit.

> 	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

O.K.

> 	project_in_list

Hmmm...

> 
> 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.

I find that this module looks a bit like a collection of fairly unrelated
subroutines at very different levels of abstractions.  

I guess that you don't want to split gitweb into too many modules,
and splitting gitweb is more of one of steps to final goal of adding
write functionality to gitweb, rather than the goal in and of itself.
Nevertheless it would be good to be able to immediately know from the
description of module what kind of subroutines should be there.

> 
> This module depends on Git.pm, Config.pm, Request.pm,
> Escape.pm and RepoConfig.pm. Some subroutines which
> output HTML but are not included in this module due
> to unmet dependencies.

Which subroutines and what unmet dependencies?

> 
> Subroutines moved:

Perhaps this list should be grouped in categories, either by separating
groups with empty line, or by putting '*' before first subroutine in
group, as shown in example below: 

> 	href

Hmmm... that's the opposte side of Gitweb::Request, crating rather than
parsing gitweb links...

> 	get_feed_info

To be passed to href(); I think it is the only one.

> 	chop_str
> 	chop_and_escape_str

Those are pure utility functions.

>     * age_class
>       age_string

Those are about coloring based on relative time and (age_string - not
a great name) generating relative time ("nn units ago").

>     * print_local_time
>       format_local_time

Shouldn't this be in Gitweb::Format?  If not, why?

>     * S_ISGITLINK
> 	mode_str
> 	file_type
> 	file_type_long

Those are all about types of files from git-ls-tree and friends, but
with single exception of S_ISGITLINK they are about gitweb output side.

> 	get_page_title
> 	git_header_html
> 	git_footer_html
> 	die_error

Main parts of page.

>     * git_print_page_nav
> 	format_paging_nav
> 	git_print_header_div
> 	git_print_page_path

Fragments of page (used in most of views).  This decidely belong to
Gitweb::View.

>     * insert_file

More of utility function.

>     * git_get_link_target
> 	normalize_link_target
> 	git_print_tree_entry

Should it be here?  The *_body subroutines aren't there in Gitweb::View.
The *_link_target are utility subroutines for git_print_tree_entry.

> 	print_sort_th
> 	format_sort_th

Hmmm...

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

I have my doubts...

> 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
> 	fill_from_file_info
> 	is_deleted
> 	is_patch_split

O.K.

> 	run_highlighter

_Perhaps_ with exception of this subroutine.

> 
> 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.

Why it depends on Gitweb::View, and not the reverse?  I understand why
it depends on Gitweb::Config and Gitweb::Escape, and I guess it needs
$cgi from Gitweb::Request (or is it something more?).

> 
> 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

O.K., I think.

> 
> 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.

Why it depends on Gitweb::View?  It is a strange dependency.
I understand depending on Gitweb::Git to some extent, I'm not sure
if we shouldn't simply move unescape to it instead of requiring
Gitweb::Escape (unless there is more: to_utf8 perhaps?), and
I understand that Gitweb::Util has some required subroutines.

> 
> Subroutines moved:
> 	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

O.K.

> 
> 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              | 3855 +++------------------------------------

Nice!

>  gitweb/lib/Gitweb/Config.pm     |  498 +++++
>  gitweb/lib/Gitweb/Escape.pm     |  175 ++
>  gitweb/lib/Gitweb/Format.pm     |  537 ++++++
>  gitweb/lib/Gitweb/Git.pm        |   48 +
>  gitweb/lib/Gitweb/Parse.pm      |  378 ++++
>  gitweb/lib/Gitweb/RepoConfig.pm |  424 +++++
>  gitweb/lib/Gitweb/Request.pm    |  153 ++
>  gitweb/lib/Gitweb/Util.pm       |  447 +++++
>  gitweb/lib/Gitweb/View.pm       | 1022 +++++++++++
>  11 files changed, 3909 insertions(+), 3642 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

Keep up good work!

-- 
Jakub Narebski
Poland

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

* Re: [PATCH GSoC] gitweb: Add support for enabling 'write' feature
  2010-06-21 22:30 ` [PATCH GSoC] gitweb: Add support for enabling 'write' feature Pavan Kumar Sunkara
@ 2010-06-22 11:12   ` Jakub Narebski
  2010-06-22 18:35     ` Pavan Kumar Sunkara
  0 siblings, 1 reply; 21+ messages in thread
From: Jakub Narebski @ 2010-06-22 11:12 UTC (permalink / raw)
  To: Pavan Kumar Sunkara; +Cc: git, chriscool, pasky

On Tue, 22 June 2010, Pavan Kumar Sunkara wrote:

> Provide $feature{'write'} to enable the 'write'
> functionalities for gitweb. While outputting HTML
> it checks for gitweb_check_feature('write') and if
> it's enabled, proper links will appear along with
> the HTML divs.
> 
> Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
> ---
>  This patch is based on my previous patch series.
>  http://mid.gmane.org/1277157648-6029-1-git-send-email-pavan.sss1991@gmail.com

It looks a bit like this email is send by accident...
  
>  And this is my first patch in the preparation for integration
>  of write functionality into gitweb.
> 
>  gitweb/lib/Gitweb/Config.pm |   11 +++++++++++
>  1 files changed, 11 insertions(+), 0 deletions(-)
> 
> diff --git a/gitweb/lib/Gitweb/Config.pm b/gitweb/lib/Gitweb/Config.pm
> index c528595..87f0ba6 100644
> --- a/gitweb/lib/Gitweb/Config.pm
> +++ b/gitweb/lib/Gitweb/Config.pm
> @@ -203,6 +203,17 @@ our %feature = (
>  	# (an array) or gitweb_check_feature(<feature>) to check if <feature>
>  	# is enabled
>  
> +	# Enable the 'write' functionalities for gitweb. While outputting HTML
> +	# it checks for gitweb_check_feature('write') and if it's enabled,
> +	# proper links will appear along with the HTML divs.
> +	
> +	# To enable system wide have in $GITWEB_CONFIG
> +	# $feature{'write'}{'default'} = [1];
> +	# Project specific override is not supported.
> +	'write' => {
> +		'override' => 0,
> +		'default' => [0]},
> +
>  	# Enable the 'blame' blob view, showing the last commit that modified
>  	# each line in the file. This can be very CPU-intensive.

...as there is nothing non-trivial in there.

-- 
Jakub Narebski
Poland

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

* Re: [PATCH 00/11 GSoC] gitweb: Split gitweb into modules
  2010-06-22 11:11 ` [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Jakub Narebski
@ 2010-06-22 18:34   ` Pavan Kumar Sunkara
  2010-06-22 20:29     ` Jakub Narebski
  0 siblings, 1 reply; 21+ messages in thread
From: Pavan Kumar Sunkara @ 2010-06-22 18:34 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: git, Petr Baudis, Christian Couder

2010/6/22 Jakub Narebski <jnareb@gmail.com>:
> Thank you very much for providing such detailed cover letter.  It makes
> commenting on this series much easier.
>
>
> On Tue, 22 June 2010 at 03:30:36 +0530, Pavan Kumar Sunkara wrote:
>
>> The patch series is based on 'pu' branch.
>
> You should probably say also which commit (which branch) this depends
> on, rather than blanket 'pu'.
>
>>
>> 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.
>
> I think it would be better to send this patch separately, standalone
> (not in series), based on 'master' or even 'maint' to have it applied,
> and not being held back by being in this series.  This is pure bugfix
> for a (rare) bug.
>
>>
>> The second patch is produced by my commit amend to Jakub Narebski'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.
>
> Thanks for finding it, and for improving it.
>
> Actually, as long as we have only individual files in $(GITWEB_MODULES),
> it should not matter.  The version before changes used
>
>  install [OPTION]... SOURCE DEST
>
> form, and after changes it uses
>
>  install [OPTION]... SOURCE... DIRECTORY
>
> with a single SOURCE.  So I don't think it is strictly necessary, but
> it might be a good improvement anyway.

Thanks.

>>
>> 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
>
> What I would like to see here is a dependency graph, i.e. which modules
> include (use) which modules, and perhaps also how much this split
> reduces size of main gitweb.perl file.

The dependency is written in individual commits.

>>
>> shortlog:
>> =========
>
> Nitpick: I would say "Table of contents", because shortlog it is not ;-)))
>
>>
>> 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 425e225 has missed fixing esc_url.
>
> I would probably write this commit message differently, saying that
> original author (Giuseppe Bilotta) fixed esc_param, but didn't finish
> fixing this bug by not fixing esc_url, and that this commit finishes
> it.  Probably also use "fix esc_url, use CGI::escape" in subject.
> But that's me, not you.
>
> Note: the commit is 452e225, not 425e225 !!!
>                     ^^           ^^

ok.

>>
>>
>> 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.
>
> It makes adding modules to install very easy, isn't it?
yes.

>> 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.
>
> The "processing output of git commands with external programs"
> looks a bit strange to me.  If it is about quote_command()
> subroutine, it is more about shell escaping of commands, or
> about running other programs... but it might be O.K., if what
> you want to say that quote_command() is used in pipe-ing output
> of git commands (e.g. git-archive) to other programs (e.g. gzip).
>
> So take the above comment with a bit of salt...

actually that message has been written by you itself in one of the
earlier threads.

>>
>> This module is intended as standalone module, which does n>ot 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
>
> O.K.
>
>> 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 most of the configuration related variables and
>> subroutines (Including those required for configuration
>> of gitweb features).
>
> "Most" meaning what?  Which subroutines weren't moved, and why (they
> should not be in Gitweb::Config, they cannot be moved for technical
> reasons)?

Yes, due to either unment dependency or circular dependency problem.
But no need to worry as they are very small.

>>
>> Subroutines moved:
>>       evaluate_gitweb_config
>>       configure_gitweb_features
>
> I'm not sure if configure_gitweb_features() should be there, as it
> is run-once subroutine, about flow of the main program... but perhaps
> it is O.K. to have it here.

That's what I thought.

>>       filter_snapshot_fmts
>>       gitweb_get_feature
>>       gitweb_check_feature
>>
>> Update gitweb/Makefile to install Gitweb::Config module alongside gitweb
>
> O.K.
>
>> 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.
>>
>> validate_project and validate_action can't be included in this
>> module due to unmet dependencies.
>
> What unment dependencies?  Can it be worked around by changing their
> signatures to use additional parameters, e.g.:
>
>  sub validate_action {
>        my $input = shift || return undef;
>        my $actions = shift || return undef;
>
>        return undef unless exists $actions->{$input};
>        return $input;
>  }
>

Yes. I think we can but don't you think that we need to import
$actions everytime we use validate_action.
So, I figured it would be better If i leave it untouched for now.

> But I guess it would be very hard to do the same with validate_project.
> Also such change might be out of scope for _this_ series.
>
>>
>> Subroutines moved:
>>       evaluate_uri
>>       evaluate_query_params
>>       validate_pathname
>>       validate_refname
>
> I'm not completly sure if validate_* subroutines should be not separate,
> as they do require more knowledge about setup, and about git repositories
> served, than the rest of Gitweb::Request subroutines...

But they are being used in Gitweb::Request subroutines, so I included
them in there.

>>
>> Update gitweb/Makefile to install Gitweb::Request module alongside gitweb
>
> O.K.
>
>> 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
>
> O.K.
>
>>       unquote
>
> Shouldn't unquote be in Gitweb::Parse, as contrary to the rest of
> subroutines is is not about gitweb output escaping/quoting, but
> about processing (parsing) output of git commands?  Perhaps it
> could even be provate to Gitweb::Parse (i.e. not exported by
> default)...

It would result in circular dependency. So, Escape module is best for
it's place.

>>       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.
>
> Why?  Is it to not have too many tiny modules, or is simply there no
> single good place where to put this subroutine?

There is no single good place. Anyhow, as they are regarding project
configuration, I thought it would be better place for it.

>>
>> Subroutines moved:
>>       check_head_link
>>       check_export_ok
>
> O.K.
>
>>       hash_set_multi
>>       config_to_bool
>>       config_to_int
>>       config_to_multi
>>       git_parse_project_config
>
> Hmmm... I think that in the future all the subroutines dealing with
> reading of gitweb config and making its values available should be
> encapsulated in a separate module... Git::Config perhaps?
>
> But I guess that is for the future commit.
>
>>       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
>
> O.K.
>
>>       project_in_list
>
> Hmmm...
>
>>
>> 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.
>
> I find that this module looks a bit like a collection of fairly unrelated
> subroutines at very different levels of abstractions.
>
> I guess that you don't want to split gitweb into too many modules,
> and splitting gitweb is more of one of steps to final goal of adding
> write functionality to gitweb, rather than the goal in and of itself.
> Nevertheless it would be good to be able to immediately know from the
> description of module what kind of subroutines should be there.
>
>>
>> This module depends on Git.pm, Config.pm, Request.pm,
>> Escape.pm and RepoConfig.pm. Some subroutines which
>> output HTML but are not included in this module due
>> to unmet dependencies.
>
> Which subroutines and what unmet dependencies?

action specific HTML divs. They include format_* and parse_* subs.

>>
>> Subroutines moved:
>
> Perhaps this list should be grouped in categories, either by separating
> groups with empty line, or by putting '*' before first subroutine in
> group, as shown in example below:
>
>>       href
>
> Hmmm... that's the opposte side of Gitweb::Request, crating rather than
> parsing gitweb links...
>
>>       get_feed_info
>
> To be passed to href(); I think it is the only one.
>
>>       chop_str
>>       chop_and_escape_str
>
> Those are pure utility functions.
>
>>     * age_class
>>       age_string
>
> Those are about coloring based on relative time and (age_string - not
> a great name) generating relative time ("nn units ago").
>
>>     * print_local_time
>>       format_local_time
>
> Shouldn't this be in Gitweb::Format?  If not, why?
>
>>     * S_ISGITLINK
>>       mode_str
>>       file_type
>>       file_type_long
>
> Those are all about types of files from git-ls-tree and friends, but
> with single exception of S_ISGITLINK they are about gitweb output side.
>
>>       get_page_title
>>       git_header_html
>>       git_footer_html
>>       die_error
>
> Main parts of page.
>
>>     * git_print_page_nav
>>       format_paging_nav
>>       git_print_header_div
>>       git_print_page_path
>
> Fragments of page (used in most of views).  This decidely belong to
> Gitweb::View.
>
>>     * insert_file
>
> More of utility function.
>
>>     * git_get_link_target
>>       normalize_link_target
>>       git_print_tree_entry
>
> Should it be here?  The *_body subroutines aren't there in Gitweb::View.
> The *_link_target are utility subroutines for git_print_tree_entry.
>
>>       print_sort_th
>>       format_sort_th
>
> Hmmm...

Ok.

>>
>> Update 'gitweb/Makefile' to install Gitweb::View alongside gitweb.
>
> I have my doubts...
>
>> 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
>>       fill_from_file_info
>>       is_deleted
>>       is_patch_split
>
> O.K.
>
>>       run_highlighter
>
> _Perhaps_ with exception of this subroutine.

Well. It was in the utility category in gitweb.perl script. So, I
added it in here without giving much thought.

>>
>> 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.
>
> Why it depends on Gitweb::View, and not the reverse?  I understand why
> it depends on Gitweb::Config and Gitweb::Escape, and I guess it needs
> $cgi from Gitweb::Request (or is it something more?).

$hash, $hash_parent also.

>>
>> 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
>
> O.K., I think.
>
>>
>> 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.
>
> Why it depends on Gitweb::View?  It is a strange dependency.
> I understand depending on Gitweb::Git to some extent, I'm not sure
> if we shouldn't simply move unescape to it instead of requiring
> Gitweb::Escape (unless there is more: to_utf8 perhaps?), and
> I understand that Gitweb::Util has some required subroutines.

for die_error.

>>
>> Subroutines moved:
>>       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
>
> O.K.
>
>>
>> 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              | 3855 +++------------------------------------
>
> Nice!
>
>>  gitweb/lib/Gitweb/Config.pm     |  498 +++++
>>  gitweb/lib/Gitweb/Escape.pm     |  175 ++
>>  gitweb/lib/Gitweb/Format.pm     |  537 ++++++
>>  gitweb/lib/Gitweb/Git.pm        |   48 +
>>  gitweb/lib/Gitweb/Parse.pm      |  378 ++++
>>  gitweb/lib/Gitweb/RepoConfig.pm |  424 +++++
>>  gitweb/lib/Gitweb/Request.pm    |  153 ++
>>  gitweb/lib/Gitweb/Util.pm       |  447 +++++
>>  gitweb/lib/Gitweb/View.pm       | 1022 +++++++++++
>>  11 files changed, 3909 insertions(+), 3642 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
>
> Keep up good work!
>
> --
> Jakub Narebski
> Poland
>

So, If you can tell me what I need to change in this series, I would
like to complete it and send a second version so that you can ack it
and junio can merge it soon.

Thanks,
Pavan.

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

* Re: [PATCH GSoC] gitweb: Add support for enabling 'write' feature
  2010-06-22 11:12   ` Jakub Narebski
@ 2010-06-22 18:35     ` Pavan Kumar Sunkara
  2010-06-22 19:23       ` Jakub Narebski
  0 siblings, 1 reply; 21+ messages in thread
From: Pavan Kumar Sunkara @ 2010-06-22 18:35 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: git, chriscool, pasky

2010/6/22 Jakub Narebski <jnareb@gmail.com>:
> On Tue, 22 June 2010, Pavan Kumar Sunkara wrote:
>
>> Provide $feature{'write'} to enable the 'write'
>> functionalities for gitweb. While outputting HTML
>> it checks for gitweb_check_feature('write') and if
>> it's enabled, proper links will appear along with
>> the HTML divs.
>>
>> Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
>> ---
>>  This patch is based on my previous patch series.
>>  http://mid.gmane.org/1277157648-6029-1-git-send-email-pavan.sss1991@gmail.com
>
> It looks a bit like this email is send by accident...
>
>>  And this is my first patch in the preparation for integration
>>  of write functionality into gitweb.
>>
>>  gitweb/lib/Gitweb/Config.pm |   11 +++++++++++
>>  1 files changed, 11 insertions(+), 0 deletions(-)
>>
>> diff --git a/gitweb/lib/Gitweb/Config.pm b/gitweb/lib/Gitweb/Config.pm
>> index c528595..87f0ba6 100644
>> --- a/gitweb/lib/Gitweb/Config.pm
>> +++ b/gitweb/lib/Gitweb/Config.pm
>> @@ -203,6 +203,17 @@ our %feature = (
>>       # (an array) or gitweb_check_feature(<feature>) to check if <feature>
>>       # is enabled
>>
>> +     # Enable the 'write' functionalities for gitweb. While outputting HTML
>> +     # it checks for gitweb_check_feature('write') and if it's enabled,
>> +     # proper links will appear along with the HTML divs.
>> +
>> +     # To enable system wide have in $GITWEB_CONFIG
>> +     # $feature{'write'}{'default'} = [1];
>> +     # Project specific override is not supported.
>> +     'write' => {
>> +             'override' => 0,
>> +             'default' => [0]},
>> +
>>       # Enable the 'blame' blob view, showing the last commit that modified
>>       # each line in the file. This can be very CPU-intensive.
>
> ...as there is nothing non-trivial in there.
>
> --
> Jakub Narebski
> Poland
>

But don't u think it would be like a small starting patch.

Thanks,
Pavan.

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

* Re: [PATCH GSoC] gitweb: Add support for enabling 'write' feature
  2010-06-22 18:35     ` Pavan Kumar Sunkara
@ 2010-06-22 19:23       ` Jakub Narebski
  0 siblings, 0 replies; 21+ messages in thread
From: Jakub Narebski @ 2010-06-22 19:23 UTC (permalink / raw)
  To: Pavan Kumar Sunkara; +Cc: git, chriscool, pasky

Dnia wtorek 22. czerwca 2010 20:35, Pavan Kumar Sunkara napisał:
> 2010/6/22 Jakub Narebski <jnareb@gmail.com>:
>> On Tue, 22 June 2010, Pavan Kumar Sunkara wrote:
>>
>>> Provide $feature{'write'} to enable the 'write'
>>> functionalities for gitweb. While outputting HTML
>>> it checks for gitweb_check_feature('write') and if
>>> it's enabled, proper links will appear along with
>>> the HTML divs.
>>>
>>> Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>

>>> --- a/gitweb/lib/Gitweb/Config.pm
>>> +++ b/gitweb/lib/Gitweb/Config.pm
>>> @@ -203,6 +203,17 @@ our %feature = (
>>>       # (an array) or gitweb_check_feature(<feature>) to check if <feature>
>>>       # is enabled
>>>
>>> +     # Enable the 'write' functionalities for gitweb. While outputting HTML
>>> +     # it checks for gitweb_check_feature('write') and if it's enabled,
>>> +     # proper links will appear along with the HTML divs.
>>> +
>>> +     # To enable system wide have in $GITWEB_CONFIG
>>> +     # $feature{'write'}{'default'} = [1];
>>> +     # Project specific override is not supported.
>>> +     'write' => {
>>> +             'override' => 0,
>>> +             'default' => [0]},
>>> +
>>>       # Enable the 'blame' blob view, showing the last commit that modified
>>>       # each line in the file. This can be very CPU-intensive.
>>
>> ...as there is nothing non-trivial in there.
> 
> But don't u think it would be like a small starting patch.

There is staring small, and there is too small.

You don't provide _any_ write functionality here, or even any authorization
framework for write functionality, like checking if we are on localhost.

-- 
Jakub Narebski
Poland

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

* Re: [PATCH 00/11 GSoC] gitweb: Split gitweb into modules
  2010-06-22 18:34   ` Pavan Kumar Sunkara
@ 2010-06-22 20:29     ` Jakub Narebski
  2010-06-22 21:04       ` Pavan Kumar Sunkara
  0 siblings, 1 reply; 21+ messages in thread
From: Jakub Narebski @ 2010-06-22 20:29 UTC (permalink / raw)
  To: Pavan Kumar Sunkara; +Cc: git, Petr Baudis, Christian Couder

On Tue, 22 June 2010, Pavan Kumar Sunkara wrote:
> 
> So, If you can tell me what I need to change in this series, I would
> like to complete it and send a second version so that you can ack it
> and junio can merge it soon.

I think this series is of quite good quality, perhaps with the sole
exclusion of the problem with some modules not being clear of what they
should contain, but I don't think the issue is show-stopper.  Note that
any O.K. after description of module means that I think that it is good
as it is now.

More important than my ACK would be ACK from your mentors.

Also, you don't need to wait for the code to be merged.  That's what
(interactive) rebase and patch management interfaces like StGit, Guilt
and TopGit are for ;-)


> 2010/6/22 Jakub Narebski <jnareb@gmail.com>:
>> On Tue, 22 June 2010 at 03:30:36 +0530, Pavan Kumar Sunkara wrote:

>>> 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
>>
>> What I would like to see here is a dependency graph, i.e. which modules
>> include (use) which modules, and perhaps also how much this split
>> reduces size of main gitweb.perl file.
> 
> The dependency is written in individual commits.

I have noticed, and thank you for that.

Nevertheless some upfront description of module dependencies, as a
(partial) description of overall architecture, would be nice to have
here.  Something like:

   Gitweb::Git
   Gitweb::Config
     Gitweb::Git ($git_dir)
   Gitweb::Request
   Gitweb::Escape
     Gitweb::Config ($fallback_encoding)
   Gitweb::RepoConfig
     Gitweb::Git, Gitweb::Config, Gitweb::Escape, Gitweb::Request
   Gitweb::View
     Gitweb::Git, Gitweb::Config, Gitweb::Escape, Gitweb::Request
     Gitweb::RepoConfig
   Gitweb::Util
     ?
   Gitweb::Format
     Gitweb::Config, Gitweb::Escape, Gitweb::Request,
     Gitweb::View (?)
   Gitweb::Parse
     Gitweb::Git, Gitweb::Escape, Gitweb::Util,
     Gitweb::View (die_error)


>>> Shortlog:
>>> =========

>>> 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.
>>
>> The "processing output of git commands with external programs"
>> looks a bit strange to me.  If it is about quote_command()
>> subroutine, it is more about shell escaping of commands, or
>> about running other programs... but it might be O.K., if what
>> you want to say that quote_command() is used in pipe-ing output
>> of git commands (e.g. git-archive) to other programs (e.g. gzip).
>>
>> So take the above comment with a bit of salt...
> 
> Actually that message has been written by you itself in one of the
> earlier threads.

Well, as you can see coming up with a good commit message is not easy.
Nevertheless current message is, I guess, good enough.

>>> 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 most of the configuration related variables and
>>> subroutines (Including those required for configuration
>>> of gitweb features).
>>
>> "Most" meaning what?  Which subroutines weren't moved, and why (they
>> should not be in Gitweb::Config, they cannot be moved for technical
>> reasons)?
> 
> Yes, due to either unment dependency or circular dependency problem.
> But no need to worry as they are very small.

Could you _*LIST*_ those subroutines that you feel should belong in
Gitweb::Config but could not be put in it, and write _why_ they could not
be put (on what subroutines / variables do they depend that it makes
impossible to move them)?

>>>
>>> Subroutines moved:
[...]
>>>       configure_gitweb_features
>>
>> I'm not sure if configure_gitweb_features() should be there, as it
>> is run-once subroutine, about flow of the main program... but perhaps
>> it is O.K. to have it here.
> 
> That's what I thought.

All right.

By the way, in the future we might think about making this module use
object oriented interface, and/or tie-ing %features hash to it.  But that
is not something to worry about now.

>>> 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.
>>>
>>> validate_project and validate_action can't be included in this
>>> module due to unmet dependencies.
>>
>> What unment dependencies?  Can it be worked around by changing their
>> signatures to use additional parameters, e.g.:
>>
>>  sub validate_action {
>>        my $input = shift || return undef;
>>        my $actions = shift || return undef;
>>
>>        return undef unless exists $actions->{$input};
>>        return $input;
>>  }
> 
> Yes. I think we can but don't you think that we need to import
> %actions everytime we use validate_action.
> So, I figured it would be better If i leave it untouched for now.

Well, changing subroutines could be thought as being ot of scope of
current series, which is about splitting gitweb and moving subroutines to
appripriate modules... unless it would be not possible because of
circular dependencies problem (but I guess it is not the case,
ultimately).

> 
>> But I guess it would be very hard to do the same with validate_project.
>> Also such change might be out of scope for _this_ series.

Perhaps it would be better then to move _all_ validate_* subroutines to
separate Gitweb::Request::Validate module... unless they are used by some
subroutine from Gitweb::Request.

>>>
>>> Subroutines moved:
>>>       evaluate_uri
>>>       evaluate_query_params
>>>       validate_pathname
>>>       validate_refname
>>
>> I'm not completly sure if validate_* subroutines should be not separate,
>> as they do require more knowledge about setup, and about git repositories
>> served, than the rest of Gitweb::Request subroutines...
> 
> But they are being used in Gitweb::Request subroutines, so I included
> them in there.

Which Gitweb::Request subroutine uses validate_* subroutines?  Neither
evaluate_uri, nor evaluate_query_params, nor evaluate_path_info uses
them.  And evaluate_and_validate_params can be in Gitweb::Request::Validate.

If there is circular dependency in reasoning given above, please explain
it.

>>> 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:
[...]
>>>       unquote
>>
>> Shouldn't unquote be in Gitweb::Parse, as contrary to the rest of
>> subroutines is is not about gitweb output escaping/quoting, but
>> about processing (parsing) output of git commands?  Perhaps it
>> could even be provate to Gitweb::Parse (i.e. not exported by
>> default)...
> 
> It would result in circular dependency. So, Escape module is best for
> it's place.

Errr... how?  If unquote is used only by subroutines in Gitweb::Parse
(and I think it is), it could be local to Gitweb::Parse, not even
exported (by default).  Please explain.

>>> 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.
>>
>> Why?  Is it to not have too many tiny modules, or is simply there no
>> single good place where to put this subroutine?
> 
> There is no single good place. Anyhow, as they are regarding project
> configuration, I thought it would be better place for it.

O.K.

>>> 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.
>>
>> I find that this module looks a bit like a collection of fairly unrelated
>> subroutines at very different levels of abstractions.
>>
>> I guess that you don't want to split gitweb into too many modules,
>> and splitting gitweb is more of one of steps to final goal of adding
>> write functionality to gitweb, rather than the goal in and of itself.
>> Nevertheless it would be good to be able to immediately know from the
>> description of module what kind of subroutines should be there.

Any comment on this, if you don't mind?  Even "I don't want to think more
about better split" would be all right for me.

>>> This module depends on Git.pm, Config.pm, Request.pm,
>>> Escape.pm and RepoConfig.pm. Some subroutines which
>>> output HTML but are not included in this module due
>>> to unmet dependencies.
>>
>> Which subroutines and what unmet dependencies?
> 
> action specific HTML divs. They include format_* and parse_* subs.

Thanks for this info.  It should be, IMHO, in the comit message.

But... Shouldn't all format_* subs be in Gitweb::Format anyway?
Shouldn't all parse_* subs be in Gitweb::Parse anyway?  Which of format_*
and parse_* do you feel that belong here?

>>> 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
[...]
>>>       is_patch_split
>>
>> O.K.
>>
>>>       run_highlighter
>>
>> _Perhaps_ with exception of this subroutine.
> 
> Well. It was in the utility category in gitweb.perl script. So, I
> added it in here without giving much thought.

Well, it is good explanation.  All right, then.

>>> 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.
>>
>> Why it depends on Gitweb::View, and not the reverse?  I understand why
>> it depends on Gitweb::Config and Gitweb::Escape, and I guess it needs
>> $cgi from Gitweb::Request (or is it something more?).
> 
> $hash, $hash_parent also.

Why it depends on Gitweb::View?  Is it because of ubiquitous and
troublesome :-) die_error?

>>> 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.
>>
>> Why it depends on Gitweb::View?  It is a strange dependency.
>> I understand depending on Gitweb::Git to some extent, I'm not sure
>> if we shouldn't simply move unescape to it instead of requiring
>> Gitweb::Escape (unless there is more: to_utf8 perhaps?), and
>> I understand that Gitweb::Util has some required subroutines.
> 
> for die_error.

Ah, I se that any run of git command is protected with 
'or die_error(...)'.  Hmmm, not very good.

Well, in the future we could think about decoupling Gitweb::Parse from
other modules, but for now it should be enough to mention in commit
message that Gitweb::Parse depends on Gitweb::View because of die_error.
Good for now.  O.K.

Perhaps die_error should be in Gitweb::Carp?  But it neds git_header_html
etc. anyway.  Ugh.

-- 
Jakub Narebski
Poland

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

* Re: [PATCH 00/11 GSoC] gitweb: Split gitweb into modules
  2010-06-22 20:29     ` Jakub Narebski
@ 2010-06-22 21:04       ` Pavan Kumar Sunkara
  2010-06-23  7:59         ` Jakub Narebski
  0 siblings, 1 reply; 21+ messages in thread
From: Pavan Kumar Sunkara @ 2010-06-22 21:04 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: git, Petr Baudis, Christian Couder

On Wed, Jun 23, 2010 at 1:59 AM, Jakub Narebski <jnareb@gmail.com> wrote:
> On Tue, 22 June 2010, Pavan Kumar Sunkara wrote:
>>
>> Yes, due to either unment dependency or circular dependency problem.
>> But no need to worry as they are very small.
>
> Could you _*LIST*_ those subroutines that you feel should belong in
> Gitweb::Config but could not be put in it, and write _why_ they could not
> be put (on what subroutines / variables do they depend that it makes
> impossible to move them)?

feature_* subs. They can't be put in it because they use
git_get_project_config from Gitweb::RepoConfig module

>>>
>>> What unment dependencies?  Can it be worked around by changing their
>>> signatures to use additional parameters, e.g.:
>>>
>>>  sub validate_action {
>>>        my $input = shift || return undef;
>>>        my $actions = shift || return undef;
>>>
>>>        return undef unless exists $actions->{$input};
>>>        return $input;
>>>  }
>>
>> Yes. I think we can but don't you think that we need to import
>> %actions everytime we use validate_action.
>> So, I figured it would be better If i leave it untouched for now.
>
> Well, changing subroutines could be thought as being ot of scope of
> current series, which is about splitting gitweb and moving subroutines to
> appripriate modules... unless it would be not possible because of
> circular dependencies problem (but I guess it is not the case,
> ultimately).
>
>>
>>> But I guess it would be very hard to do the same with validate_project.
>>> Also such change might be out of scope for _this_ series.
>
> Perhaps it would be better then to move _all_ validate_* subroutines to
> separate Gitweb::Request::Validate module... unless they are used by some
> subroutine from Gitweb::Request.

or even better if we leave them untouched for now. (in gitweb.perl script)
and move them later along with the evaluate_validate_params function

>>>>
>>>> Subroutines moved:
>>>>       evaluate_uri
>>>>       evaluate_query_params
>>>>       validate_pathname
>>>>       validate_refname
>>>
>>> I'm not completly sure if validate_* subroutines should be not separate,
>>> as they do require more knowledge about setup, and about git repositories
>>> served, than the rest of Gitweb::Request subroutines...
>>
>> But they are being used in Gitweb::Request subroutines, so I included
>> them in there.
>
> Which Gitweb::Request subroutine uses validate_* subroutines?  Neither
> evaluate_uri, nor evaluate_query_params, nor evaluate_path_info uses
> them.  And evaluate_and_validate_params can be in Gitweb::Request::Validate.
>
> If there is circular dependency in reasoning given above, please explain
> it.
>
>>>> 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:
> [...]
>>>>       unquote
>>>
>>> Shouldn't unquote be in Gitweb::Parse, as contrary to the rest of
>>> subroutines is is not about gitweb output escaping/quoting, but
>>> about processing (parsing) output of git commands?  Perhaps it
>>> could even be provate to Gitweb::Parse (i.e. not exported by
>>> default)...
>>
>> It would result in circular dependency. So, Escape module is best for
>> it's place.
>
> Errr... how?  If unquote is used only by subroutines in Gitweb::Parse
> (and I think it is), it could be local to Gitweb::Parse, not even
> exported (by default).  Please explain.

Oh! I didn't know that.
I will change it.

>>>> 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.
>>>
>>> Why?  Is it to not have too many tiny modules, or is simply there no
>>> single good place where to put this subroutine?
>>
>> There is no single good place. Anyhow, as they are regarding project
>> configuration, I thought it would be better place for it.
>
> O.K.
>
>>>> 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.
>>>
>>> I find that this module looks a bit like a collection of fairly unrelated
>>> subroutines at very different levels of abstractions.
>>>
>>> I guess that you don't want to split gitweb into too many modules,
>>> and splitting gitweb is more of one of steps to final goal of adding
>>> write functionality to gitweb, rather than the goal in and of itself.
>>> Nevertheless it would be good to be able to immediately know from the
>>> description of module what kind of subroutines should be there.
>
> Any comment on this, if you don't mind?  Even "I don't want to think more
> about better split" would be all right for me.

I don't want to think more about better split :)

>>>> This module depends on Git.pm, Config.pm, Request.pm,
>>>> Escape.pm and RepoConfig.pm. Some subroutines which
>>>> output HTML but are not included in this module due
>>>> to unmet dependencies.
>>>
>>> Which subroutines and what unmet dependencies?
>>
>> action specific HTML divs. They include format_* and parse_* subs.
>
> Thanks for this info.  It should be, IMHO, in the comit message.
>
> But... Shouldn't all format_* subs be in Gitweb::Format anyway?
> Shouldn't all parse_* subs be in Gitweb::Parse anyway?  Which of format_*
> and parse_* do you feel that belong here?

Yes. I think u misunderstood me.
Gitweb::parse and Gitweb::Formst depend on Gitweb::View.
So, action specific HTML divs can't be placed in Gitweb::View because
they depend on Gitweb::Format and Gitweb::parse.

I think it's better if they are in GitwebAction::* modules

>>>> 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
> [...]
>>>>       is_patch_split
>>>
>>> O.K.
>>>
>>>>       run_highlighter
>>>
>>> _Perhaps_ with exception of this subroutine.
>>
>> Well. It was in the utility category in gitweb.perl script. So, I
>> added it in here without giving much thought.
>
> Well, it is good explanation.  All right, then.
>
>>>> 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.
>>>
>>> Why it depends on Gitweb::View, and not the reverse?  I understand why
>>> it depends on Gitweb::Config and Gitweb::Escape, and I guess it needs
>>> $cgi from Gitweb::Request (or is it something more?).
>>
>> $hash, $hash_parent also.
>
> Why it depends on Gitweb::View?  Is it because of ubiquitous and
> troublesome :-) die_error?
>
>>>> 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.
>>>
>>> Why it depends on Gitweb::View?  It is a strange dependency.
>>> I understand depending on Gitweb::Git to some extent, I'm not sure
>>> if we shouldn't simply move unescape to it instead of requiring
>>> Gitweb::Escape (unless there is more: to_utf8 perhaps?), and
>>> I understand that Gitweb::Util has some required subroutines.
>>
>> for die_error.
>
> Ah, I se that any run of git command is protected with
> 'or die_error(...)'.  Hmmm, not very good.
>
> Well, in the future we could think about decoupling Gitweb::Parse from
> other modules, but for now it should be enough to mention in commit
> message that Gitweb::Parse depends on Gitweb::View because of die_error.
> Good for now.  O.K.
>
> Perhaps die_error should be in Gitweb::Carp?  But it neds git_header_html
> etc. anyway.  Ugh.
>
> --
> Jakub Narebski
> Poland
>

Thanks,
Pavan.

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

* Re: [PATCH 00/11 GSoC] gitweb: Split gitweb into modules
  2010-06-22 21:04       ` Pavan Kumar Sunkara
@ 2010-06-23  7:59         ` Jakub Narebski
  0 siblings, 0 replies; 21+ messages in thread
From: Jakub Narebski @ 2010-06-23  7:59 UTC (permalink / raw)
  To: Pavan Kumar Sunkara; +Cc: git, Petr Baudis, Christian Couder

On Tue, 22 June 2010, Pavan Kumar Sunkara wrote:
> On Wed, Jun 23, 2010 at 1:59 AM, Jakub Narebski <jnareb@gmail.com> wrote:
>> On Tue, 22 June 2010, Pavan Kumar Sunkara wrote:
>>>
>>> Yes, due to either unment dependency or circular dependency problem.
>>> But no need to worry as they are very small.
>>
>> Could you _*LIST*_ those subroutines that you feel should belong in
>> Gitweb::Config but could not be put in it, and write _why_ they could not
>> be put (on what subroutines / variables do they depend that it makes
>> impossible to move them)?
> 
> feature_* subs. They can't be put in it because they use
> git_get_project_config from Gitweb::RepoConfig module

O.K., but that information should IMHO be in the commit message.
But shouldn't feature_* subs (except perhaps utility feature_bool etc.)
be in Gitweb::RepoConfig rather than Gitweb::Config anyway?

[...]
>> Perhaps it would be better then to move _all_ validate_* subroutines to
>> separate Gitweb::Request::Validate module... unless they are used by some
>> subroutine from Gitweb::Request.
> 
> or even better if we leave them untouched for now. (in gitweb.perl script)
> and move them later along with the evaluate_validate_params function

That's good enough for me.

>>>>> 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:
>> [...]
>>>>>       unquote
>>>>
>>>> Shouldn't unquote be in Gitweb::Parse, as contrary to the rest of
>>>> subroutines is is not about gitweb output escaping/quoting, but
>>>> about processing (parsing) output of git commands?  Perhaps it
>>>> could even be provate to Gitweb::Parse (i.e. not exported by
>>>> default)...
>>>
>>> It would result in circular dependency. So, Escape module is best for
>>> it's place.
>>
>> Errr... how?  If unquote is used only by subroutines in Gitweb::Parse
>> (and I think it is), it could be local to Gitweb::Parse, not even
>> exported (by default).  Please explain.
> 
> Oh! I didn't know that.
> I will change it.

O.K.
 

>>>>> 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.
>>>>
>>>> I find that this module looks a bit like a collection of fairly unrelated
>>>> subroutines at very different levels of abstractions.
>>>>
>>>> I guess that you don't want to split gitweb into too many modules,
>>>> and splitting gitweb is more of one of steps to final goal of adding
>>>> write functionality to gitweb, rather than the goal in and of itself.
>>>> Nevertheless it would be good to be able to immediately know from the
>>>> description of module what kind of subroutines should be there.
>>
>> Any comment on this, if you don't mind?  Even "I don't want to think more
>> about better split" would be all right for me.
> 
> I don't want to think more about better split :)

NOTE that the goal of splitting gitweb was to make it possible to put
write functionalities for gitweb in separate module(s).  Would it be
possible with this proposed split?  If it is, then further enhancements
and refactoring to how gitweb is split into modules could be done 
"in tree", after this GSoC 2010 project is completed.
 
BTW. what write feature would be easiest to write?  Authentication
(only localhost / 127.0.0.1)?  Creating lightweight tags?  Scanning
for repositories and saving them in project index file?

>>>>> This module depends on Git.pm, Config.pm, Request.pm,
>>>>> Escape.pm and RepoConfig.pm. Some subroutines which
>>>>> output HTML but are not included in this module due
>>>>> to unmet dependencies.
>>>>
>>>> Which subroutines and what unmet dependencies?
>>>
>>> action specific HTML divs. They include format_* and parse_* subs.
>>
>> Thanks for this info.  It should be, IMHO, in the comit message.
>>
>> But... Shouldn't all format_* subs be in Gitweb::Format anyway?
>> Shouldn't all parse_* subs be in Gitweb::Parse anyway?  Which of format_*
>> and parse_* do you feel that belong here?
> 
> Yes. I think you misunderstood me.

Ah, format_* and parse_* were "unment dependencies", not "not included
subroutines".

So what were those "not includes subroutines"?

>
> Gitweb::Parse and Gitweb::Formst depend on Gitweb::View.
> So, action specific HTML divs can't be placed in Gitweb::View because
> they depend on Gitweb::Format and Gitweb::Parse.
> 
> I think it's better if they are in GitwebAction::* modules

All right.


P.S. Unentangling interdependencies between gitweb subroutines can be
left for later, but it looks like it would have to be necessary later.

-- 
Jakub Narebski
Poland

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

end of thread, other threads:[~2010-06-23  7:59 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-06-21 22:00 [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Pavan Kumar Sunkara
2010-06-21 22:00 ` [PATCH 01/11 GSoC] gitweb: fix esc_url Pavan Kumar Sunkara
2010-06-21 22:00 ` [PATCH 02/11 GSoC] gitweb: Prepare for splitting gitweb Pavan Kumar Sunkara
2010-06-21 22:00 ` [PATCH 03/11 GSoC] gitweb: Create Gitweb::Git module Pavan Kumar Sunkara
2010-06-21 22:00 ` [PATCH 04/11 GSoC] gitweb: Create Gitweb::Config module Pavan Kumar Sunkara
2010-06-21 22:00 ` [PATCH 05/11 GSoC] gitweb: Create Gitweb::Request module Pavan Kumar Sunkara
2010-06-21 22:00 ` [PATCH 06/11 GSoC] gitweb: Create Gitweb::Escape module Pavan Kumar Sunkara
2010-06-21 22:00 ` [PATCH 07/11 GSoC] gitweb: Create Gitweb::RepoConfig module Pavan Kumar Sunkara
2010-06-21 22:00 ` [PATCH 08/11 GSoC] gitweb: Create Gitweb::View module Pavan Kumar Sunkara
2010-06-21 22:00 ` [PATCH 09/11 GSoC] gitweb: Create Gitweb::Util module Pavan Kumar Sunkara
2010-06-21 22:00 ` [PATCH 10/11 GSoC] gitweb: Create Gitweb::Format module Pavan Kumar Sunkara
2010-06-21 22:00 ` [PATCH 11/11 GSoC] gitweb: Create Gitweb::Parse module Pavan Kumar Sunkara
2010-06-21 22:30 ` [PATCH GSoC] gitweb: Add support for enabling 'write' feature Pavan Kumar Sunkara
2010-06-22 11:12   ` Jakub Narebski
2010-06-22 18:35     ` Pavan Kumar Sunkara
2010-06-22 19:23       ` Jakub Narebski
2010-06-22 11:11 ` [PATCH 00/11 GSoC] gitweb: Split gitweb into modules Jakub Narebski
2010-06-22 18:34   ` Pavan Kumar Sunkara
2010-06-22 20:29     ` Jakub Narebski
2010-06-22 21:04       ` Pavan Kumar Sunkara
2010-06-23  7:59         ` Jakub Narebski

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).