From e6e592db4c0099a6412aed6e868769535900f112 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 14 Feb 2010 22:46:28 +0100 Subject: gitweb: Die if there are parsing errors in config file Otherwise the errors can propagate, and show in damnest places, and you would spend your time chasing ghosts instead of debugging real problem (yes, it is from personal experience). This follows (parts of) advice in `perldoc -f do` documentation. This required restructoring code a bit, so we die only if we are reading (executing) config file. As a side effect $GITWEB_CONFIG_SYSTEM is always available, even when we use $GITWEB_CONFIG. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1f6978ac1f..20106a4f42 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -550,11 +550,14 @@ sub filter_snapshot_fmts { } 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; -} else { - our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++"; - do $GITWEB_CONFIG_SYSTEM if -e $GITWEB_CONFIG_SYSTEM; + die $@ if $@; +} elsif (-e $GITWEB_CONFIG_SYSTEM) { + do $GITWEB_CONFIG_SYSTEM; + die $@ if $@; } # Get loadavg of system, to compare against $maxload. -- cgit v1.3 From 453541fcfcbc54aa3b0035667e5d5885d407d0a5 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 7 Feb 2010 21:51:18 +0100 Subject: gitweb: esc_html (short) error message in die_error The error message (second argument to die_error) is meant to be short, one-line text description of given error. A few callers call die_error with error message containing unescaped user supplied data ($hash, $file_name). Instead of forcing callers to escape data, simply call esc_html on the parameter. Note that optional third parameter, which contains detailed error description, is meant to be HTML formatted, and therefore should be not escaped. While at it update esc_html synopsis/usage, and bring default error description to read 'Internal Server Error' (titlecased). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1f6978ac1f..2ccbb6aa34 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3372,7 +3372,7 @@ sub git_footer_html { ""; } -# die_error(, ) +# die_error(, [, ]) # 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 @@ -3387,7 +3387,7 @@ sub git_footer_html { # or down for maintenance). Generally, this is a temporary state. sub die_error { my $status = shift || 500; - my $error = shift || "Internal server error"; + my $error = esc_html(shift || "Internal Server Error"); my $extra = shift; my %http_responses = ( -- cgit v1.3 From 1df48766137f2040e2e992d7d278d5aca26406cf Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 7 Feb 2010 21:52:25 +0100 Subject: gitweb: Protect escaping functions against calling on undef This is a bit of future-proofing esc_html and friends: when called with undefined value they would now would return undef... which would probably mean that error would still occur, but closer to the source of problem. This means that we can safely use esc_html(shift) || "Internal Server Error" in die_error() instead of esc_html(shift || "Internal Server Error") Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 2ccbb6aa34..3c879b88fe 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1143,6 +1143,7 @@ sub validate_refname { # 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; @@ -1155,6 +1156,7 @@ sub to_utf8 { # 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; @@ -1163,6 +1165,7 @@ sub esc_param { # 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; @@ -1174,6 +1177,8 @@ sub esc_html { my $str = shift; my %opts = @_; + return undef unless defined $str; + $str = to_utf8($str); $str = $cgi->escapeHTML($str); if ($opts{'-nbsp'}) { @@ -1188,6 +1193,8 @@ sub esc_path { my $str = shift; my %opts = @_; + return undef unless defined $str; + $str = to_utf8($str); $str = $cgi->escapeHTML($str); if ($opts{'-nbsp'}) { @@ -3387,7 +3394,7 @@ sub git_footer_html { # 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 $error = esc_html(shift) || "Internal Server Error"; my $extra = shift; my %http_responses = ( -- cgit v1.3 From 964ad928d65b8bdf15bee4a662629824a0b3a0e7 Mon Sep 17 00:00:00 2001 From: Sylvain Rabot Date: Tue, 2 Mar 2010 01:04:57 +0100 Subject: gitweb multiple project roots documentation This commit adds in the gitweb/README file a description of how to use gitweb with several project roots using apache virtualhost rewrite rules. Signed-off-by: Sylvain Rabot Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/README | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 3 deletions(-) (limited to 'gitweb') diff --git a/gitweb/README b/gitweb/README index 6c2c8e1259..ad6a04c464 100644 --- a/gitweb/README +++ b/gitweb/README @@ -312,12 +312,16 @@ If you want to have one URL for both gitweb and your http:// repositories, you can configure apache like this: - ServerName git.example.org - DocumentRoot /pub/git - SetEnv GITWEB_CONFIG /etc/gitweb.conf + ServerName git.example.org + DocumentRoot /pub/git + SetEnv GITWEB_CONFIG /etc/gitweb.conf + + # turning on mod rewrite RewriteEngine on + # make the front page an internal rewrite to the gitweb script RewriteRule ^/$ /cgi-bin/gitweb.cgi + # make access for "dumb clients" work RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ /cgi-bin/gitweb.cgi%{REQUEST_URI} [L,PT] @@ -343,6 +347,63 @@ something like the following in your gitweb.conf (or gitweb_config.perl) file: $home_link = "/"; +Webserver configuration with multiple projects' root +---------------------------------------------------- + +If you want to use gitweb with several project roots you can edit your apache +virtual host and gitweb.conf configuration files like this : + +virtual host configuration : + + + ServerName git.example.org + DocumentRoot /pub/git + SetEnv GITWEB_CONFIG /etc/gitweb.conf + + # turning on mod rewrite + RewriteEngine on + + # make the front page an internal rewrite to the gitweb script + RewriteRule ^/$ /cgi-bin/gitweb.cgi [QSA,L,PT] + + # look for a public_git folder in unix users' home + # http://git.example.org/~/ + RewriteRule ^/\~([^\/]+)(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT] + + # http://git.example.org/+/ + #RewriteRule ^/\+([^\/]+)(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT] + + # http://git.example.org/user// + #RewriteRule ^/user/([^\/]+)/(gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT] + + # defined list of project roots + RewriteRule ^/scm(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/pub/scm/,L,PT] + RewriteRule ^/var(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/var/git/,L,PT] + + # make access for "dumb clients" work + RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ /cgi-bin/gitweb.cgi%{REQUEST_URI} [L,PT] + + +gitweb.conf configuration : + +$projectroot = $ENV{'GITWEB_PROJECTROOT'} || "/pub/git"; + +These configurations enable two things. First, each unix user () of the +server will be able to browse through gitweb git repositories found in +~/public_git/ with the following url : http://git.example.org/~/ + +If you do not want this feature on your server just remove the second rewrite rule. + +If you already use mod_userdir in your virtual host or you don't want to use +the '~' as first character just comment or remove the second rewrite rule and +uncomment one of the following according to what you want. + +Second, repositories found in /pub/scm/ and /var/git/ will be accesible +through http://git.example.org/scm/ and http://git.example.org/var/. +You can add as many project roots as you want by adding rewrite rules like the +third and the fourth. + + PATH_INFO usage ----------------------- If you enable PATH_INFO usage in gitweb by putting -- cgit v1.3 From 9be3614eff36271d5f1cd460a568a219902cb044 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 1 Mar 2010 22:51:34 +0100 Subject: gitweb: Fix project-specific feature override behavior This commit fixes a bug in processing project-specific override in a situation when there is no project, e.g. for the projects list page. When 'snapshot' feature had project specific config override enabled by putting $feature{'snapshot'}{'override'} = 1; (or equivalent) in $GITWEB_CONFIG, and when viewing toplevel gitweb page, which means the projects list page (to be more exact this happens for any project-less action), gitweb would put the following Perl warnings in error log: gitweb.cgi: Use of uninitialized value $git_dir in concatenation (.) or string at gitweb.cgi line 2065. fatal: error processing config file(s) gitweb.cgi: Use of uninitialized value $git_dir in concatenation (.) or string at gitweb.cgi line 2221. gitweb.cgi: Use of uninitialized value $git_dir in concatenation (.) or string at gitweb.cgi line 2218. The problem is in the following fragment of code: # path to the current git repository our $git_dir; $git_dir = "$projectroot/$project" if $project; # list of supported snapshot formats our @snapshot_fmts = gitweb_get_feature('snapshot'); @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts); For the toplevel gitweb page, which is the list of projects, $project is not defined, therefore neither is $git_dir. gitweb_get_feature() subroutine calls git_get_project_config() if project specific override is turned on... but we don't have project here. Those errors mentioned above occur in the following fragment of code in git_get_project_config(): # get config if (!defined $config_file || $config_file ne "$git_dir/config") { %config = git_parse_project_config('gitweb'); $config_file = "$git_dir/config"; } git_parse_project_config() calls git_cmd() which has '--git-dir='.$git_dir There are (at least) three possible solutions: 1. Harden gitweb_get_feature() so that it doesn't call git_get_project_config() if $project (and therefore $git_dir) is not defined; there is no project for project specific config. 2. Harden git_get_project_config() like you did in your fix, returning early if $git_dir is not defined. 3. Harden git_cmd() so that it doesn't add "--git-dir=$git_dir" if $git_dir is not defined, and change git_get_project_config() so that it doesn't even try to access $git_dir if it is not defined. This commit implements both 1.) and 2.), i.e. gitweb_get_feature() doesn't call project-specific override if $git_dir is not defined (if there is no project), and git_get_project_config() returns early if $git_dir is not defined. Add a test for this bug to t/t9500-gitweb-standalone-no-errors.sh test. Reported-by: Eli Barzilay Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 9 ++++++++- t/t9500-gitweb-standalone-no-errors.sh | 18 +++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1f6978ac1f..0b1e357ce9 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -454,7 +454,11 @@ sub gitweb_get_feature { $feature{$name}{'sub'}, $feature{$name}{'override'}, @{$feature{$name}{'default'}}); - if (!$override) { return @defaults; } + # project specific override is possible only if we have project + our $git_dir; # global variable, declared later + if (!$override || !defined $git_dir) { + return @defaults; + } if (!defined $sub) { warn "feature $name is not overridable"; return @defaults; @@ -2202,6 +2206,9 @@ sub config_to_multi { sub git_get_project_config { my ($key, $type) = @_; + # do we have project + return unless (defined $project && defined $git_dir); + # key sanity check return unless ($key); $key =~ s/^gitweb\.//; diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 2fc7fdb124..63b6b060e6 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -591,13 +591,21 @@ test_debug 'cat gitweb.log' # ---------------------------------------------------------------------- # gitweb config and repo config -cat >>gitweb_config.perl <>gitweb_config.perl <<\EOF + +# turn on override for each overridable feature +foreach my $key (keys %feature) { + if ($feature{$key}{'sub'}) { + $feature{$key}{'override'} = 1; + } +} EOF +test_expect_success \ + 'config override: projects list (implicit)' \ + 'gitweb_run' +test_debug 'cat gitweb.log' + test_expect_success \ 'config override: tree view, features not overridden in repo config' \ 'gitweb_run "p=.git;a=tree"' -- cgit v1.3 From 7a49c254cdaec6b15a6e2818e29fdb34fc6f7717 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 27 Mar 2010 20:26:59 +0100 Subject: gitweb: git_get_project_config requires only $git_dir, not also $project Fix overeager early return in git_get_project_config, introduced in 9be3614 (gitweb: Fix project-specific feature override behavior, 2010-03-01). When git_get_project_config is called from projects list page via git_get_project_owner($path) etc., it is called with $git_dir defined (in git_get_project_owner($path) etc.), but $project variable is not defined. git_get_project_config doesn't use $project variable anyway. Reported-by: Tobias Heinlein Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 3d80deba01..9d4c58238e 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2209,8 +2209,7 @@ sub config_to_multi { sub git_get_project_config { my ($key, $type) = @_; - # do we have project - return unless (defined $project && defined $git_dir); + return unless defined $git_dir; # key sanity check return unless ($key); -- cgit v1.3 From 0e6ce21361c5d8e35cd15327539eec1f627aa0e3 Mon Sep 17 00:00:00 2001 From: Mark Rada Date: Fri, 2 Apr 2010 20:35:05 -0400 Subject: Gitweb: add support for minifying gitweb.css The build system added support minifying gitweb.js through a JavaScript minifier, but most minifiers come with support for minifying CSS files as well, so we should use it if we can. This patch will add the same facilities to gitweb.css that gitweb.js has for minification. That does not mean that they will use the same minifier though, as it is not safe to assume that all JavaScript minifiers will also minify CSS files. This patch also adds the GITWEB_PROGRAMS variable to the Makefile to keep a list of potential gitweb dependencies separate from OTHER_PROGRAMS when we need to know just the gitweb dependencies. Though the bandwidth savings will not be as dramatic as with the JavaScript minifier, every byte saved is important. Signed-off-by: Mark Rada Signed-off-by: Junio C Hamano --- Makefile | 21 +++++++++++++++------ gitweb/INSTALL | 5 +++++ gitweb/Makefile | 28 +++++++++++++++++++++------- gitweb/README | 3 ++- 4 files changed, 43 insertions(+), 14 deletions(-) (limited to 'gitweb') diff --git a/Makefile b/Makefile index 62a029ccc4..9bebcf1c44 100644 --- a/Makefile +++ b/Makefile @@ -203,6 +203,9 @@ all:: # Define JSMIN to point to JavaScript minifier that functions as # a filter to have gitweb.js minified. # +# Define CSSMIN to point to a CSS minifier in order to generate a minified +# version of gitweb.css +# # Define DEFAULT_PAGER to a sensible pager command (defaults to "less") if # you want to use something different. The value will be interpreted by the # shell at runtime when it is used. @@ -279,8 +282,9 @@ lib = lib # DESTDIR= pathsep = : -# JavaScript minifier invocation that can function as filter +# JavaScript/CSS minifier invocation that can function as filter JSMIN = +CSSMIN = export prefix bindir sharedir sysconfdir @@ -1560,18 +1564,23 @@ gitweb: $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) all ifdef JSMIN -OTHER_PROGRAMS += gitweb/gitweb.cgi gitweb/gitweb.min.js -gitweb/gitweb.cgi: gitweb/gitweb.perl gitweb/gitweb.min.js -else -OTHER_PROGRAMS += gitweb/gitweb.cgi -gitweb/gitweb.cgi: gitweb/gitweb.perl +GITWEB_PROGRAMS += gitweb/gitweb.min.js endif +ifdef CSSMIN +GITWEB_PROGRAMS += gitweb/gitweb.min.css +endif +OTHER_PROGRAMS += gitweb/gitweb.cgi $(GITWEB_PROGRAMS) +gitweb/gitweb.cgi: gitweb/gitweb.perl $(GITWEB_PROGRAMS) $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@) ifdef JSMIN gitweb/gitweb.min.js: gitweb/gitweb.js $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@) endif # JSMIN +ifdef CSSMIN +gitweb/gitweb.min.css: gitweb/gitweb.css + $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@) +endif # CSSMIN git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css gitweb/gitweb.js diff --git a/gitweb/INSTALL b/gitweb/INSTALL index b76a0cffff..b75a90be7d 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -66,6 +66,11 @@ file for gitweb (in gitweb/README). build configuration variables. By default gitweb tries to find them in the same directory as gitweb.cgi script. +- You can optionally generate a minified version of gitweb.css by defining + the CSSMIN build configuration variable. By default the non-minified + version of gitweb.css will be used. NOTE: if you enable this option, + substitute gitweb.min.css for all uses of gitweb.css in the help files. + Build example ~~~~~~~~~~~~~ diff --git a/gitweb/Makefile b/gitweb/Makefile index c9eb1ee667..fffe700760 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -6,13 +6,17 @@ all:: # Define JSMIN to point to JavaScript minifier that functions as # a filter to have gitweb.js minified. # +# Define CSSMIN to point to a CSS minifier in order to generate a minified +# version of gitweb.css +# prefix ?= $(HOME) bindir ?= $(prefix)/bin RM ?= rm -f -# JavaScript minifier invocation that can function as filter +# JavaScript/CSS minifier invocation that can function as filter JSMIN ?= +CSSMIN ?= # default configuration for gitweb GITWEB_CONFIG = gitweb_config.perl @@ -26,7 +30,11 @@ GITWEB_STRICT_EXPORT = GITWEB_BASE_URL = GITWEB_LIST = GITWEB_HOMETEXT = indextext.html +ifdef CSSMIN +GITWEB_CSS = gitweb.min.css +else GITWEB_CSS = gitweb.css +endif GITWEB_LOGO = git-logo.png GITWEB_FAVICON = git-favicon.png ifdef JSMIN @@ -84,13 +92,14 @@ endif all:: gitweb.cgi +FILES = gitweb.cgi ifdef JSMIN -FILES=gitweb.cgi gitweb.min.js -gitweb.cgi: gitweb.perl gitweb.min.js -else # !JSMIN -FILES=gitweb.cgi -gitweb.cgi: gitweb.perl -endif # JSMIN +FILES += gitweb.min.js +endif +ifdef CSSMIN +FILES += gitweb.min.css +endif +gitweb.cgi: gitweb.perl $(GITWEB_JS) $(GITWEB_CSS) gitweb.cgi: $(QUIET_GEN)$(RM) $@ $@+ && \ @@ -123,6 +132,11 @@ gitweb.min.js: gitweb.js $(QUIET_GEN)$(JSMIN) <$< >$@ endif # JSMIN +ifdef CSSMIN +gitweb.min.css: gitweb.css + $(QUIET_GEN)$(CSSMIN) <$ >$@ +endif + clean: $(RM) $(FILES) diff --git a/gitweb/README b/gitweb/README index ad6a04c464..71742b335d 100644 --- a/gitweb/README +++ b/gitweb/README @@ -80,7 +80,8 @@ You can specify the following configuration variables when building GIT: Points to the location where you put gitweb.css on your web server (or to be more generic, the URI of gitweb stylesheet). Relative to the base URI of gitweb. Note that you can setup multiple stylesheets from - the gitweb config file. [Default: gitweb.css] + the gitweb config file. [Default: gitweb.css (or gitweb.min.css if the + CSSMIN variable is defined / CSS minifier is used)] * GITWEB_LOGO Points to the location where you put git-logo.png on your web server (or to be more generic URI of logo, 72x27 size, displayed in top right -- cgit v1.3 From bb4bbf75829d1a6c021e34943ff4e942f064bb55 Mon Sep 17 00:00:00 2001 From: Mark Rada Date: Fri, 2 Apr 2010 20:35:54 -0400 Subject: Gitweb: add autoconfigure support for minifiers This will allow users to set a JavaScript/CSS minifier when/if they run the autoconfigure script while building git. Signed-off-by: Mark Rada Signed-off-by: Junio C Hamano --- Makefile | 4 ---- configure.ac | 20 ++++++++++++++++++++ gitweb/Makefile | 14 ++------------ 3 files changed, 22 insertions(+), 16 deletions(-) (limited to 'gitweb') diff --git a/Makefile b/Makefile index 9bebcf1c44..2c36def3ec 100644 --- a/Makefile +++ b/Makefile @@ -282,10 +282,6 @@ lib = lib # DESTDIR= pathsep = : -# JavaScript/CSS minifier invocation that can function as filter -JSMIN = -CSSMIN = - export prefix bindir sharedir sysconfdir CC = gcc diff --git a/configure.ac b/configure.ac index 914ae5759f..f4d7372ef8 100644 --- a/configure.ac +++ b/configure.ac @@ -179,6 +179,26 @@ fi], AC_MSG_NOTICE([Will try -pthread then -lpthread to enable POSIX Threads.]) ]) +# Define option to enable JavaScript minification +AC_ARG_ENABLE([jsmin], +[AS_HELP_STRING([--enable-jsmin=PATH], + [PATH is the name of a JavaScript minifier or the absolute path to one.])], +[ + JSMIN=$enableval; + AC_MSG_NOTICE([Setting JSMIN to '$JSMIN' to enable JavaScript minifying]) + GIT_CONF_APPEND_LINE(JSMIN=$enableval); +]) + +# Define option to enable CSS minification +AC_ARG_ENABLE([cssmin], +[AS_HELP_STRING([--enable-cssmin=PATH], + [PATH is the name of a CSS minifier or the absolute path to one.])], +[ + CSSMIN=$enableval; + AC_MSG_NOTICE([Setting CSSMIN to '$CSSMIN' to enable CSS minifying]) + GIT_CONF_APPEND_LINE(CSSMIN=$enableval); +]) + ## Site configuration (override autodetection) ## --with-PACKAGE[=ARG] and --without-PACKAGE AC_MSG_NOTICE([CHECKS for site configuration]) diff --git a/gitweb/Makefile b/gitweb/Makefile index fffe700760..ffee4bd1e3 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -14,10 +14,6 @@ prefix ?= $(HOME) bindir ?= $(prefix)/bin RM ?= rm -f -# JavaScript/CSS minifier invocation that can function as filter -JSMIN ?= -CSSMIN ?= - # default configuration for gitweb GITWEB_CONFIG = gitweb_config.perl GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf @@ -30,18 +26,10 @@ GITWEB_STRICT_EXPORT = GITWEB_BASE_URL = GITWEB_LIST = GITWEB_HOMETEXT = indextext.html -ifdef CSSMIN -GITWEB_CSS = gitweb.min.css -else GITWEB_CSS = gitweb.css -endif GITWEB_LOGO = git-logo.png GITWEB_FAVICON = git-favicon.png -ifdef JSMIN -GITWEB_JS = gitweb.min.js -else GITWEB_JS = gitweb.js -endif GITWEB_SITE_HEADER = GITWEB_SITE_FOOTER = @@ -95,9 +83,11 @@ all:: gitweb.cgi FILES = gitweb.cgi ifdef JSMIN FILES += gitweb.min.js +GITWEB_JS = gitweb.min.js endif ifdef CSSMIN FILES += gitweb.min.css +GITWEB_CSS = gitweb.min.css endif gitweb.cgi: gitweb.perl $(GITWEB_JS) $(GITWEB_CSS) -- cgit v1.3 From a8ab675f21b6e8b003b7a8ba0a72933cf34afb68 Mon Sep 17 00:00:00 2001 From: Mark Rada Date: Fri, 2 Apr 2010 20:36:44 -0400 Subject: gitweb: add documentation to INSTALL regarding gitweb.js This patch updates gitweb/INSTALL to mention gitweb.js, including JavaScript minification support. Signed-off-by: Mark Rada Signed-off-by: Junio C Hamano --- gitweb/INSTALL | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'gitweb') diff --git a/gitweb/INSTALL b/gitweb/INSTALL index b75a90be7d..44ff17d628 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -3,8 +3,9 @@ GIT web Interface (gitweb) Installation First you have to generate gitweb.cgi from gitweb.perl using "make gitweb/gitweb.cgi", then copy appropriate files (gitweb.cgi, -gitweb.css, git-logo.png and git-favicon.png) to their destination. -For example if git was (or is) installed with /usr prefix, you can do +gitweb.js, gitweb.css, git-logo.png and git-favicon.png) to their +destination. For example if git was (or is) installed with /usr prefix, +you can do $ make prefix=/usr gitweb/gitweb.cgi ;# as yourself # cp gitweb/git* /var/www/cgi-bin/ ;# as root @@ -62,14 +63,15 @@ file for gitweb (in gitweb/README). a suggestion). - You can control where gitweb tries to find its main CSS style file, - its favicon and logo with the GITWEB_CSS, GITWEB_FAVICON and GITWEB_LOGO - build configuration variables. By default gitweb tries to find them - in the same directory as gitweb.cgi script. + its JavaScript file, its favicon and logo with the GITWEB_CSS, GITWEB_JS + GITWEB_FAVICON and GITWEB_LOGO build configuration variables. By default + gitweb tries to find them in the same directory as gitweb.cgi script. -- You can optionally generate a minified version of gitweb.css by defining - the CSSMIN build configuration variable. By default the non-minified - version of gitweb.css will be used. NOTE: if you enable this option, - substitute gitweb.min.css for all uses of gitweb.css in the help files. +- You can optionally generate minified versions of gitweb.js and gitweb.css + by defining the JSMIN and CSSMIN build configuration variables. By default + the non-minified versions will be used. NOTE: if you enable this option, + substitute gitweb.min.js and gitweb.min.css for all uses of gitweb.js and + gitweb.css in the help files. Build example ~~~~~~~~~~~~~ @@ -79,13 +81,14 @@ Build example we want to display are under /home/local/scm, you can do make GITWEB_PROJECTROOT="/home/local/scm" \ + GITWEB_JS="/gitweb/gitweb.js" \ GITWEB_CSS="/gitweb/gitweb.css" \ GITWEB_LOGO="/gitweb/git-logo.png" \ GITWEB_FAVICON="/gitweb/git-favicon.png" \ bindir=/usr/local/bin \ gitweb/gitweb.cgi - cp -fv ~/git/gitweb/gitweb.{cgi,css} \ + cp -fv ~/git/gitweb/gitweb.{cgi,js,css} \ ~/git/gitweb/git-{favicon,logo}.png \ /var/www/cgi-bin/gitweb/ -- cgit v1.3 From e3918594f6269b37ad7bd7e419df433b6d586428 Mon Sep 17 00:00:00 2001 From: Mark Rada Date: Fri, 2 Apr 2010 20:37:33 -0400 Subject: gitweb: update INSTALL to use shorter make target Gitweb can be generated by the gitweb/gitweb.cgi target or the gitweb target. Since the gitweb target is shorter, I think it would be better to have new users be instructed to use it. Signed-off-by: Mark Rada Signed-off-by: Junio C Hamano --- gitweb/INSTALL | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) (limited to 'gitweb') diff --git a/gitweb/INSTALL b/gitweb/INSTALL index 44ff17d628..cbdc136470 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -2,12 +2,11 @@ GIT web Interface (gitweb) Installation ======================================= First you have to generate gitweb.cgi from gitweb.perl using -"make gitweb/gitweb.cgi", then copy appropriate files (gitweb.cgi, -gitweb.js, gitweb.css, git-logo.png and git-favicon.png) to their -destination. For example if git was (or is) installed with /usr prefix, -you can do +"make gitweb", then copy appropriate files (gitweb.cgi, gitweb.js, +gitweb.css, git-logo.png and git-favicon.png) to their destination. +For example if git was (or is) installed with /usr prefix, you can do - $ make prefix=/usr gitweb/gitweb.cgi ;# as yourself + $ make prefix=/usr gitweb ;# as yourself # cp gitweb/git* /var/www/cgi-bin/ ;# as root Alternatively you can use autoconf generated ./configure script to @@ -16,7 +15,7 @@ instead $ make configure ;# as yourself $ ./configure --prefix=/usr ;# as yourself - $ make gitweb/gitweb.cgi ;# as yourself + $ make gitweb ;# as yourself # cp gitweb/git* /var/www/cgi-bin/ ;# as root The above example assumes that your web server is configured to run @@ -32,8 +31,7 @@ file for gitweb (in gitweb/README). - There are many configuration variables which affect building of gitweb.cgi; see "default configuration for gitweb" section in main - (top dir) Makefile, and instructions for building gitweb/gitweb.cgi - target. + (top dir) Makefile, and instructions for building gitweb target. One of the most important is where to find the git wrapper binary. Gitweb tries to find the git wrapper at $(bindir)/git, so you have to set $bindir @@ -86,7 +84,7 @@ Build example GITWEB_LOGO="/gitweb/git-logo.png" \ GITWEB_FAVICON="/gitweb/git-favicon.png" \ bindir=/usr/local/bin \ - gitweb/gitweb.cgi + gitweb cp -fv ~/git/gitweb/gitweb.{cgi,js,css} \ ~/git/gitweb/git-{favicon,logo}.png \ -- cgit v1.3 From 8de096b6718048e84ee88e604a94b22937a0758e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 15 Apr 2010 08:57:18 -0400 Subject: gitweb: simplify gitweb.min.* generation and clean-up rules GITWEB_CSS and GITWEB_JS are meant to be "what URI should the installed cgi script use to refer to the stylesheet and JavaScript", never "this is the name of the file we are building". Don't use them to decide what file to build minified versions in. While we are at it, lose FILES that is used only for "clean" target in a misguided way. "make clean" should try to remove all the potential build artifacts regardless of a minor configuration change. Instead of trying to remove only the build product "make clean" would have created if it were run without "clean", explicitly list the three potential build products for removal. Tested-by: Mark Rada Signed-off-by: Junio C Hamano --- gitweb/Makefile | 75 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 37 deletions(-) (limited to 'gitweb') diff --git a/gitweb/Makefile b/gitweb/Makefile index ffee4bd1e3..f2e1d92fbb 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -80,54 +80,55 @@ endif all:: gitweb.cgi -FILES = gitweb.cgi ifdef JSMIN -FILES += gitweb.min.js GITWEB_JS = gitweb.min.js +all:: gitweb.min.js +gitweb.min.js: gitweb.js GITWEB-BUILD-OPTIONS + $(QUIET_GEN)$(JSMIN) <$< >$@ endif + ifdef CSSMIN -FILES += gitweb.min.css GITWEB_CSS = gitweb.min.css +all:: gitweb.min.css +gitweb.min.css: gitweb.css GITWEB-BUILD-OPTIONS + $(QUIET_GEN)$(CSSMIN) <$ >$@ endif -gitweb.cgi: gitweb.perl $(GITWEB_JS) $(GITWEB_CSS) -gitweb.cgi: +GITWEB_REPLACE = \ + -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ + -e 's|++GIT_BINDIR++|$(bindir)|g' \ + -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \ + -e 's|++GITWEB_CONFIG_SYSTEM++|$(GITWEB_CONFIG_SYSTEM)|g' \ + -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \ + -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \ + -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \ + -e 's|"++GITWEB_PROJECT_MAXDEPTH++"|$(GITWEB_PROJECT_MAXDEPTH)|g' \ + -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \ + -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \ + -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \ + -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \ + -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \ + -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \ + -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \ + -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \ + -e 's|++GITWEB_JS++|$(GITWEB_JS)|g' \ + -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \ + -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' + +GITWEB-BUILD-OPTIONS: FORCE + @rm -f $@+ + @echo "x" '$(PERL_PATH_SQ)' $(GITWEB_REPLACE) "$(JSMIN)|$(CSSMIN)" >$@+ + @cmp -s $@+ $@ && rm -f $@+ || mv -f $@+ $@ + +gitweb.cgi: gitweb.perl GITWEB-BUILD-OPTIONS $(QUIET_GEN)$(RM) $@ $@+ && \ sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ - -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ - -e 's|++GIT_BINDIR++|$(bindir)|g' \ - -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \ - -e 's|++GITWEB_CONFIG_SYSTEM++|$(GITWEB_CONFIG_SYSTEM)|g' \ - -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \ - -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \ - -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \ - -e 's|"++GITWEB_PROJECT_MAXDEPTH++"|$(GITWEB_PROJECT_MAXDEPTH)|g' \ - -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \ - -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \ - -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \ - -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \ - -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \ - -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \ - -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \ - -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \ - -e 's|++GITWEB_JS++|$(GITWEB_JS)|g' \ - -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \ - -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \ - $< >$@+ && \ + $(GITWEB_REPLACE) $< >$@+ && \ chmod +x $@+ && \ mv $@+ $@ -ifdef JSMIN -gitweb.min.js: gitweb.js - $(QUIET_GEN)$(JSMIN) <$< >$@ -endif # JSMIN - -ifdef CSSMIN -gitweb.min.css: gitweb.css - $(QUIET_GEN)$(CSSMIN) <$ >$@ -endif - clean: - $(RM) $(FILES) + $(RM) gitweb.cgi gitweb.min.js gitweb.min.css GITWEB-BUILD-OPTIONS + +.PHONY: all clean .FORCE-GIT-VERSION-FILE FORCE -.PHONY: all clean .FORCE-GIT-VERSION-FILE -- cgit v1.3 From 377bee3424ba871278d230ed296f74ccac8ad607 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 24 Apr 2010 15:53:19 +0200 Subject: gitweb: href(..., -path_info => 0|1) If named boolean option -path_info is passed to href() subroutine, it would use its value to decide whether to generate path_info URL form. If this option is not passed, href() queries 'pathinfo' feature to check whether to generate path_info URL (if generating path_info link is possible at all). href(-replay=>1, -path_info=>0) is meant to be used to generate a key for caching gitweb output; alternate solution would be to use freeze() from Storable (core module) on %input_params hash (or its reference), e.g.: $key = freeze \%input_params; or other serialization of %input_params. While at it document extra options/flags to href(). Signed-off-by: Jakub Narebski Acked-by: Petr Baudis Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c356e95f18..6cefb09b45 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -977,6 +977,10 @@ exit; ## ====================================================================== ## 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 @@ -993,7 +997,8 @@ sub href { } my $use_pathinfo = gitweb_check_feature('pathinfo'); - if ($use_pathinfo and defined $params{'project'}) { + 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 -- cgit v1.3 From c42b00c8f2b8c04ff9829e88dcde92d7294cd460 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 24 Apr 2010 15:56:13 +0200 Subject: gitweb: Use nonlocal jump instead of 'exit' in die_error Use 'goto DONE' in place of 'exit' to end request processing in die_error() subroutine. While at it, do not end gitweb with 'exit'. This would make it easier in the future to add support or improve support for persistent environments such as FastCGI and mod_perl. It would also make it easier to make use of die_error() as an error handler (for fatalsToBrowser). Perl 5 allows non-local jumps; the restriction is that you cannot jump into a scope. Signed-off-by: Jakub Narebski Acked-by: Petr Baudis Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 6cefb09b45..ed92dcac08 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -972,7 +972,8 @@ if ($action !~ m/^(?:opml|project_list|project_index)$/ && die_error(400, "Project needed"); } $actions{$action}->(); -exit; +DONE_GITWEB: +1; ## ====================================================================== ## action links @@ -3432,7 +3433,7 @@ EOF print "\n"; git_footer_html(); - exit; + goto DONE_GITWEB; } ## ---------------------------------------------------------------------- -- cgit v1.3 From 7a59745710e964f9498f37a3184bf95b9b4a772b Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 24 Apr 2010 16:00:04 +0200 Subject: gitweb: Add custom error handler using die_error Change the default message for errors (for fatalsToBrowser) to use die_error() subroutine. This way errors (and explicitely calling 'die MESSAGE') would generate 'Internal Server Error' error message. Note that call to set_message is intentionally not put in BEGIN block; we set error handler to use die_error() only after we are sure that we can use it, after all needed variables are set. Due to the fact that error handler set via set_message() subroutine from CGI::Carp (in the fatalsToBrowser case) is called after HTTP headers were already printed (with exception of MOD_PERL), gitweb cannot return 'Status: 500 Internal Server Error'. Thanks to the fact that die_error() no longer uses 'exit', errors would be logged by CGI::Carp, independent on whether default error handler is used, or handle_errors_html which uses die_error is used. Signed-off-by: Jakub Narebski Acked-by: Petr Baudis Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ed92dcac08..e579c14b44 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -11,7 +11,7 @@ use strict; use warnings; use CGI qw(:standard :escapeHTML -nosticky); use CGI::Util qw(unescape); -use CGI::Carp qw(fatalsToBrowser); +use CGI::Carp qw(fatalsToBrowser set_message); use Encode; use Fcntl ':mode'; use File::Find qw(); @@ -952,6 +952,21 @@ if ($git_avatar eq 'gravatar') { $git_avatar = ''; } +# custom error handler: 'die ' is Internal Server Error +sub handle_errors_html { + my $msg = shift; # it is already HTML escaped + + # to avoid infinite loop where error occurs in die_error, + # change handler to default handler, disabling handle_errors_html + set_message("Error occured when inside die_error:\n$msg"); + + # you cannot jump out of die_error when called as error handler; + # the subroutine set via CGI::Carp::set_message is called _after_ + # HTTP headers are already written, so it cannot write them itself + die_error(undef, undef, $msg, -error_handler => 1, -no_http_header => 1); +} +set_message(\&handle_errors_html); + # dispatch if (!defined $action) { if (defined $hash) { @@ -3167,6 +3182,7 @@ sub blob_contenttype { sub git_header_html { my $status = shift || "200 OK"; my $expires = shift; + my %opts = @_; my $title = "$site_name"; if (defined $project) { @@ -3194,7 +3210,8 @@ sub git_header_html { $content_type = 'text/html'; } print $cgi->header(-type=>$content_type, -charset => 'utf-8', - -status=> $status, -expires => $expires); + -status=> $status, -expires => $expires) + unless ($opts{'-no_http_headers'}); my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : ''; print < @@ -3411,6 +3428,7 @@ 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', @@ -3419,7 +3437,7 @@ sub die_error { 500 => '500 Internal Server Error', 503 => '503 Service Unavailable', ); - git_header_html($http_responses{$status}); + git_header_html($http_responses{$status}, undef, %opts); print <

@@ -3433,7 +3451,8 @@ EOF print "\n"; git_footer_html(); - goto DONE_GITWEB; + goto DONE_GITWEB + unless ($opts{'-error_handler'}); } ## ---------------------------------------------------------------------- -- cgit v1.3 From efb2d0c5dcea1069bae2d26c9534e2025ee63e66 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 24 Apr 2010 16:01:10 +0200 Subject: gitweb: Move generating page title to separate subroutine get_page_title subroutine is currently used only in git_header_html. Nevertheless refactoring title generation allowed to reduce indent level. It would be used in more than one callsite in the patch adding caching activity indicator to gitweb. Signed-off-by: Jakub Narebski Acked-by: Petr Baudis Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index e579c14b44..7d75dc4c8c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3179,24 +3179,30 @@ sub blob_contenttype { ## ====================================================================== ## 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 = "$site_name"; - if (defined $project) { - $title .= " - " . to_utf8($project); - if (defined $action) { - $title .= "/$action"; - if (defined $file_name) { - $title .= " - " . esc_path($file_name); - if ($action eq "tree" && $file_name !~ m|/$|) { - $title .= "/"; - } - } - } - } + 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'. -- cgit v1.3 From ee1d8ee0f0f3faaa72f5c4886e61befaf7915718 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 30 Apr 2010 18:30:31 +0200 Subject: gitweb: Silence 'Variable VAR may be unavailable' warnings When $projects_list points to a directory, and git_get_projects_list scans this directory for repositories, there can be generated the following warnings (for persistent services like mod_perl or plackup): Variable "$project_maxdepth" may be unavailable at gitweb.cgi line 2443. Variable "$projectroot" may be unavailable at gitweb.cgi line 2451. Those are false positives; silence those warnings by explicitely declaring $project_maxdepth and $projectroot with 'our', as global variables, in anonymous subrotine passed to File::Find::find. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 3 +++ 1 file changed, 3 insertions(+) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 48e21dad6c..64eaa75996 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1520,6 +1520,9 @@ 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 -- cgit v1.3 From 8515392f5d4c5a36a0f9e4a73068f7106d7f4638 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 27 Apr 2010 22:45:19 +0200 Subject: gitweb: Improve installation instructions in gitweb/INSTALL Signed-off-by: Jakub Narebski Acked-by: Mark Rada Signed-off-by: Junio C Hamano --- gitweb/INSTALL | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'gitweb') diff --git a/gitweb/INSTALL b/gitweb/INSTALL index cbdc136470..208e4a8f05 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -7,7 +7,9 @@ gitweb.css, git-logo.png and git-favicon.png) to their destination. For example if git was (or is) installed with /usr prefix, you can do $ make prefix=/usr gitweb ;# as yourself - # cp gitweb/git* /var/www/cgi-bin/ ;# as root + # cp gitweb/*.cgi gitweb/*.css \ + gitweb/*.js gitweb/*.png \ + /var/www/cgi-bin/ ;# as root Alternatively you can use autoconf generated ./configure script to set up path to git binaries (via config.mak.autogen), so you can write @@ -16,7 +18,9 @@ instead $ make configure ;# as yourself $ ./configure --prefix=/usr ;# as yourself $ make gitweb ;# as yourself - # cp gitweb/git* /var/www/cgi-bin/ ;# as root + # cp gitweb/*.cgi gitweb/*.css \ + gitweb/*.js gitweb/*.png \ + /var/www/cgi-bin/ ;# as root The above example assumes that your web server is configured to run [executable] files in /var/www/cgi-bin/ as server scripts (as CGI @@ -74,9 +78,10 @@ file for gitweb (in gitweb/README). Build example ~~~~~~~~~~~~~ -- To install gitweb to /var/www/cgi-bin/gitweb/ when git wrapper - is installed at /usr/local/bin/git and the repositories (projects) - we want to display are under /home/local/scm, you can do +- To install gitweb to /var/www/cgi-bin/gitweb/, when git wrapper + is installed at /usr/local/bin/git, the repositories (projects) + we want to display are under /home/local/scm, and you do not use + minifiers, you can do make GITWEB_PROJECTROOT="/home/local/scm" \ GITWEB_JS="/gitweb/gitweb.js" \ @@ -86,8 +91,8 @@ Build example bindir=/usr/local/bin \ gitweb - cp -fv ~/git/gitweb/gitweb.{cgi,js,css} \ - ~/git/gitweb/git-{favicon,logo}.png \ + cp -fv gitweb/gitweb.{cgi,js,css} \ + gitweb/git-{favicon,logo}.png \ /var/www/cgi-bin/gitweb/ -- cgit v1.3 From 152d94348f6a38eb7cb5f4af8397f51ba06ddffb Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 1 May 2010 22:36:15 +0200 Subject: gitweb: Create install target for gitweb in Makefile Installing gitweb is now as easy as # make gitwebdir=/var/www/cgi-bin gitweb-install ;# as root The gitweb/INSTALL file was updated accordingly, to make use of this new target. Fix shell quoting, i.e. setting bindir_SQ etc., in gitweb/Makefile. Those variables were not used previously. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- Makefile | 3 +++ gitweb/INSTALL | 11 ++++------- gitweb/Makefile | 32 ++++++++++++++++++++++++++++---- 3 files changed, 35 insertions(+), 11 deletions(-) (limited to 'gitweb') diff --git a/Makefile b/Makefile index 910f4713ef..dab5a14e41 100644 --- a/Makefile +++ b/Makefile @@ -2004,6 +2004,9 @@ endif done; } && \ ./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X" +install-gitweb: + $(MAKE) -C gitweb install + install-doc: $(MAKE) -C Documentation install diff --git a/gitweb/INSTALL b/gitweb/INSTALL index 208e4a8f05..d484d76b75 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -6,10 +6,8 @@ First you have to generate gitweb.cgi from gitweb.perl using gitweb.css, git-logo.png and git-favicon.png) to their destination. For example if git was (or is) installed with /usr prefix, you can do - $ make prefix=/usr gitweb ;# as yourself - # cp gitweb/*.cgi gitweb/*.css \ - gitweb/*.js gitweb/*.png \ - /var/www/cgi-bin/ ;# as root + $ make prefix=/usr gitweb ;# as yourself + # make gitwebdir=/var/www/cgi-bin install-gitweb ;# as root Alternatively you can use autoconf generated ./configure script to set up path to git binaries (via config.mak.autogen), so you can write @@ -18,9 +16,8 @@ instead $ make configure ;# as yourself $ ./configure --prefix=/usr ;# as yourself $ make gitweb ;# as yourself - # cp gitweb/*.cgi gitweb/*.css \ - gitweb/*.js gitweb/*.png \ - /var/www/cgi-bin/ ;# as root + # make gitwebdir=/var/www/cgi-bin \ + install-gitweb ;# as root The above example assumes that your web server is configured to run [executable] files in /var/www/cgi-bin/ as server scripts (as CGI diff --git a/gitweb/Makefile b/gitweb/Makefile index f2e1d92fbb..935d2d2e07 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -12,7 +12,10 @@ all:: prefix ?= $(HOME) bindir ?= $(prefix)/bin +gitwebdir ?= /var/www/cgi-bin + RM ?= rm -f +INSTALL ?= install # default configuration for gitweb GITWEB_CONFIG = gitweb_config.perl @@ -49,9 +52,11 @@ SHELL_PATH ?= $(SHELL) PERL_PATH ?= /usr/bin/perl # Shell quote; -bindir_SQ = $(subst ','\'',$(bindir)) #' -SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) #' -PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) #' +bindir_SQ = $(subst ','\'',$(bindir))#' +gitwebdir_SQ = $(subst ','\'',$(gitwebdir))#' +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))#' +PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))#' +DESTDIR_SQ = $(subst ','\'',$(DESTDIR))#' # Quiet generation (unless V=1) QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir @@ -80,20 +85,30 @@ endif all:: gitweb.cgi +GITWEB_PROGRAMS = gitweb.cgi + ifdef JSMIN +GITWEB_FILES += gitweb.min.js GITWEB_JS = gitweb.min.js all:: gitweb.min.js gitweb.min.js: gitweb.js GITWEB-BUILD-OPTIONS $(QUIET_GEN)$(JSMIN) <$< >$@ +else +GITWEB_FILES += gitweb.js endif ifdef CSSMIN +GITWEB_FILES += gitweb.min.css GITWEB_CSS = gitweb.min.css all:: gitweb.min.css gitweb.min.css: gitweb.css GITWEB-BUILD-OPTIONS $(QUIET_GEN)$(CSSMIN) <$ >$@ +else +GITWEB_FILES += gitweb.css endif +GITWEB_FILES += git-logo.png git-favicon.png + GITWEB_REPLACE = \ -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ -e 's|++GIT_BINDIR++|$(bindir)|g' \ @@ -127,8 +142,17 @@ gitweb.cgi: gitweb.perl GITWEB-BUILD-OPTIONS chmod +x $@+ && \ mv $@+ $@ +### Installation rules + +install: all + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitwebdir_SQ)' + $(INSTALL) -m 755 $(GITWEB_PROGRAMS) '$(DESTDIR_SQ)$(gitwebdir_SQ)' + $(INSTALL) -m 644 $(GITWEB_FILES) '$(DESTDIR_SQ)$(gitwebdir_SQ)' + +### Cleaning rules + clean: $(RM) gitweb.cgi gitweb.min.js gitweb.min.css GITWEB-BUILD-OPTIONS -.PHONY: all clean .FORCE-GIT-VERSION-FILE FORCE +.PHONY: all clean install .FORCE-GIT-VERSION-FILE FORCE -- cgit v1.3 From b331fe54762770c647b8f95c8f5b16004c6120da Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 27 Apr 2010 21:34:44 +0200 Subject: gitweb: Syntax highlighting support It requires the 'highlight' program to do all the heavy-lifting. This is loosely based on Daniel Svensson's and Sham Chukoury's work in gitweb-xmms2.git (it cannot be cherry-picked, as gitweb-xmms2 first forked wildly, then not contributed back, and then went stale). [jn: cherry picked from bc1ed6aafd9ee4937559535c66c8bddf1864bec6 in http://repo.or.cz/w/git/dscho.git, with a few changes] Signed-off-by: Johannes Schindelin Signed-off-by: Jakub Narebski Acked-by: Petr Baudis Signed-off-by: Junio C Hamano --- gitweb/gitweb.css | 18 +++++++++++++++ gitweb/gitweb.perl | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 50067f2e0d..4132aabcdb 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -572,3 +572,21 @@ span.match { div.binary { font-style: italic; } + +/* Style definition generated by highlight 2.4.5, http://www.andre-simon.de/ */ + +/* Highlighting theme definition: */ + +.num { color:#2928ff; } +.esc { color:#ff00ff; } +.str { color:#ff0000; } +.dstr { color:#818100; } +.slc { color:#838183; font-style:italic; } +.com { color:#838183; font-style:italic; } +.dir { color:#008200; } +.sym { color:#000000; } +.line { color:#555555; } +.kwa { color:#000000; font-weight:bold; } +.kwb { color:#830000; } +.kwc { color:#000000; font-weight:bold; } +.kwd { color:#010181; } diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c356e95f18..de18ebf301 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -227,6 +227,36 @@ our %avatar_size = ( # Leave it undefined (or set to 'undef') to turn off load checking. our $maxload = 300; +# syntax highlighting +our %highlight_type = ( + # match by basename + 'SConstruct' => 'py', + 'Program' => 'py', + 'Library' => 'py', + 'Makefile' => 'make', + # match by extension + '\.py$' => 'py', # Python + '\.c$' => 'c', + '\.h$' => 'c', + '\.cpp$' => 'cpp', + '\.cxx$' => 'cpp', + '\.rb$' => 'ruby', + '\.java$' => 'java', + '\.css$' => 'css', + '\.php3?$' => 'php', + '\.sh$' => 'sh', # Bash / shell script + '\.pl$' => 'pl', # Perl + '\.js$' => 'js', # JavaScript + '\.tex$' => 'tex', # TeX and LaTeX + '\.bib$' => 'bib', # BibTeX + '\.x?html$' => 'xml', + '\.xml$' => 'xml', + '\.awk$' => 'awk', + '\.bat$' => 'bat', # DOS Batch script + '\.ini$' => 'ini', + '\.spec$' => 'spec', # RPM Spec +); + # You define site-wide feature defaults here; override them with # $GITWEB_CONFIG as necessary. our %feature = ( @@ -445,6 +475,19 @@ our %feature = ( '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, 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 { @@ -5346,6 +5389,7 @@ sub git_blob { open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash or die_error(500, "Couldn't cat $file_name, $hash"); my $mimetype = blob_mimetype($fd, $file_name); + # use 'blob_plain' (aka 'raw') view for files that cannot be displayed if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)! && -B $fd) { close $fd; return git_blob_plain($mimetype); @@ -5353,6 +5397,25 @@ sub git_blob { # we can have blame only for text/* mimetype $have_blame &&= ($mimetype =~ m!^text/!); + my $have_highlight = gitweb_check_feature('highlight'); + my $syntax; + if ($have_highlight && defined($file_name)) { + my $basename = basename($file_name, '.in'); + foreach my $regexp (keys %highlight_type) { + if ($basename =~ /$regexp/) { + $syntax = $highlight_type{$regexp}; + last; + } + } + + if ($syntax) { + close $fd; + open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ". + "highlight --xhtml --fragment -t 8 --syntax $syntax |" + or die_error(500, "Couldn't open file or run syntax highlighter"); + } + } + git_header_html(undef, $expires); my $formats_nav = ''; if (defined $hash_base && (my %co = parse_commit($hash_base))) { @@ -5404,7 +5467,7 @@ sub git_blob { $line = untabify($line); printf "\n", - $nr, $nr, $nr, esc_html($line, -nbsp=>1); + $nr, $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1); } } close $fd -- cgit v1.3 From 592ea4173af6b445042d74b989762fd41aecdf4c Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 27 Apr 2010 21:34:45 +0200 Subject: gitweb: Refactor syntax highlighting support This refactoring (adding guess_file_syntax and run_highlighter subroutines) is meant to make it easier in the future to add support for other syntax highlighing solutions, or make it smarter by not re-running `git cat-file` second time. Instead of looping over list of regexps (keys of %highlight_type hash), make use of the fact that choosing syntax is based either on full basename (%highlight_basename), or on file extension (%highlight_ext). Add some basic test of syntax highlighting (with 'highlight' as prerequisite) to t/t9500-gitweb-standalone-no-errors.sh test. While at it make git_blob Perl style prettier. Signed-off-by: Jakub Narebski Acked-by: Petr Baudis Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 116 ++++++++++++++++++--------------- t/t9500-gitweb-standalone-no-errors.sh | 29 +++++++++ 2 files changed, 92 insertions(+), 53 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index de18ebf301..7d9b660463 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -227,36 +227,6 @@ our %avatar_size = ( # Leave it undefined (or set to 'undef') to turn off load checking. our $maxload = 300; -# syntax highlighting -our %highlight_type = ( - # match by basename - 'SConstruct' => 'py', - 'Program' => 'py', - 'Library' => 'py', - 'Makefile' => 'make', - # match by extension - '\.py$' => 'py', # Python - '\.c$' => 'c', - '\.h$' => 'c', - '\.cpp$' => 'cpp', - '\.cxx$' => 'cpp', - '\.rb$' => 'ruby', - '\.java$' => 'java', - '\.css$' => 'css', - '\.php3?$' => 'php', - '\.sh$' => 'sh', # Bash / shell script - '\.pl$' => 'pl', # Perl - '\.js$' => 'js', # JavaScript - '\.tex$' => 'tex', # TeX and LaTeX - '\.bib$' => 'bib', # BibTeX - '\.x?html$' => 'xml', - '\.xml$' => 'xml', - '\.awk$' => 'awk', - '\.bat$' => 'bat', # DOS Batch script - '\.ini$' => 'ini', - '\.spec$' => 'spec', # RPM Spec -); - # You define site-wide feature defaults here; override them with # $GITWEB_CONFIG as necessary. our %feature = ( @@ -478,8 +448,8 @@ our %feature = ( # Syntax highlighting support. This is based on Daniel Svensson's # and Sham Chukoury's work in gitweb-xmms2.git. - # It requires the 'highlight' program, and therefore is disabled - # by default. + # 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]; @@ -3198,6 +3168,61 @@ sub blob_contenttype { 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 HTML: header, footer, error page @@ -5397,24 +5422,10 @@ sub git_blob { # we can have blame only for text/* mimetype $have_blame &&= ($mimetype =~ m!^text/!); - my $have_highlight = gitweb_check_feature('highlight'); - my $syntax; - if ($have_highlight && defined($file_name)) { - my $basename = basename($file_name, '.in'); - foreach my $regexp (keys %highlight_type) { - if ($basename =~ /$regexp/) { - $syntax = $highlight_type{$regexp}; - last; - } - } - - if ($syntax) { - close $fd; - open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ". - "highlight --xhtml --fragment -t 8 --syntax $syntax |" - or die_error(500, "Couldn't open file or run syntax highlighter"); - } - } + my $highlight = gitweb_check_feature('highlight'); + my $syntax = guess_file_syntax($highlight, $mimetype, $file_name); + $fd = run_highlighter($fd, $highlight, $syntax) + if $syntax; git_header_html(undef, $expires); my $formats_nav = ''; @@ -5465,9 +5476,8 @@ sub git_blob { chomp $line; $nr++; $line = untabify($line); - printf "\n", - $nr, $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1); + printf qq!
%4i %s
\n!, + $nr, href(-replay => 1), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1); } } close $fd diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 63b6b060e6..4f2b9b062b 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -647,4 +647,33 @@ test_expect_success \ gitweb_run "p=.git;a=summary"' test_debug 'cat gitweb.log' +# ---------------------------------------------------------------------- +# syntax highlighting + +cat >>gitweb_config.perl <<\EOF +$feature{'highlight'}{'override'} = 1; +EOF + +highlight --version >/dev/null 2>&1 +if [ $? -eq 127 ]; then + say "Skipping syntax highlighting test, because 'highlight' was not found" +else + test_set_prereq HIGHLIGHT +fi + +test_expect_success HIGHLIGHT \ + 'syntax highlighting (no highlight)' \ + 'git config gitweb.highlight yes && + gitweb_run "p=.git;a=blob;f=file"' +test_debug 'cat gitweb.log' + +test_expect_success HIGHLIGHT \ + 'syntax highlighting (highlighted)' \ + 'git config gitweb.highlight yes && + echo "#!/usr/bin/sh" > test.sh && + git add test.sh && + git commit -m "Add test.sh" && + gitweb_run "p=.git;a=blob;f=test.sh"' +test_debug 'cat gitweb.log' + test_done -- cgit v1.3 From c2394fe93441618056363a14c8b63f526afa9615 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 7 May 2010 14:54:04 +0200 Subject: gitweb: Put all per-connection code in run() subroutine All code that is run per-connection (as opposed to those parts of gitweb code that can be run once) is put into appropriate subroutines: - evaluate_uri - evaluate_gitweb_config - evaluate_git_version (here only because $GIT can be set in config) - check_loadavg (as soon as possible; $git_version must be defined) - evaluate_query_params (counterpart to evaluate_path_info) - evaluate_and_validate_params - evaluate_git_dir (requires $project) - configure_gitweb_features (@snapshot_fmts, $git_avatar) - dispatch (includes setting default $action) The difference is best viewed with '-w', '--ignore-all-space' option, because of reindent caused by putting code in subroutines. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 357 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 204 insertions(+), 153 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ed92dcac08..4a3632ab50 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -28,34 +28,42 @@ BEGIN { CGI->compile() if $ENV{'MOD_PERL'}; } -our $cgi = new CGI; our $version = "++GIT_VERSION++"; -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; +our ($my_url, $my_uri, $base_url, $path_info, $home_link); +sub evaluate_uri { + our $cgi; -# 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'}; + 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 || "/"; } # core git executable to use @@ -70,9 +78,6 @@ our $projectroot = "++GITWEB_PROJECTROOT++"; # the number is relative to the projectroot our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++"; -# target of the home link on top of all pages -our $home_link = $my_uri || "/"; - # string of the home link on top of all pages our $home_link_str = "++GITWEB_HOME_LINK_STR++"; @@ -553,15 +558,18 @@ sub filter_snapshot_fmts { !$known_snapshot_formats{$_}{'disabled'}} @fmts; } -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 $@; +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. @@ -587,13 +595,16 @@ sub get_loadavg { } # version of the core git binary -our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown"; -$number_of_git_cmds++; - -$projects_list ||= $projectroot; +our $git_version; +sub evaluate_git_version { + our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown"; + $number_of_git_cmds++; +} -if (defined $maxload && get_loadavg() > $maxload) { - die_error(503, "The load average on the server is too high"); +sub check_loadavg { + if (defined $maxload && get_loadavg() > $maxload) { + die_error(503, "The load average on the server is too high"); + } } # ====================================================================== @@ -680,11 +691,15 @@ our %allowed_options = ( # 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 -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_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); + } } } @@ -831,149 +846,185 @@ sub evaluate_path_info { } } } -evaluate_path_info(); -our $action = $input_params{'action'}; -if (defined $action) { - if (!validate_action($action)) { - die_error(400, "Invalid action parameter"); +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'}; + if (defined $action) { + if (!validate_action($action)) { + die_error(400, "Invalid action parameter"); + } } -} -# parameters which are pathnames -our $project = $input_params{'project'}; -if (defined $project) { - if (!validate_project($project)) { - undef $project; - die_error(404, "No such project"); + # parameters which are pathnames + our $project = $input_params{'project'}; + if (defined $project) { + if (!validate_project($project)) { + undef $project; + die_error(404, "No such project"); + } } -} -our $file_name = $input_params{'file_name'}; -if (defined $file_name) { - if (!validate_pathname($file_name)) { - die_error(400, "Invalid file parameter"); + our $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'}; -if (defined $file_parent) { - if (!validate_pathname($file_parent)) { - die_error(400, "Invalid file parent parameter"); + our $file_parent = $input_params{'file_parent'}; + if (defined $file_parent) { + if (!validate_pathname($file_parent)) { + die_error(400, "Invalid file parent parameter"); + } } -} -# parameters which are refnames -our $hash = $input_params{'hash'}; -if (defined $hash) { - if (!validate_refname($hash)) { - die_error(400, "Invalid hash parameter"); + # parameters which are refnames + our $hash = $input_params{'hash'}; + if (defined $hash) { + if (!validate_refname($hash)) { + die_error(400, "Invalid hash parameter"); + } } -} -our $hash_parent = $input_params{'hash_parent'}; -if (defined $hash_parent) { - if (!validate_refname($hash_parent)) { - die_error(400, "Invalid hash parent parameter"); + our $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'}; -if (defined $hash_base) { - if (!validate_refname($hash_base)) { - die_error(400, "Invalid hash base parameter"); + our $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 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 -foreach my $opt (@extra_options) { - if (not exists $allowed_options{$opt}) { - die_error(400, "Invalid option parameter"); - } - if (not grep(/^$action$/, @{$allowed_options{$opt}})) { - die_error(400, "Invalid option parameter for this action"); + our @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 + foreach my $opt (@extra_options) { + if (not exists $allowed_options{$opt}) { + die_error(400, "Invalid option parameter"); + } + if (not grep(/^$action$/, @{$allowed_options{$opt}})) { + die_error(400, "Invalid option parameter for this action"); + } } -} -our $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"); + our $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"); + } } -} -# other parameters -our $page = $input_params{'page'}; -if (defined $page) { - if ($page =~ m/[^0-9]/) { - die_error(400, "Invalid page parameter"); + # other parameters + our $page = $input_params{'page'}; + if (defined $page) { + if ($page =~ m/[^0-9]/) { + die_error(400, "Invalid page parameter"); + } } -} -our $searchtype = $input_params{'searchtype'}; -if (defined $searchtype) { - if ($searchtype =~ m/[^a-z]/) { - die_error(400, "Invalid searchtype parameter"); + our $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'}; + our $search_use_regexp = $input_params{'search_use_regexp'}; -our $searchtext = $input_params{'searchtext'}; -our $search_regexp; -if (defined $searchtext) { - if (length($searchtext) < 2) { - die_error(403, "At least two characters are required for search parameter"); + our $searchtext = $input_params{'searchtext'}; + our $search_regexp; + if (defined $searchtext) { + if (length($searchtext) < 2) { + die_error(403, "At least two characters are required for search parameter"); + } + $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext; } - $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext; } # path to the current git repository our $git_dir; -$git_dir = "$projectroot/$project" if $project; +sub evaluate_git_dir { + our $git_dir = "$projectroot/$project" if $project; +} -# list of supported snapshot formats -our @snapshot_fmts = gitweb_get_feature('snapshot'); -@snapshot_fmts = filter_snapshot_fmts(@snapshot_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 = ''; + # 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 = ''; + } } # dispatch -if (!defined $action) { - if (defined $hash) { - $action = git_get_type($hash); - } elsif (defined $hash_base && defined $file_name) { - $action = git_get_type("$hash_base:$file_name"); - } elsif (defined $project) { - $action = 'summary'; - } else { - $action = 'project_list'; +sub dispatch { + if (!defined $action) { + if (defined $hash) { + $action = git_get_type($hash); + } elsif (defined $hash_base && defined $file_name) { + $action = git_get_type("$hash_base:$file_name"); + } elsif (defined $project) { + $action = 'summary'; + } else { + $action = 'project_list'; + } } + if (!defined($actions{$action})) { + die_error(400, "Unknown action"); + } + if ($action !~ m/^(?:opml|project_list|project_index)$/ && + !$project) { + die_error(400, "Project needed"); + } + $actions{$action}->(); } -if (!defined($actions{$action})) { - die_error(400, "Unknown action"); -} -if ($action !~ m/^(?:opml|project_list|project_index)$/ && - !$project) { - die_error(400, "Project needed"); + +sub run { + our $t0 = [Time::HiRes::gettimeofday()] + if defined $t0; + + evaluate_uri(); + evaluate_gitweb_config(); + evaluate_git_version(); + check_loadavg(); + + # $projectroot and $projects_list might be set in gitweb config file + $projects_list ||= $projectroot; + + evaluate_query_params(); + evaluate_path_info(); + evaluate_and_validate_params(); + evaluate_git_dir(); + + configure_gitweb_features(); + + dispatch(); + + DONE_GITWEB: + 1; } -$actions{$action}->(); -DONE_GITWEB: -1; +our $cgi = CGI->new(); +run(); ## ====================================================================== ## action links -- cgit v1.3 From a0446e7ba8dc05c67052b67c2faacdf5f7ce625a Mon Sep 17 00:00:00 2001 From: Sam Vilain Date: Fri, 7 May 2010 14:54:05 +0200 Subject: gitweb: Add support for FastCGI, using CGI::Fast Former run() subroutine got renamed to run_request(). The new run() subroutine can run multiple requests at once if run as FastCGI script. To run gitweb as FastCGI script you must specify '--fastcgi' / '-f' command line option to gitweb, otherwise it runs as an ordinary CGI script. [jn: cherry picked from 56d7d436644ab296155a697552ea1345f2701620 in http://utsl.gen.nz/gitweb/?p=gitweb which was originally based on v264 (2326acfa95ac86a53804ca8eeeb482c2f9265e34) by Kay Sievers; updated to reflect current gitweb code] TODO: update 'gitweb/README' and/or 'gitweb/INSTALL' files. Signed-off-by: Sam Vilain Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 4a3632ab50..b044d18aca 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -999,7 +999,7 @@ sub dispatch { $actions{$action}->(); } -sub run { +sub run_request { our $t0 = [Time::HiRes::gettimeofday()] if defined $t0; @@ -1019,11 +1019,61 @@ sub run { configure_gitweb_features(); dispatch(); +} + +our $is_last_request = sub { 1 }; +our ($pre_dispatch_hook, $post_dispatch_hook, $pre_listen_hook); +our $CGI = 'CGI'; +our $cgi; +sub evaluate_argv { + return unless (@ARGV); + + require Getopt::Long; + Getopt::Long::GetOptions( + 'fastcgi|fcgi|f' => sub { + require CGI::Fast; + our $CGI = 'CGI::Fast'; + + my $request_number = 0; + # let each child service 100 requests + our $is_last_request = sub { ++$request_number > 100 }; + }, + 'nproc|n=i' => sub { + my ($arg, $val) = @_; + return unless eval { require FCGI::ProcManager; 1; }; + my $proc_manager = FCGI::ProcManager->new({ + n_processes => $val, + }); + our $pre_listen_hook = sub { $proc_manager->pm_manage() }; + our $pre_dispatch_hook = sub { $proc_manager->pm_pre_dispatch() }; + our $post_dispatch_hook = sub { $proc_manager->pm_post_dispatch() }; + }, + ); +} + +sub run { + evaluate_argv(); + + $pre_listen_hook->() + if $pre_listen_hook; + + REQUEST: + while ($cgi = $CGI->new()) { + $pre_dispatch_hook->() + if $pre_dispatch_hook; + + run_request(); + + $pre_dispatch_hook->() + if $post_dispatch_hook; + + last REQUEST if ($is_last_request->()); + } DONE_GITWEB: 1; } -our $cgi = CGI->new(); + run(); ## ====================================================================== -- cgit v1.3 From 04794fdc2701f15d79d92805cfe94c9e754e2e05 Mon Sep 17 00:00:00 2001 From: Pavan Kumar Sunkara Date: Mon, 10 May 2010 18:41:35 +0200 Subject: gitweb: Use @diff_opts while using format-patch Make git-format-patch (used by 'patch' and 'patches' views) use the same rename detection options that git-diff and git-diff-tree (used by 'commitdiff', 'blobdiff', etc.) use. Signed-off-by: Pavan Kumar Sunkara Acked-by: Jakub Narebski Acked-by: Giuseppe Bilotta Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c356e95f18..77e5f795a1 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -6117,8 +6117,8 @@ sub git_commitdiff { } push @commit_spec, '--root', $hash; } - open $fd, "-|", git_cmd(), "format-patch", '--encoding=utf8', - '--stdout', @commit_spec + open $fd, "-|", git_cmd(), "format-patch", @diff_opts, + '--encoding=utf8', '--stdout', @commit_spec or die_error(500, "Open git-format-patch failed"); } else { die_error(400, "Unknown commitdiff format"); -- cgit v1.3 From 18d05328f3333e63bfffa65ac887ea16de60918c Mon Sep 17 00:00:00 2001 From: Pavan Kumar Sunkara Date: Fri, 28 May 2010 11:55:49 +0530 Subject: gitweb: Move static files into seperate subdirectory Create a new subdirectory called 'static' in gitweb/, and move all static files required by gitweb.cgi when running, which means styles, images and Javascript code. This should make gitweb more readable and easier to maintain. Update t/gitweb-lib.sh to reflect this change.The install-gitweb now also include moving of static files into 'static' subdirectory in target directory: update Makefile, gitweb's INSTALL, README and Makefile accordingly. Signed-off-by: Pavan Kumar Sunkara Mentored-by: Christian Couder Mentored-by: Petr Baudis Acked-by: Jakub Narebski Acked-by: Petr Baudis Signed-off-by: Junio C Hamano --- Makefile | 18 +- gitweb/INSTALL | 19 +- gitweb/Makefile | 40 +- gitweb/README | 14 +- gitweb/git-favicon.png | Bin 115 -> 0 bytes gitweb/git-logo.png | Bin 207 -> 0 bytes gitweb/gitweb.css | 574 --------------------------- gitweb/gitweb.js | 875 ------------------------------------------ gitweb/static/git-favicon.png | Bin 0 -> 115 bytes gitweb/static/git-logo.png | Bin 0 -> 207 bytes gitweb/static/gitweb.css | 574 +++++++++++++++++++++++++++ gitweb/static/gitweb.js | 875 ++++++++++++++++++++++++++++++++++++++++++ t/gitweb-lib.sh | 6 +- 13 files changed, 1499 insertions(+), 1496 deletions(-) delete mode 100644 gitweb/git-favicon.png delete mode 100644 gitweb/git-logo.png delete mode 100644 gitweb/gitweb.css delete mode 100644 gitweb/gitweb.js create mode 100644 gitweb/static/git-favicon.png create mode 100644 gitweb/static/git-logo.png create mode 100644 gitweb/static/gitweb.css create mode 100644 gitweb/static/gitweb.js (limited to 'gitweb') diff --git a/Makefile b/Makefile index 07cab8f6a4..1ec90f53fa 100644 --- a/Makefile +++ b/Makefile @@ -1578,32 +1578,32 @@ gitweb: $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) all ifdef JSMIN -GITWEB_PROGRAMS += gitweb/gitweb.min.js -GITWEB_JS = gitweb/gitweb.min.js +GITWEB_PROGRAMS += gitweb/static/gitweb.min.js +GITWEB_JS = gitweb/static/gitweb.min.js else -GITWEB_JS = gitweb/gitweb.js +GITWEB_JS = gitweb/static/gitweb.js endif ifdef CSSMIN -GITWEB_PROGRAMS += gitweb/gitweb.min.css -GITWEB_CSS = gitweb/gitweb.min.css +GITWEB_PROGRAMS += gitweb/static/gitweb.min.css +GITWEB_CSS = gitweb/static/gitweb.min.css else -GITWEB_CSS = gitweb/gitweb.css +GITWEB_CSS = gitweb/static/gitweb.css endif OTHER_PROGRAMS += gitweb/gitweb.cgi $(GITWEB_PROGRAMS) gitweb/gitweb.cgi: gitweb/gitweb.perl $(GITWEB_PROGRAMS) $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@) ifdef JSMIN -gitweb/gitweb.min.js: gitweb/gitweb.js +gitweb/static/gitweb.min.js: gitweb/static/gitweb.js $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@) endif # JSMIN ifdef CSSMIN -gitweb/gitweb.min.css: gitweb/gitweb.css +gitweb/static/gitweb.min.css: gitweb/static/gitweb.css $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@) endif # CSSMIN -git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css gitweb/gitweb.js +git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/static/gitweb.css gitweb/static/gitweb.js $(QUIET_GEN)$(RM) $@ $@+ && \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ diff --git a/gitweb/INSTALL b/gitweb/INSTALL index d484d76b75..823053173c 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -2,9 +2,10 @@ GIT web Interface (gitweb) Installation ======================================= First you have to generate gitweb.cgi from gitweb.perl using -"make gitweb", then copy appropriate files (gitweb.cgi, gitweb.js, -gitweb.css, git-logo.png and git-favicon.png) to their destination. -For example if git was (or is) installed with /usr prefix, you can do +"make gitweb", then "make install-gitweb" appropriate files +(gitweb.cgi, gitweb.js, gitweb.css, git-logo.png and git-favicon.png) +to their destination. For example if git was (or is) installed with +/usr prefix and gitwebdir is /var/www/cgi-bin, you can do $ make prefix=/usr gitweb ;# as yourself # make gitwebdir=/var/www/cgi-bin install-gitweb ;# as root @@ -81,16 +82,14 @@ Build example minifiers, you can do make GITWEB_PROJECTROOT="/home/local/scm" \ - GITWEB_JS="/gitweb/gitweb.js" \ - GITWEB_CSS="/gitweb/gitweb.css" \ - GITWEB_LOGO="/gitweb/git-logo.png" \ - GITWEB_FAVICON="/gitweb/git-favicon.png" \ + GITWEB_JS="gitweb/static/gitweb.js" \ + GITWEB_CSS="gitweb/static/gitweb.css" \ + GITWEB_LOGO="gitweb/static/git-logo.png" \ + GITWEB_FAVICON="gitweb/static/git-favicon.png" \ bindir=/usr/local/bin \ gitweb - cp -fv gitweb/gitweb.{cgi,js,css} \ - gitweb/git-{favicon,logo}.png \ - /var/www/cgi-bin/gitweb/ + make gitwebdir=/var/www/cgi-bin/gitweb install-gitweb Gitweb config file diff --git a/gitweb/Makefile b/gitweb/Makefile index 935d2d2e07..d2584fedd8 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -4,10 +4,10 @@ all:: # Define V=1 to have a more verbose compile. # # Define JSMIN to point to JavaScript minifier that functions as -# a filter to have gitweb.js minified. +# a filter to have static/gitweb.js minified. # # Define CSSMIN to point to a CSS minifier in order to generate a minified -# version of gitweb.css +# version of static/gitweb.css # prefix ?= $(HOME) @@ -29,10 +29,10 @@ GITWEB_STRICT_EXPORT = GITWEB_BASE_URL = GITWEB_LIST = GITWEB_HOMETEXT = indextext.html -GITWEB_CSS = gitweb.css -GITWEB_LOGO = git-logo.png -GITWEB_FAVICON = git-favicon.png -GITWEB_JS = gitweb.js +GITWEB_CSS = static/gitweb.css +GITWEB_LOGO = static/git-logo.png +GITWEB_FAVICON = static/git-favicon.png +GITWEB_JS = static/gitweb.js GITWEB_SITE_HEADER = GITWEB_SITE_FOOTER = @@ -54,6 +54,7 @@ PERL_PATH ?= /usr/bin/perl # Shell quote; bindir_SQ = $(subst ','\'',$(bindir))#' gitwebdir_SQ = $(subst ','\'',$(gitwebdir))#' +gitwebstaticdir_SQ = $(subst ','\'',$(gitwebdir)/static)#' SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))#' PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))#' DESTDIR_SQ = $(subst ','\'',$(DESTDIR))#' @@ -88,26 +89,26 @@ all:: gitweb.cgi GITWEB_PROGRAMS = gitweb.cgi ifdef JSMIN -GITWEB_FILES += gitweb.min.js -GITWEB_JS = gitweb.min.js -all:: gitweb.min.js -gitweb.min.js: gitweb.js GITWEB-BUILD-OPTIONS +GITWEB_FILES += static/gitweb.min.js +GITWEB_JS = static/gitweb.min.js +all:: static/gitweb.min.js +static/gitweb.min.js: static/gitweb.js GITWEB-BUILD-OPTIONS $(QUIET_GEN)$(JSMIN) <$< >$@ else -GITWEB_FILES += gitweb.js +GITWEB_FILES += static/gitweb.js endif ifdef CSSMIN -GITWEB_FILES += gitweb.min.css -GITWEB_CSS = gitweb.min.css -all:: gitweb.min.css -gitweb.min.css: gitweb.css GITWEB-BUILD-OPTIONS +GITWEB_FILES += static/gitweb.min.css +GITWEB_CSS = static/gitweb.min.css +all:: static/gitweb.min.css +static/gitweb.min.css: static/gitweb.css GITWEB-BUILD-OPTIONS $(QUIET_GEN)$(CSSMIN) <$ >$@ else -GITWEB_FILES += gitweb.css +GITWEB_FILES += static/gitweb.css endif -GITWEB_FILES += git-logo.png git-favicon.png +GITWEB_FILES += static/git-logo.png static/git-favicon.png GITWEB_REPLACE = \ -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ @@ -147,12 +148,13 @@ gitweb.cgi: gitweb.perl GITWEB-BUILD-OPTIONS install: all $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitwebdir_SQ)' $(INSTALL) -m 755 $(GITWEB_PROGRAMS) '$(DESTDIR_SQ)$(gitwebdir_SQ)' - $(INSTALL) -m 644 $(GITWEB_FILES) '$(DESTDIR_SQ)$(gitwebdir_SQ)' + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)' + $(INSTALL) -m 644 $(GITWEB_FILES) '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)' ### Cleaning rules clean: - $(RM) gitweb.cgi gitweb.min.js gitweb.min.css GITWEB-BUILD-OPTIONS + $(RM) gitweb.cgi static/gitweb.min.js static/gitweb.min.css GITWEB-BUILD-OPTIONS .PHONY: all clean install .FORCE-GIT-VERSION-FILE FORCE diff --git a/gitweb/README b/gitweb/README index 71742b335d..0e19be8d21 100644 --- a/gitweb/README +++ b/gitweb/README @@ -80,24 +80,26 @@ You can specify the following configuration variables when building GIT: Points to the location where you put gitweb.css on your web server (or to be more generic, the URI of gitweb stylesheet). Relative to the base URI of gitweb. Note that you can setup multiple stylesheets from - the gitweb config file. [Default: gitweb.css (or gitweb.min.css if the - CSSMIN variable is defined / CSS minifier is used)] + the gitweb config file. [Default: static/gitweb.css (or + static/gitweb.min.css if the CSSMIN variable is defined / CSS minifier + is used)] * GITWEB_LOGO Points to the location where you put git-logo.png on your web server (or to be more generic URI of logo, 72x27 size, displayed in top right corner of each gitweb page, and used as logo for Atom feed). Relative - to base URI of gitweb. [Default: git-logo.png] + to base URI of gitweb. [Default: static/git-logo.png] * GITWEB_FAVICON Points to the location where you put git-favicon.png on your web server (or to be more generic URI of favicon, assumed to be image/png type; web browsers that support favicons (website icons) may display them in the browser's URL bar and next to site name in bookmarks). Relative - to base URI of gitweb. [Default: git-favicon.png] + to base URI of gitweb. [Default: static/git-favicon.png] * GITWEB_JS Points to the localtion where you put gitweb.js on your web server (or to be more generic URI of JavaScript code used by gitweb). - Relative to base URI of gitweb. [Default: gitweb.js (or gitweb.min.js - if JSMIN build variable is defined / JavaScript minifier is used)] + Relative to base URI of gitweb. [Default: static/gitweb.js (or + static/gitweb.min.js if JSMIN build variable is defined / JavaScript + minifier is used)] * GITWEB_CONFIG This Perl file will be loaded using 'do' and can be used to override any of the options above as well as some other options -- see the "Runtime diff --git a/gitweb/git-favicon.png b/gitweb/git-favicon.png deleted file mode 100644 index aae35a70e7..0000000000 Binary files a/gitweb/git-favicon.png and /dev/null differ diff --git a/gitweb/git-logo.png b/gitweb/git-logo.png deleted file mode 100644 index f4ede2e944..0000000000 Binary files a/gitweb/git-logo.png and /dev/null differ diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css deleted file mode 100644 index 50067f2e0d..0000000000 --- a/gitweb/gitweb.css +++ /dev/null @@ -1,574 +0,0 @@ -body { - font-family: sans-serif; - font-size: small; - border: solid #d9d8d1; - border-width: 1px; - margin: 10px; - background-color: #ffffff; - color: #000000; -} - -a { - color: #0000cc; -} - -a:hover, a:visited, a:active { - color: #880000; -} - -span.cntrl { - border: dashed #aaaaaa; - border-width: 1px; - padding: 0px 2px 0px 2px; - margin: 0px 2px 0px 2px; -} - -img.logo { - float: right; - border-width: 0px; -} - -img.avatar { - vertical-align: middle; -} - -a.list img.avatar { - border-style: none; -} - -div.page_header { - height: 25px; - padding: 8px; - font-size: 150%; - font-weight: bold; - background-color: #d9d8d1; -} - -div.page_header a:visited, a.header { - color: #0000cc; -} - -div.page_header a:hover { - color: #880000; -} - -div.page_nav { - padding: 8px; -} - -div.page_nav a:visited { - color: #0000cc; -} - -div.page_path { - padding: 8px; - font-weight: bold; - border: solid #d9d8d1; - border-width: 0px 0px 1px; -} - -div.page_footer { - height: 17px; - padding: 4px 8px; - background-color: #d9d8d1; -} - -div.page_footer_text { - float: left; - color: #555555; - font-style: italic; -} - -div#generating_info { - margin: 4px; - font-size: smaller; - text-align: center; - color: #505050; -} - -div.page_body { - padding: 8px; - font-family: monospace; -} - -div.title, a.title { - display: block; - padding: 6px 8px; - font-weight: bold; - background-color: #edece6; - text-decoration: none; - color: #000000; -} - -div.readme { - padding: 8px; -} - -a.title:hover { - background-color: #d9d8d1; -} - -div.title_text { - padding: 6px 0px; - border: solid #d9d8d1; - border-width: 0px 0px 1px; - font-family: monospace; -} - -div.log_body { - padding: 8px 8px 8px 150px; -} - -span.age { - position: relative; - float: left; - width: 142px; - font-style: italic; -} - -span.signoff { - color: #888888; -} - -div.log_link { - padding: 0px 8px; - font-size: 70%; - font-family: sans-serif; - font-style: normal; - position: relative; - float: left; - width: 136px; -} - -div.list_head { - padding: 6px 8px 4px; - border: solid #d9d8d1; - border-width: 1px 0px 0px; - font-style: italic; -} - -.author_date, .author { - font-style: italic; -} - -div.author_date { - padding: 8px; - border: solid #d9d8d1; - border-width: 0px 0px 1px 0px; -} - -a.list { - text-decoration: none; - color: #000000; -} - -a.subject, a.name { - font-weight: bold; -} - -table.tags a.subject { - font-weight: normal; -} - -a.list:hover { - text-decoration: underline; - color: #880000; -} - -a.text { - text-decoration: none; - color: #0000cc; -} - -a.text:visited { - text-decoration: none; - color: #880000; -} - -a.text:hover { - text-decoration: underline; - color: #880000; -} - -table { - padding: 8px 4px; - border-spacing: 0; -} - -table.diff_tree { - font-family: monospace; -} - -table.combined.diff_tree th { - text-align: center; -} - -table.combined.diff_tree td { - padding-right: 24px; -} - -table.combined.diff_tree th.link, -table.combined.diff_tree td.link { - padding: 0px 2px; -} - -table.combined.diff_tree td.nochange a { - color: #6666ff; -} - -table.combined.diff_tree td.nochange a:hover, -table.combined.diff_tree td.nochange a:visited { - color: #d06666; -} - -table.blame { - border-collapse: collapse; -} - -table.blame td { - padding: 0px 5px; - font-size: 100%; - vertical-align: top; -} - -th { - padding: 2px 5px; - font-size: 100%; - text-align: left; -} - -/* do not change row style on hover for 'blame' view */ -tr.light, -table.blame .light:hover { - background-color: #ffffff; -} - -tr.dark, -table.blame .dark:hover { - background-color: #f6f6f0; -} - -/* currently both use the same, but it can change */ -tr.light:hover, -tr.dark:hover { - background-color: #edece6; -} - -/* boundary commits in 'blame' view */ -/* and commits without "previous" */ -tr.boundary td.sha1, -tr.no-previous td.linenr { - font-weight: bold; -} - -/* for 'blame_incremental', during processing */ -tr.color1 { background-color: #f6fff6; } -tr.color2 { background-color: #f6f6ff; } -tr.color3 { background-color: #fff6f6; } - -td { - padding: 2px 5px; - font-size: 100%; - vertical-align: top; -} - -td.link, td.selflink { - padding: 2px 5px; - font-family: sans-serif; - font-size: 70%; -} - -td.selflink { - padding-right: 0px; -} - -td.sha1 { - font-family: monospace; -} - -.error { - color: red; - background-color: yellow; -} - -td.current_head { - text-decoration: underline; -} - -table.diff_tree span.file_status.new { - color: #008000; -} - -table.diff_tree span.file_status.deleted { - color: #c00000; -} - -table.diff_tree span.file_status.moved, -table.diff_tree span.file_status.mode_chnge { - color: #777777; -} - -table.diff_tree span.file_status.copied { - color: #70a070; -} - -/* noage: "No commits" */ -table.project_list td.noage { - color: #808080; - font-style: italic; -} - -/* age2: 60*60*24*2 <= age */ -table.project_list td.age2, table.blame td.age2 { - font-style: italic; -} - -/* age1: 60*60*2 <= age < 60*60*24*2 */ -table.project_list td.age1 { - color: #009900; - font-style: italic; -} - -table.blame td.age1 { - color: #009900; - background: transparent; -} - -/* age0: age < 60*60*2 */ -table.project_list td.age0 { - color: #009900; - font-style: italic; - font-weight: bold; -} - -table.blame td.age0 { - color: #009900; - background: transparent; - font-weight: bold; -} - -td.pre, div.pre, div.diff { - font-family: monospace; - font-size: 12px; - white-space: pre; -} - -td.mode { - font-family: monospace; -} - -/* progress of blame_interactive */ -div#progress_bar { - height: 2px; - margin-bottom: -2px; - background-color: #d8d9d0; -} -div#progress_info { - float: right; - text-align: right; -} - -/* format of (optional) objects size in 'tree' view */ -td.size { - font-family: monospace; - text-align: right; -} - -/* styling of diffs (patchsets): commitdiff and blobdiff views */ -div.diff.header, -div.diff.extended_header { - white-space: normal; -} - -div.diff.header { - font-weight: bold; - - background-color: #edece6; - - margin-top: 4px; - padding: 4px 0px 2px 0px; - border: solid #d9d8d1; - border-width: 1px 0px 1px 0px; -} - -div.diff.header a.path { - text-decoration: underline; -} - -div.diff.extended_header, -div.diff.extended_header a.path, -div.diff.extended_header a.hash { - color: #777777; -} - -div.diff.extended_header .info { - color: #b0b0b0; -} - -div.diff.extended_header { - background-color: #f6f5ee; - padding: 2px 0px 2px 0px; -} - -div.diff a.list, -div.diff a.path, -div.diff a.hash { - text-decoration: none; -} - -div.diff a.list:hover, -div.diff a.path:hover, -div.diff a.hash:hover { - text-decoration: underline; -} - -div.diff.to_file a.path, -div.diff.to_file { - color: #007000; -} - -div.diff.add { - color: #008800; -} - -div.diff.from_file a.path, -div.diff.from_file { - color: #aa0000; -} - -div.diff.rem { - color: #cc0000; -} - -div.diff.chunk_header a, -div.diff.chunk_header { - color: #990099; -} - -div.diff.chunk_header { - border: dotted #ffe0ff; - border-width: 1px 0px 0px 0px; - margin-top: 2px; -} - -div.diff.chunk_header span.chunk_info { - background-color: #ffeeff; -} - -div.diff.chunk_header span.section { - color: #aa22aa; -} - -div.diff.incomplete { - color: #cccccc; -} - -div.diff.nodifferences { - font-weight: bold; - color: #600000; -} - -div.index_include { - border: solid #d9d8d1; - border-width: 0px 0px 1px; - padding: 12px 8px; -} - -div.search { - font-size: 100%; - font-weight: normal; - margin: 4px 8px; - float: right; - top: 56px; - right: 12px -} - -p.projsearch { - text-align: center; -} - -td.linenr { - text-align: right; -} - -a.linenr { - color: #999999; - text-decoration: none -} - -a.rss_logo { - float: right; - padding: 3px 0px; - width: 35px; - line-height: 10px; - border: 1px solid; - border-color: #fcc7a5 #7d3302 #3e1a01 #ff954e; - color: #ffffff; - background-color: #ff6600; - font-weight: bold; - font-family: sans-serif; - font-size: 70%; - text-align: center; - text-decoration: none; -} - -a.rss_logo:hover { - background-color: #ee5500; -} - -a.rss_logo.generic { - background-color: #ff8800; -} - -a.rss_logo.generic:hover { - background-color: #ee7700; -} - -span.refs span { - padding: 0px 4px; - font-size: 70%; - font-weight: normal; - border: 1px solid; - background-color: #ffaaff; - border-color: #ffccff #ff00ee #ff00ee #ffccff; -} - -span.refs span a { - text-decoration: none; - color: inherit; -} - -span.refs span a:hover { - text-decoration: underline; -} - -span.refs span.indirect { - font-style: italic; -} - -span.refs span.ref { - background-color: #aaaaff; - border-color: #ccccff #0033cc #0033cc #ccccff; -} - -span.refs span.tag { - background-color: #ffffaa; - border-color: #ffffcc #ffee00 #ffee00 #ffffcc; -} - -span.refs span.head { - background-color: #aaffaa; - border-color: #ccffcc #00cc33 #00cc33 #ccffcc; -} - -span.atnight { - color: #cc0000; -} - -span.match { - color: #e00000; -} - -div.binary { - font-style: italic; -} diff --git a/gitweb/gitweb.js b/gitweb/gitweb.js deleted file mode 100644 index 9c66928c4a..0000000000 --- a/gitweb/gitweb.js +++ /dev/null @@ -1,875 +0,0 @@ -// Copyright (C) 2007, Fredrik Kuivinen -// 2007, Petr Baudis -// 2008-2009, Jakub Narebski - -/** - * @fileOverview JavaScript code for gitweb (git web interface). - * @license GPLv2 or later - */ - -/* ============================================================ */ -/* functions for generic gitweb actions and views */ - -/** - * used to check if link has 'js' query parameter already (at end), - * and other reasons to not add 'js=1' param at the end of link - * @constant - */ -var jsExceptionsRe = /[;?]js=[01]$/; - -/** - * Add '?js=1' or ';js=1' to the end of every link in the document - * that doesn't have 'js' query parameter set already. - * - * Links with 'js=1' lead to JavaScript version of given action, if it - * exists (currently there is only 'blame_incremental' for 'blame') - * - * @globals jsExceptionsRe - */ -function fixLinks() { - var allLinks = document.getElementsByTagName("a") || document.links; - for (var i = 0, len = allLinks.length; i < len; i++) { - var link = allLinks[i]; - if (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/; - link.href += - (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1'; - } - } -} - - -/* ============================================================ */ - -/* - * This code uses DOM methods instead of (nonstandard) innerHTML - * to modify page. - * - * innerHTML is non-standard IE extension, though supported by most - * browsers; however Firefox up to version 1.5 didn't implement it in - * a strict mode (application/xml+xhtml mimetype). - * - * Also my simple benchmarks show that using elem.firstChild.data = - * 'content' is slightly faster than elem.innerHTML = 'content'. It - * is however more fragile (text element fragment must exists), and - * less feature-rich (we cannot add HTML). - * - * Note that DOM 2 HTML is preferred over generic DOM 2 Core; the - * equivalent using DOM 2 Core is usually shown in comments. - */ - - -/* ============================================================ */ -/* generic utility functions */ - - -/** - * pad number N with nonbreakable spaces on the left, to WIDTH characters - * example: padLeftStr(12, 3, '\u00A0') == '\u00A012' - * ('\u00A0' is nonbreakable space) - * - * @param {Number|String} input: number to pad - * @param {Number} width: visible width of output - * @param {String} str: string to prefix to string, e.g. '\u00A0' - * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR - */ -function padLeftStr(input, width, str) { - var prefix = ''; - - width -= input.toString().length; - while (width > 0) { - prefix += str; - width--; - } - return prefix + input; -} - -/** - * Pad INPUT on the left to SIZE width, using given padding character CH, - * for example padLeft('a', 3, '_') is '__a'. - * - * @param {String} input: input value converted to string. - * @param {Number} width: desired length of output. - * @param {String} ch: single character to prefix to string. - * - * @returns {String} Modified string, at least SIZE length. - */ -function padLeft(input, width, ch) { - var s = input + ""; - while (s.length < width) { - s = ch + s; - } - return s; -} - -/** - * Create XMLHttpRequest object in cross-browser way - * @returns XMLHttpRequest object, or null - */ -function createRequestObject() { - try { - return new XMLHttpRequest(); - } catch (e) {} - try { - return window.createRequest(); - } catch (e) {} - try { - return new ActiveXObject("Msxml2.XMLHTTP"); - } catch (e) {} - try { - return new ActiveXObject("Microsoft.XMLHTTP"); - } catch (e) {} - - return null; -} - - -/* ============================================================ */ -/* utility/helper functions (and variables) */ - -var xhr; // XMLHttpRequest object -var projectUrl; // partial query + separator ('?' or ';') - -// 'commits' is an associative map. It maps SHA1s to Commit objects. -var commits = {}; - -/** - * constructor for Commit objects, used in 'blame' - * @class Represents a blamed commit - * @param {String} sha1: SHA-1 identifier of a commit - */ -function Commit(sha1) { - if (this instanceof Commit) { - this.sha1 = sha1; - this.nprevious = 0; /* number of 'previous', effective parents */ - } else { - return new Commit(sha1); - } -} - -/* ............................................................ */ -/* progress info, timing, error reporting */ - -var blamedLines = 0; -var totalLines = '???'; -var div_progress_bar; -var div_progress_info; - -/** - * Detects how many lines does a blamed file have, - * This information is used in progress info - * - * @returns {Number|String} Number of lines in file, or string '...' - */ -function countLines() { - var table = - document.getElementById('blame_table') || - document.getElementsByTagName('table')[0]; - - if (table) { - return table.getElementsByTagName('tr').length - 1; // for header - } else { - return '...'; - } -} - -/** - * update progress info and length (width) of progress bar - * - * @globals div_progress_info, div_progress_bar, blamedLines, totalLines - */ -function updateProgressInfo() { - if (!div_progress_info) { - div_progress_info = document.getElementById('progress_info'); - } - if (!div_progress_bar) { - div_progress_bar = document.getElementById('progress_bar'); - } - if (!div_progress_info && !div_progress_bar) { - return; - } - - var percentage = Math.floor(100.0*blamedLines/totalLines); - - if (div_progress_info) { - div_progress_info.firstChild.data = blamedLines + ' / ' + totalLines + - ' (' + padLeftStr(percentage, 3, '\u00A0') + '%)'; - } - - if (div_progress_bar) { - //div_progress_bar.setAttribute('style', 'width: '+percentage+'%;'); - div_progress_bar.style.width = percentage + '%'; - } -} - - -var t_interval_server = ''; -var cmds_server = ''; -var t0 = new Date(); - -/** - * write how much it took to generate data, and to run script - * - * @globals t0, t_interval_server, cmds_server - */ -function writeTimeInterval() { - var info_time = document.getElementById('generating_time'); - if (!info_time || !t_interval_server) { - return; - } - var t1 = new Date(); - info_time.firstChild.data += ' + (' + - t_interval_server + ' sec server blame_data / ' + - (t1.getTime() - t0.getTime())/1000 + ' sec client JavaScript)'; - - var info_cmds = document.getElementById('generating_cmd'); - if (!info_time || !cmds_server) { - return; - } - info_cmds.firstChild.data += ' + ' + cmds_server; -} - -/** - * show an error message alert to user within page (in prohress info area) - * @param {String} str: plain text error message (no HTML) - * - * @globals div_progress_info - */ -function errorInfo(str) { - if (!div_progress_info) { - div_progress_info = document.getElementById('progress_info'); - } - if (div_progress_info) { - div_progress_info.className = 'error'; - div_progress_info.firstChild.data = str; - } -} - -/* ............................................................ */ -/* coloring rows during blame_data (git blame --incremental) run */ - -/** - * used to extract N from 'colorN', where N is a number, - * @constant - */ -var colorRe = /\bcolor([0-9]*)\b/; - -/** - * return N if , otherwise return null - * (some browsers require CSS class names to begin with letter) - * - * @param {HTMLElement} tr: table row element to check - * @param {String} tr.className: 'class' attribute of tr element - * @returns {Number|null} N if tr.className == 'colorN', otherwise null - * - * @globals colorRe - */ -function getColorNo(tr) { - if (!tr) { - return null; - } - var className = tr.className; - if (className) { - var match = colorRe.exec(className); - if (match) { - return parseInt(match[1], 10); - } - } - return null; -} - -var colorsFreq = [0, 0, 0]; -/** - * return one of given possible colors (curently least used one) - * example: chooseColorNoFrom(2, 3) returns 2 or 3 - * - * @param {Number[]} arguments: one or more numbers - * assumes that 1 <= arguments[i] <= colorsFreq.length - * @returns {Number} Least used color number from arguments - * @globals colorsFreq - */ -function chooseColorNoFrom() { - // choose the color which is least used - var colorNo = arguments[0]; - for (var i = 1; i < arguments.length; i++) { - if (colorsFreq[arguments[i]-1] < colorsFreq[colorNo-1]) { - colorNo = arguments[i]; - } - } - colorsFreq[colorNo-1]++; - return colorNo; -} - -/** - * given two neigbour elements, find color which would be different - * from color of both of neighbours; used to 3-color blame table - * - * @param {HTMLElement} tr_prev - * @param {HTMLElement} tr_next - * @returns {Number} color number N such that - * colorN != tr_prev.className && colorN != tr_next.className - */ -function findColorNo(tr_prev, tr_next) { - var color_prev = getColorNo(tr_prev); - var color_next = getColorNo(tr_next); - - - // neither of neighbours has color set - // THEN we can use any of 3 possible colors - if (!color_prev && !color_next) { - return chooseColorNoFrom(1,2,3); - } - - // either both neighbours have the same color, - // or only one of neighbours have color set - // THEN we can use any color except given - var color; - if (color_prev === color_next) { - color = color_prev; // = color_next; - } else if (!color_prev) { - color = color_next; - } else if (!color_next) { - color = color_prev; - } - if (color) { - return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1); - } - - // neighbours have different colors - // THEN there is only one color left - return (3 - ((color_prev + color_next) % 3)); -} - -/* ............................................................ */ -/* coloring rows like 'blame' after 'blame_data' finishes */ - -/** - * returns true if given row element (tr) is first in commit group - * to be used only after 'blame_data' finishes (after processing) - * - * @param {HTMLElement} tr: table row - * @returns {Boolean} true if TR is first in commit group - */ -function isStartOfGroup(tr) { - return tr.firstChild.className === 'sha1'; -} - -/** - * change colors to use zebra coloring (2 colors) instead of 3 colors - * concatenate neighbour commit groups belonging to the same commit - * - * @globals colorRe - */ -function fixColorsAndGroups() { - var colorClasses = ['light', 'dark']; - var linenum = 1; - var tr, prev_group; - var colorClass = 0; - var table = - document.getElementById('blame_table') || - document.getElementsByTagName('table')[0]; - - while ((tr = document.getElementById('l'+linenum))) { - // index origin is 0, which is table header; start from 1 - //while ((tr = table.rows[linenum])) { // <- it is slower - if (isStartOfGroup(tr, linenum, document)) { - if (prev_group && - prev_group.firstChild.firstChild.href === - tr.firstChild.firstChild.href) { - // we have to concatenate groups - var prev_rows = prev_group.firstChild.rowSpan || 1; - var curr_rows = tr.firstChild.rowSpan || 1; - prev_group.firstChild.rowSpan = prev_rows + curr_rows; - //tr.removeChild(tr.firstChild); - tr.deleteCell(0); // DOM2 HTML way - } else { - colorClass = (colorClass + 1) % 2; - prev_group = tr; - } - } - var tr_class = tr.className; - tr.className = tr_class.replace(colorRe, colorClasses[colorClass]); - linenum++; - } -} - -/* ............................................................ */ -/* time and data */ - -/** - * used to extract hours and minutes from timezone info, e.g '-0900' - * @constant - */ -var tzRe = /^([+-][0-9][0-9])([0-9][0-9])$/; - -/** - * return date in local time formatted in iso-8601 like format - * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200' - * - * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' - * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' - * @returns {String} date in local time in iso-8601 like format - * - * @globals tzRe - */ -function formatDateISOLocal(epoch, timezoneInfo) { - var match = tzRe.exec(timezoneInfo); - // date corrected by timezone - var localDate = new Date(1000 * (epoch + - (parseInt(match[1],10)*3600 + parseInt(match[2],10)*60))); - var localDateStr = // e.g. '2005-08-07' - localDate.getUTCFullYear() + '-' + - padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' + - padLeft(localDate.getUTCDate(), 2, '0'); - var localTimeStr = // e.g. '21:49:46' - padLeft(localDate.getUTCHours(), 2, '0') + ':' + - padLeft(localDate.getUTCMinutes(), 2, '0') + ':' + - padLeft(localDate.getUTCSeconds(), 2, '0'); - - return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo; -} - -/* ............................................................ */ -/* unquoting/unescaping filenames */ - -/**#@+ - * @constant - */ -var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g; -var octEscRe = /^[0-7]{1,3}$/; -var maybeQuotedRe = /^\"(.*)\"$/; -/**#@-*/ - -/** - * unquote maybe git-quoted filename - * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a a' - * - * @param {String} str: git-quoted string - * @returns {String} Unquoted and unescaped string - * - * @globals escCodeRe, octEscRe, maybeQuotedRe - */ -function unquote(str) { - function unq(seq) { - var es = { - // character escape codes, aka escape sequences (from C) - // replacements are to some extent JavaScript specific - t: "\t", // tab (HT, TAB) - n: "\n", // newline (NL) - r: "\r", // return (CR) - f: "\f", // form feed (FF) - b: "\b", // backspace (BS) - a: "\x07", // alarm (bell) (BEL) - e: "\x1B", // escape (ESC) - v: "\v" // vertical tab (VT) - }; - - if (seq.search(octEscRe) !== -1) { - // octal char sequence - return String.fromCharCode(parseInt(seq, 8)); - } else if (seq in es) { - // C escape sequence, aka character escape code - return es[seq]; - } - // quoted ordinary character - return seq; - } - - var match = str.match(maybeQuotedRe); - if (match) { - str = match[1]; - // perhaps str = eval('"'+str+'"'); would be enough? - str = str.replace(escCodeRe, - function (substr, p1, offset, s) { return unq(p1); }); - } - return str; -} - -/* ============================================================ */ -/* main part: parsing response */ - -/** - * Function called for each blame entry, as soon as it finishes. - * It updates page via DOM manipulation, adding sha1 info, etc. - * - * @param {Commit} commit: blamed commit - * @param {Object} group: object representing group of lines, - * which blame the same commit (blame entry) - * - * @globals blamedLines - */ -function handleLine(commit, group) { - /* - This is the structure of the HTML fragment we are working - with: - - - - 123 - # times (my ext3 doesn't). - - */ - - var resline = group.resline; - - // format date and time string only once per commit - if (!commit.info) { - /* e.g. 'Kay Sievers, 2005-08-07 21:49:46 +0200' */ - commit.info = commit.author + ', ' + - formatDateISOLocal(commit.authorTime, commit.authorTimezone); - } - - // color depends on group of lines, not only on blamed commit - var colorNo = findColorNo( - document.getElementById('l'+(resline-1)), - document.getElementById('l'+(resline+group.numlines)) - ); - - // loop over lines in commit group - for (var i = 0; i < group.numlines; i++, resline++) { - var tr = document.getElementById('l'+resline); - if (!tr) { - break; - } - /* - - - 123 - # times (my ext3 doesn't). - - */ - var td_sha1 = tr.firstChild; - var a_sha1 = td_sha1.firstChild; - var a_linenr = td_sha1.nextSibling.firstChild; - - /* */ - var tr_class = ''; - if (colorNo !== null) { - tr_class = 'color'+colorNo; - } - if (commit.boundary) { - tr_class += ' boundary'; - } - if (commit.nprevious === 0) { - tr_class += ' no-previous'; - } else if (commit.nprevious > 1) { - tr_class += ' multiple-previous'; - } - tr.className = tr_class; - - /* ? */ - if (i === 0) { - td_sha1.title = commit.info; - td_sha1.rowSpan = group.numlines; - - a_sha1.href = projectUrl + 'a=commit;h=' + commit.sha1; - if (a_sha1.firstChild) { - a_sha1.firstChild.data = commit.sha1.substr(0, 8); - } else { - a_sha1.appendChild( - document.createTextNode(commit.sha1.substr(0, 8))); - } - if (group.numlines >= 2) { - var fragment = document.createDocumentFragment(); - var br = document.createElement("br"); - var match = commit.author.match(/\b([A-Z])\B/g); - if (match) { - var text = document.createTextNode( - match.join('')); - } - if (br && text) { - var elem = fragment || td_sha1; - elem.appendChild(br); - elem.appendChild(text); - if (fragment) { - td_sha1.appendChild(fragment); - } - } - } - } else { - //tr.removeChild(td_sha1); // DOM2 Core way - tr.deleteCell(0); // DOM2 HTML way - } - - /* 123 */ - var linenr_commit = - ('previous' in commit ? commit.previous : commit.sha1); - var linenr_filename = - ('file_parent' in commit ? commit.file_parent : commit.filename); - a_linenr.href = projectUrl + 'a=blame_incremental' + - ';hb=' + linenr_commit + - ';f=' + encodeURIComponent(linenr_filename) + - '#l' + (group.srcline + i); - - blamedLines++; - - //updateProgressInfo(); - } -} - -// ---------------------------------------------------------------------- - -var inProgress = false; // are we processing response - -/**#@+ - * @constant - */ -var sha1Re = /^([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/; -var infoRe = /^([a-z-]+) ?(.*)/; -var endRe = /^END ?([^ ]*) ?(.*)/; -/**@-*/ - -var curCommit = new Commit(); -var curGroup = {}; - -var pollTimer = null; - -/** - * Parse output from 'git blame --incremental [...]', received via - * XMLHttpRequest from server (blamedataUrl), and call handleLine - * (which updates page) as soon as blame entry is completed. - * - * @param {String[]} lines: new complete lines from blamedata server - * - * @globals commits, curCommit, curGroup, t_interval_server, cmds_server - * @globals sha1Re, infoRe, endRe - */ -function processBlameLines(lines) { - var match; - - for (var i = 0, len = lines.length; i < len; i++) { - - if ((match = sha1Re.exec(lines[i]))) { - var sha1 = match[1]; - var srcline = parseInt(match[2], 10); - var resline = parseInt(match[3], 10); - var numlines = parseInt(match[4], 10); - - var c = commits[sha1]; - if (!c) { - c = new Commit(sha1); - commits[sha1] = c; - } - curCommit = c; - - curGroup.srcline = srcline; - curGroup.resline = resline; - curGroup.numlines = numlines; - - } else if ((match = infoRe.exec(lines[i]))) { - var info = match[1]; - var data = match[2]; - switch (info) { - case 'filename': - curCommit.filename = unquote(data); - // 'filename' information terminates the entry - handleLine(curCommit, curGroup); - updateProgressInfo(); - break; - case 'author': - curCommit.author = data; - break; - case 'author-time': - curCommit.authorTime = parseInt(data, 10); - break; - case 'author-tz': - curCommit.authorTimezone = data; - break; - case 'previous': - curCommit.nprevious++; - // store only first 'previous' header - if (!'previous' in curCommit) { - var parts = data.split(' ', 2); - curCommit.previous = parts[0]; - curCommit.file_parent = unquote(parts[1]); - } - break; - case 'boundary': - curCommit.boundary = true; - break; - } // end switch - - } else if ((match = endRe.exec(lines[i]))) { - t_interval_server = match[1]; - cmds_server = match[2]; - - } else if (lines[i] !== '') { - // malformed line - - } // end if (match) - - } // end for (lines) -} - -/** - * Process new data and return pointer to end of processed part - * - * @param {String} unprocessed: new data (from nextReadPos) - * @param {Number} nextReadPos: end of last processed data - * @return {Number} end of processed data (new value for nextReadPos) - */ -function processData(unprocessed, nextReadPos) { - var lastLineEnd = unprocessed.lastIndexOf('\n'); - if (lastLineEnd !== -1) { - var lines = unprocessed.substring(0, lastLineEnd).split('\n'); - nextReadPos += lastLineEnd + 1 /* 1 == '\n'.length */; - - processBlameLines(lines); - } // end if - - return nextReadPos; -} - -/** - * Handle XMLHttpRequest errors - * - * @param {XMLHttpRequest} xhr: XMLHttpRequest object - * - * @globals pollTimer, commits, inProgress - */ -function handleError(xhr) { - errorInfo('Server error: ' + - xhr.status + ' - ' + (xhr.statusText || 'Error contacting server')); - - clearInterval(pollTimer); - commits = {}; // free memory - - inProgress = false; -} - -/** - * Called after XMLHttpRequest finishes (loads) - * - * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused) - * - * @globals pollTimer, commits, inProgress - */ -function responseLoaded(xhr) { - clearInterval(pollTimer); - - fixColorsAndGroups(); - writeTimeInterval(); - commits = {}; // free memory - - inProgress = false; -} - -/** - * handler for XMLHttpRequest onreadystatechange event - * @see startBlame - * - * @globals xhr, inProgress - */ -function handleResponse() { - - /* - * xhr.readyState - * - * Value Constant (W3C) Description - * ------------------------------------------------------------------- - * 0 UNSENT open() has not been called yet. - * 1 OPENED send() has not been called yet. - * 2 HEADERS_RECEIVED send() has been called, and headers - * and status are available. - * 3 LOADING Downloading; responseText holds partial data. - * 4 DONE The operation is complete. - */ - - if (xhr.readyState !== 4 && xhr.readyState !== 3) { - return; - } - - // the server returned error - // try ... catch block is to work around bug in IE8 - try { - if (xhr.readyState === 3 && xhr.status !== 200) { - return; - } - } catch (e) { - return; - } - if (xhr.readyState === 4 && xhr.status !== 200) { - handleError(xhr); - return; - } - - // In konqueror xhr.responseText is sometimes null here... - if (xhr.responseText === null) { - return; - } - - // in case we were called before finished processing - if (inProgress) { - return; - } else { - inProgress = true; - } - - // extract new whole (complete) lines, and process them - while (xhr.prevDataLength !== xhr.responseText.length) { - if (xhr.readyState === 4 && - xhr.prevDataLength === xhr.responseText.length) { - break; - } - - xhr.prevDataLength = xhr.responseText.length; - var unprocessed = xhr.responseText.substring(xhr.nextReadPos); - xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos); - } // end while - - // did we finish work? - if (xhr.readyState === 4 && - xhr.prevDataLength === xhr.responseText.length) { - responseLoaded(xhr); - } - - inProgress = false; -} - -// ============================================================ -// ------------------------------------------------------------ - -/** - * Incrementally update line data in blame_incremental view in gitweb. - * - * @param {String} blamedataUrl: URL to server script generating blame data. - * @param {String} bUrl: partial URL to project, used to generate links. - * - * Called from 'blame_incremental' view after loading table with - * file contents, a base for blame view. - * - * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer -*/ -function startBlame(blamedataUrl, bUrl) { - - xhr = createRequestObject(); - if (!xhr) { - errorInfo('ERROR: XMLHttpRequest not supported'); - return; - } - - t0 = new Date(); - projectUrl = bUrl + (bUrl.indexOf('?') === -1 ? '?' : ';'); - if ((div_progress_bar = document.getElementById('progress_bar'))) { - //div_progress_bar.setAttribute('style', 'width: 100%;'); - div_progress_bar.style.cssText = 'width: 100%;'; - } - totalLines = countLines(); - updateProgressInfo(); - - /* add extra properties to xhr object to help processing response */ - xhr.prevDataLength = -1; // used to detect if we have new data - xhr.nextReadPos = 0; // where unread part of response starts - - xhr.onreadystatechange = handleResponse; - //xhr.onreadystatechange = function () { handleResponse(xhr); }; - - xhr.open('GET', blamedataUrl); - xhr.setRequestHeader('Accept', 'text/plain'); - xhr.send(null); - - // not all browsers call onreadystatechange event on each server flush - // poll response using timer every second to handle this issue - pollTimer = setInterval(xhr.onreadystatechange, 1000); -} - -// end of gitweb.js diff --git a/gitweb/static/git-favicon.png b/gitweb/static/git-favicon.png new file mode 100644 index 0000000000..aae35a70e7 Binary files /dev/null and b/gitweb/static/git-favicon.png differ diff --git a/gitweb/static/git-logo.png b/gitweb/static/git-logo.png new file mode 100644 index 0000000000..f4ede2e944 Binary files /dev/null and b/gitweb/static/git-logo.png differ diff --git a/gitweb/static/gitweb.css b/gitweb/static/gitweb.css new file mode 100644 index 0000000000..50067f2e0d --- /dev/null +++ b/gitweb/static/gitweb.css @@ -0,0 +1,574 @@ +body { + font-family: sans-serif; + font-size: small; + border: solid #d9d8d1; + border-width: 1px; + margin: 10px; + background-color: #ffffff; + color: #000000; +} + +a { + color: #0000cc; +} + +a:hover, a:visited, a:active { + color: #880000; +} + +span.cntrl { + border: dashed #aaaaaa; + border-width: 1px; + padding: 0px 2px 0px 2px; + margin: 0px 2px 0px 2px; +} + +img.logo { + float: right; + border-width: 0px; +} + +img.avatar { + vertical-align: middle; +} + +a.list img.avatar { + border-style: none; +} + +div.page_header { + height: 25px; + padding: 8px; + font-size: 150%; + font-weight: bold; + background-color: #d9d8d1; +} + +div.page_header a:visited, a.header { + color: #0000cc; +} + +div.page_header a:hover { + color: #880000; +} + +div.page_nav { + padding: 8px; +} + +div.page_nav a:visited { + color: #0000cc; +} + +div.page_path { + padding: 8px; + font-weight: bold; + border: solid #d9d8d1; + border-width: 0px 0px 1px; +} + +div.page_footer { + height: 17px; + padding: 4px 8px; + background-color: #d9d8d1; +} + +div.page_footer_text { + float: left; + color: #555555; + font-style: italic; +} + +div#generating_info { + margin: 4px; + font-size: smaller; + text-align: center; + color: #505050; +} + +div.page_body { + padding: 8px; + font-family: monospace; +} + +div.title, a.title { + display: block; + padding: 6px 8px; + font-weight: bold; + background-color: #edece6; + text-decoration: none; + color: #000000; +} + +div.readme { + padding: 8px; +} + +a.title:hover { + background-color: #d9d8d1; +} + +div.title_text { + padding: 6px 0px; + border: solid #d9d8d1; + border-width: 0px 0px 1px; + font-family: monospace; +} + +div.log_body { + padding: 8px 8px 8px 150px; +} + +span.age { + position: relative; + float: left; + width: 142px; + font-style: italic; +} + +span.signoff { + color: #888888; +} + +div.log_link { + padding: 0px 8px; + font-size: 70%; + font-family: sans-serif; + font-style: normal; + position: relative; + float: left; + width: 136px; +} + +div.list_head { + padding: 6px 8px 4px; + border: solid #d9d8d1; + border-width: 1px 0px 0px; + font-style: italic; +} + +.author_date, .author { + font-style: italic; +} + +div.author_date { + padding: 8px; + border: solid #d9d8d1; + border-width: 0px 0px 1px 0px; +} + +a.list { + text-decoration: none; + color: #000000; +} + +a.subject, a.name { + font-weight: bold; +} + +table.tags a.subject { + font-weight: normal; +} + +a.list:hover { + text-decoration: underline; + color: #880000; +} + +a.text { + text-decoration: none; + color: #0000cc; +} + +a.text:visited { + text-decoration: none; + color: #880000; +} + +a.text:hover { + text-decoration: underline; + color: #880000; +} + +table { + padding: 8px 4px; + border-spacing: 0; +} + +table.diff_tree { + font-family: monospace; +} + +table.combined.diff_tree th { + text-align: center; +} + +table.combined.diff_tree td { + padding-right: 24px; +} + +table.combined.diff_tree th.link, +table.combined.diff_tree td.link { + padding: 0px 2px; +} + +table.combined.diff_tree td.nochange a { + color: #6666ff; +} + +table.combined.diff_tree td.nochange a:hover, +table.combined.diff_tree td.nochange a:visited { + color: #d06666; +} + +table.blame { + border-collapse: collapse; +} + +table.blame td { + padding: 0px 5px; + font-size: 100%; + vertical-align: top; +} + +th { + padding: 2px 5px; + font-size: 100%; + text-align: left; +} + +/* do not change row style on hover for 'blame' view */ +tr.light, +table.blame .light:hover { + background-color: #ffffff; +} + +tr.dark, +table.blame .dark:hover { + background-color: #f6f6f0; +} + +/* currently both use the same, but it can change */ +tr.light:hover, +tr.dark:hover { + background-color: #edece6; +} + +/* boundary commits in 'blame' view */ +/* and commits without "previous" */ +tr.boundary td.sha1, +tr.no-previous td.linenr { + font-weight: bold; +} + +/* for 'blame_incremental', during processing */ +tr.color1 { background-color: #f6fff6; } +tr.color2 { background-color: #f6f6ff; } +tr.color3 { background-color: #fff6f6; } + +td { + padding: 2px 5px; + font-size: 100%; + vertical-align: top; +} + +td.link, td.selflink { + padding: 2px 5px; + font-family: sans-serif; + font-size: 70%; +} + +td.selflink { + padding-right: 0px; +} + +td.sha1 { + font-family: monospace; +} + +.error { + color: red; + background-color: yellow; +} + +td.current_head { + text-decoration: underline; +} + +table.diff_tree span.file_status.new { + color: #008000; +} + +table.diff_tree span.file_status.deleted { + color: #c00000; +} + +table.diff_tree span.file_status.moved, +table.diff_tree span.file_status.mode_chnge { + color: #777777; +} + +table.diff_tree span.file_status.copied { + color: #70a070; +} + +/* noage: "No commits" */ +table.project_list td.noage { + color: #808080; + font-style: italic; +} + +/* age2: 60*60*24*2 <= age */ +table.project_list td.age2, table.blame td.age2 { + font-style: italic; +} + +/* age1: 60*60*2 <= age < 60*60*24*2 */ +table.project_list td.age1 { + color: #009900; + font-style: italic; +} + +table.blame td.age1 { + color: #009900; + background: transparent; +} + +/* age0: age < 60*60*2 */ +table.project_list td.age0 { + color: #009900; + font-style: italic; + font-weight: bold; +} + +table.blame td.age0 { + color: #009900; + background: transparent; + font-weight: bold; +} + +td.pre, div.pre, div.diff { + font-family: monospace; + font-size: 12px; + white-space: pre; +} + +td.mode { + font-family: monospace; +} + +/* progress of blame_interactive */ +div#progress_bar { + height: 2px; + margin-bottom: -2px; + background-color: #d8d9d0; +} +div#progress_info { + float: right; + text-align: right; +} + +/* format of (optional) objects size in 'tree' view */ +td.size { + font-family: monospace; + text-align: right; +} + +/* styling of diffs (patchsets): commitdiff and blobdiff views */ +div.diff.header, +div.diff.extended_header { + white-space: normal; +} + +div.diff.header { + font-weight: bold; + + background-color: #edece6; + + margin-top: 4px; + padding: 4px 0px 2px 0px; + border: solid #d9d8d1; + border-width: 1px 0px 1px 0px; +} + +div.diff.header a.path { + text-decoration: underline; +} + +div.diff.extended_header, +div.diff.extended_header a.path, +div.diff.extended_header a.hash { + color: #777777; +} + +div.diff.extended_header .info { + color: #b0b0b0; +} + +div.diff.extended_header { + background-color: #f6f5ee; + padding: 2px 0px 2px 0px; +} + +div.diff a.list, +div.diff a.path, +div.diff a.hash { + text-decoration: none; +} + +div.diff a.list:hover, +div.diff a.path:hover, +div.diff a.hash:hover { + text-decoration: underline; +} + +div.diff.to_file a.path, +div.diff.to_file { + color: #007000; +} + +div.diff.add { + color: #008800; +} + +div.diff.from_file a.path, +div.diff.from_file { + color: #aa0000; +} + +div.diff.rem { + color: #cc0000; +} + +div.diff.chunk_header a, +div.diff.chunk_header { + color: #990099; +} + +div.diff.chunk_header { + border: dotted #ffe0ff; + border-width: 1px 0px 0px 0px; + margin-top: 2px; +} + +div.diff.chunk_header span.chunk_info { + background-color: #ffeeff; +} + +div.diff.chunk_header span.section { + color: #aa22aa; +} + +div.diff.incomplete { + color: #cccccc; +} + +div.diff.nodifferences { + font-weight: bold; + color: #600000; +} + +div.index_include { + border: solid #d9d8d1; + border-width: 0px 0px 1px; + padding: 12px 8px; +} + +div.search { + font-size: 100%; + font-weight: normal; + margin: 4px 8px; + float: right; + top: 56px; + right: 12px +} + +p.projsearch { + text-align: center; +} + +td.linenr { + text-align: right; +} + +a.linenr { + color: #999999; + text-decoration: none +} + +a.rss_logo { + float: right; + padding: 3px 0px; + width: 35px; + line-height: 10px; + border: 1px solid; + border-color: #fcc7a5 #7d3302 #3e1a01 #ff954e; + color: #ffffff; + background-color: #ff6600; + font-weight: bold; + font-family: sans-serif; + font-size: 70%; + text-align: center; + text-decoration: none; +} + +a.rss_logo:hover { + background-color: #ee5500; +} + +a.rss_logo.generic { + background-color: #ff8800; +} + +a.rss_logo.generic:hover { + background-color: #ee7700; +} + +span.refs span { + padding: 0px 4px; + font-size: 70%; + font-weight: normal; + border: 1px solid; + background-color: #ffaaff; + border-color: #ffccff #ff00ee #ff00ee #ffccff; +} + +span.refs span a { + text-decoration: none; + color: inherit; +} + +span.refs span a:hover { + text-decoration: underline; +} + +span.refs span.indirect { + font-style: italic; +} + +span.refs span.ref { + background-color: #aaaaff; + border-color: #ccccff #0033cc #0033cc #ccccff; +} + +span.refs span.tag { + background-color: #ffffaa; + border-color: #ffffcc #ffee00 #ffee00 #ffffcc; +} + +span.refs span.head { + background-color: #aaffaa; + border-color: #ccffcc #00cc33 #00cc33 #ccffcc; +} + +span.atnight { + color: #cc0000; +} + +span.match { + color: #e00000; +} + +div.binary { + font-style: italic; +} diff --git a/gitweb/static/gitweb.js b/gitweb/static/gitweb.js new file mode 100644 index 0000000000..9c66928c4a --- /dev/null +++ b/gitweb/static/gitweb.js @@ -0,0 +1,875 @@ +// Copyright (C) 2007, Fredrik Kuivinen +// 2007, Petr Baudis +// 2008-2009, Jakub Narebski + +/** + * @fileOverview JavaScript code for gitweb (git web interface). + * @license GPLv2 or later + */ + +/* ============================================================ */ +/* functions for generic gitweb actions and views */ + +/** + * used to check if link has 'js' query parameter already (at end), + * and other reasons to not add 'js=1' param at the end of link + * @constant + */ +var jsExceptionsRe = /[;?]js=[01]$/; + +/** + * Add '?js=1' or ';js=1' to the end of every link in the document + * that doesn't have 'js' query parameter set already. + * + * Links with 'js=1' lead to JavaScript version of given action, if it + * exists (currently there is only 'blame_incremental' for 'blame') + * + * @globals jsExceptionsRe + */ +function fixLinks() { + var allLinks = document.getElementsByTagName("a") || document.links; + for (var i = 0, len = allLinks.length; i < len; i++) { + var link = allLinks[i]; + if (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/; + link.href += + (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1'; + } + } +} + + +/* ============================================================ */ + +/* + * This code uses DOM methods instead of (nonstandard) innerHTML + * to modify page. + * + * innerHTML is non-standard IE extension, though supported by most + * browsers; however Firefox up to version 1.5 didn't implement it in + * a strict mode (application/xml+xhtml mimetype). + * + * Also my simple benchmarks show that using elem.firstChild.data = + * 'content' is slightly faster than elem.innerHTML = 'content'. It + * is however more fragile (text element fragment must exists), and + * less feature-rich (we cannot add HTML). + * + * Note that DOM 2 HTML is preferred over generic DOM 2 Core; the + * equivalent using DOM 2 Core is usually shown in comments. + */ + + +/* ============================================================ */ +/* generic utility functions */ + + +/** + * pad number N with nonbreakable spaces on the left, to WIDTH characters + * example: padLeftStr(12, 3, '\u00A0') == '\u00A012' + * ('\u00A0' is nonbreakable space) + * + * @param {Number|String} input: number to pad + * @param {Number} width: visible width of output + * @param {String} str: string to prefix to string, e.g. '\u00A0' + * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR + */ +function padLeftStr(input, width, str) { + var prefix = ''; + + width -= input.toString().length; + while (width > 0) { + prefix += str; + width--; + } + return prefix + input; +} + +/** + * Pad INPUT on the left to SIZE width, using given padding character CH, + * for example padLeft('a', 3, '_') is '__a'. + * + * @param {String} input: input value converted to string. + * @param {Number} width: desired length of output. + * @param {String} ch: single character to prefix to string. + * + * @returns {String} Modified string, at least SIZE length. + */ +function padLeft(input, width, ch) { + var s = input + ""; + while (s.length < width) { + s = ch + s; + } + return s; +} + +/** + * Create XMLHttpRequest object in cross-browser way + * @returns XMLHttpRequest object, or null + */ +function createRequestObject() { + try { + return new XMLHttpRequest(); + } catch (e) {} + try { + return window.createRequest(); + } catch (e) {} + try { + return new ActiveXObject("Msxml2.XMLHTTP"); + } catch (e) {} + try { + return new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) {} + + return null; +} + + +/* ============================================================ */ +/* utility/helper functions (and variables) */ + +var xhr; // XMLHttpRequest object +var projectUrl; // partial query + separator ('?' or ';') + +// 'commits' is an associative map. It maps SHA1s to Commit objects. +var commits = {}; + +/** + * constructor for Commit objects, used in 'blame' + * @class Represents a blamed commit + * @param {String} sha1: SHA-1 identifier of a commit + */ +function Commit(sha1) { + if (this instanceof Commit) { + this.sha1 = sha1; + this.nprevious = 0; /* number of 'previous', effective parents */ + } else { + return new Commit(sha1); + } +} + +/* ............................................................ */ +/* progress info, timing, error reporting */ + +var blamedLines = 0; +var totalLines = '???'; +var div_progress_bar; +var div_progress_info; + +/** + * Detects how many lines does a blamed file have, + * This information is used in progress info + * + * @returns {Number|String} Number of lines in file, or string '...' + */ +function countLines() { + var table = + document.getElementById('blame_table') || + document.getElementsByTagName('table')[0]; + + if (table) { + return table.getElementsByTagName('tr').length - 1; // for header + } else { + return '...'; + } +} + +/** + * update progress info and length (width) of progress bar + * + * @globals div_progress_info, div_progress_bar, blamedLines, totalLines + */ +function updateProgressInfo() { + if (!div_progress_info) { + div_progress_info = document.getElementById('progress_info'); + } + if (!div_progress_bar) { + div_progress_bar = document.getElementById('progress_bar'); + } + if (!div_progress_info && !div_progress_bar) { + return; + } + + var percentage = Math.floor(100.0*blamedLines/totalLines); + + if (div_progress_info) { + div_progress_info.firstChild.data = blamedLines + ' / ' + totalLines + + ' (' + padLeftStr(percentage, 3, '\u00A0') + '%)'; + } + + if (div_progress_bar) { + //div_progress_bar.setAttribute('style', 'width: '+percentage+'%;'); + div_progress_bar.style.width = percentage + '%'; + } +} + + +var t_interval_server = ''; +var cmds_server = ''; +var t0 = new Date(); + +/** + * write how much it took to generate data, and to run script + * + * @globals t0, t_interval_server, cmds_server + */ +function writeTimeInterval() { + var info_time = document.getElementById('generating_time'); + if (!info_time || !t_interval_server) { + return; + } + var t1 = new Date(); + info_time.firstChild.data += ' + (' + + t_interval_server + ' sec server blame_data / ' + + (t1.getTime() - t0.getTime())/1000 + ' sec client JavaScript)'; + + var info_cmds = document.getElementById('generating_cmd'); + if (!info_time || !cmds_server) { + return; + } + info_cmds.firstChild.data += ' + ' + cmds_server; +} + +/** + * show an error message alert to user within page (in prohress info area) + * @param {String} str: plain text error message (no HTML) + * + * @globals div_progress_info + */ +function errorInfo(str) { + if (!div_progress_info) { + div_progress_info = document.getElementById('progress_info'); + } + if (div_progress_info) { + div_progress_info.className = 'error'; + div_progress_info.firstChild.data = str; + } +} + +/* ............................................................ */ +/* coloring rows during blame_data (git blame --incremental) run */ + +/** + * used to extract N from 'colorN', where N is a number, + * @constant + */ +var colorRe = /\bcolor([0-9]*)\b/; + +/** + * return N if , otherwise return null + * (some browsers require CSS class names to begin with letter) + * + * @param {HTMLElement} tr: table row element to check + * @param {String} tr.className: 'class' attribute of tr element + * @returns {Number|null} N if tr.className == 'colorN', otherwise null + * + * @globals colorRe + */ +function getColorNo(tr) { + if (!tr) { + return null; + } + var className = tr.className; + if (className) { + var match = colorRe.exec(className); + if (match) { + return parseInt(match[1], 10); + } + } + return null; +} + +var colorsFreq = [0, 0, 0]; +/** + * return one of given possible colors (curently least used one) + * example: chooseColorNoFrom(2, 3) returns 2 or 3 + * + * @param {Number[]} arguments: one or more numbers + * assumes that 1 <= arguments[i] <= colorsFreq.length + * @returns {Number} Least used color number from arguments + * @globals colorsFreq + */ +function chooseColorNoFrom() { + // choose the color which is least used + var colorNo = arguments[0]; + for (var i = 1; i < arguments.length; i++) { + if (colorsFreq[arguments[i]-1] < colorsFreq[colorNo-1]) { + colorNo = arguments[i]; + } + } + colorsFreq[colorNo-1]++; + return colorNo; +} + +/** + * given two neigbour elements, find color which would be different + * from color of both of neighbours; used to 3-color blame table + * + * @param {HTMLElement} tr_prev + * @param {HTMLElement} tr_next + * @returns {Number} color number N such that + * colorN != tr_prev.className && colorN != tr_next.className + */ +function findColorNo(tr_prev, tr_next) { + var color_prev = getColorNo(tr_prev); + var color_next = getColorNo(tr_next); + + + // neither of neighbours has color set + // THEN we can use any of 3 possible colors + if (!color_prev && !color_next) { + return chooseColorNoFrom(1,2,3); + } + + // either both neighbours have the same color, + // or only one of neighbours have color set + // THEN we can use any color except given + var color; + if (color_prev === color_next) { + color = color_prev; // = color_next; + } else if (!color_prev) { + color = color_next; + } else if (!color_next) { + color = color_prev; + } + if (color) { + return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1); + } + + // neighbours have different colors + // THEN there is only one color left + return (3 - ((color_prev + color_next) % 3)); +} + +/* ............................................................ */ +/* coloring rows like 'blame' after 'blame_data' finishes */ + +/** + * returns true if given row element (tr) is first in commit group + * to be used only after 'blame_data' finishes (after processing) + * + * @param {HTMLElement} tr: table row + * @returns {Boolean} true if TR is first in commit group + */ +function isStartOfGroup(tr) { + return tr.firstChild.className === 'sha1'; +} + +/** + * change colors to use zebra coloring (2 colors) instead of 3 colors + * concatenate neighbour commit groups belonging to the same commit + * + * @globals colorRe + */ +function fixColorsAndGroups() { + var colorClasses = ['light', 'dark']; + var linenum = 1; + var tr, prev_group; + var colorClass = 0; + var table = + document.getElementById('blame_table') || + document.getElementsByTagName('table')[0]; + + while ((tr = document.getElementById('l'+linenum))) { + // index origin is 0, which is table header; start from 1 + //while ((tr = table.rows[linenum])) { // <- it is slower + if (isStartOfGroup(tr, linenum, document)) { + if (prev_group && + prev_group.firstChild.firstChild.href === + tr.firstChild.firstChild.href) { + // we have to concatenate groups + var prev_rows = prev_group.firstChild.rowSpan || 1; + var curr_rows = tr.firstChild.rowSpan || 1; + prev_group.firstChild.rowSpan = prev_rows + curr_rows; + //tr.removeChild(tr.firstChild); + tr.deleteCell(0); // DOM2 HTML way + } else { + colorClass = (colorClass + 1) % 2; + prev_group = tr; + } + } + var tr_class = tr.className; + tr.className = tr_class.replace(colorRe, colorClasses[colorClass]); + linenum++; + } +} + +/* ............................................................ */ +/* time and data */ + +/** + * used to extract hours and minutes from timezone info, e.g '-0900' + * @constant + */ +var tzRe = /^([+-][0-9][0-9])([0-9][0-9])$/; + +/** + * return date in local time formatted in iso-8601 like format + * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200' + * + * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' + * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' + * @returns {String} date in local time in iso-8601 like format + * + * @globals tzRe + */ +function formatDateISOLocal(epoch, timezoneInfo) { + var match = tzRe.exec(timezoneInfo); + // date corrected by timezone + var localDate = new Date(1000 * (epoch + + (parseInt(match[1],10)*3600 + parseInt(match[2],10)*60))); + var localDateStr = // e.g. '2005-08-07' + localDate.getUTCFullYear() + '-' + + padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' + + padLeft(localDate.getUTCDate(), 2, '0'); + var localTimeStr = // e.g. '21:49:46' + padLeft(localDate.getUTCHours(), 2, '0') + ':' + + padLeft(localDate.getUTCMinutes(), 2, '0') + ':' + + padLeft(localDate.getUTCSeconds(), 2, '0'); + + return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo; +} + +/* ............................................................ */ +/* unquoting/unescaping filenames */ + +/**#@+ + * @constant + */ +var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g; +var octEscRe = /^[0-7]{1,3}$/; +var maybeQuotedRe = /^\"(.*)\"$/; +/**#@-*/ + +/** + * unquote maybe git-quoted filename + * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a a' + * + * @param {String} str: git-quoted string + * @returns {String} Unquoted and unescaped string + * + * @globals escCodeRe, octEscRe, maybeQuotedRe + */ +function unquote(str) { + function unq(seq) { + var es = { + // character escape codes, aka escape sequences (from C) + // replacements are to some extent JavaScript specific + t: "\t", // tab (HT, TAB) + n: "\n", // newline (NL) + r: "\r", // return (CR) + f: "\f", // form feed (FF) + b: "\b", // backspace (BS) + a: "\x07", // alarm (bell) (BEL) + e: "\x1B", // escape (ESC) + v: "\v" // vertical tab (VT) + }; + + if (seq.search(octEscRe) !== -1) { + // octal char sequence + return String.fromCharCode(parseInt(seq, 8)); + } else if (seq in es) { + // C escape sequence, aka character escape code + return es[seq]; + } + // quoted ordinary character + return seq; + } + + var match = str.match(maybeQuotedRe); + if (match) { + str = match[1]; + // perhaps str = eval('"'+str+'"'); would be enough? + str = str.replace(escCodeRe, + function (substr, p1, offset, s) { return unq(p1); }); + } + return str; +} + +/* ============================================================ */ +/* main part: parsing response */ + +/** + * Function called for each blame entry, as soon as it finishes. + * It updates page via DOM manipulation, adding sha1 info, etc. + * + * @param {Commit} commit: blamed commit + * @param {Object} group: object representing group of lines, + * which blame the same commit (blame entry) + * + * @globals blamedLines + */ +function handleLine(commit, group) { + /* + This is the structure of the HTML fragment we are working + with: + + + + 123 + # times (my ext3 doesn't). + + */ + + var resline = group.resline; + + // format date and time string only once per commit + if (!commit.info) { + /* e.g. 'Kay Sievers, 2005-08-07 21:49:46 +0200' */ + commit.info = commit.author + ', ' + + formatDateISOLocal(commit.authorTime, commit.authorTimezone); + } + + // color depends on group of lines, not only on blamed commit + var colorNo = findColorNo( + document.getElementById('l'+(resline-1)), + document.getElementById('l'+(resline+group.numlines)) + ); + + // loop over lines in commit group + for (var i = 0; i < group.numlines; i++, resline++) { + var tr = document.getElementById('l'+resline); + if (!tr) { + break; + } + /* + + + 123 + # times (my ext3 doesn't). + + */ + var td_sha1 = tr.firstChild; + var a_sha1 = td_sha1.firstChild; + var a_linenr = td_sha1.nextSibling.firstChild; + + /* */ + var tr_class = ''; + if (colorNo !== null) { + tr_class = 'color'+colorNo; + } + if (commit.boundary) { + tr_class += ' boundary'; + } + if (commit.nprevious === 0) { + tr_class += ' no-previous'; + } else if (commit.nprevious > 1) { + tr_class += ' multiple-previous'; + } + tr.className = tr_class; + + /* ? */ + if (i === 0) { + td_sha1.title = commit.info; + td_sha1.rowSpan = group.numlines; + + a_sha1.href = projectUrl + 'a=commit;h=' + commit.sha1; + if (a_sha1.firstChild) { + a_sha1.firstChild.data = commit.sha1.substr(0, 8); + } else { + a_sha1.appendChild( + document.createTextNode(commit.sha1.substr(0, 8))); + } + if (group.numlines >= 2) { + var fragment = document.createDocumentFragment(); + var br = document.createElement("br"); + var match = commit.author.match(/\b([A-Z])\B/g); + if (match) { + var text = document.createTextNode( + match.join('')); + } + if (br && text) { + var elem = fragment || td_sha1; + elem.appendChild(br); + elem.appendChild(text); + if (fragment) { + td_sha1.appendChild(fragment); + } + } + } + } else { + //tr.removeChild(td_sha1); // DOM2 Core way + tr.deleteCell(0); // DOM2 HTML way + } + + /* 123 */ + var linenr_commit = + ('previous' in commit ? commit.previous : commit.sha1); + var linenr_filename = + ('file_parent' in commit ? commit.file_parent : commit.filename); + a_linenr.href = projectUrl + 'a=blame_incremental' + + ';hb=' + linenr_commit + + ';f=' + encodeURIComponent(linenr_filename) + + '#l' + (group.srcline + i); + + blamedLines++; + + //updateProgressInfo(); + } +} + +// ---------------------------------------------------------------------- + +var inProgress = false; // are we processing response + +/**#@+ + * @constant + */ +var sha1Re = /^([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/; +var infoRe = /^([a-z-]+) ?(.*)/; +var endRe = /^END ?([^ ]*) ?(.*)/; +/**@-*/ + +var curCommit = new Commit(); +var curGroup = {}; + +var pollTimer = null; + +/** + * Parse output from 'git blame --incremental [...]', received via + * XMLHttpRequest from server (blamedataUrl), and call handleLine + * (which updates page) as soon as blame entry is completed. + * + * @param {String[]} lines: new complete lines from blamedata server + * + * @globals commits, curCommit, curGroup, t_interval_server, cmds_server + * @globals sha1Re, infoRe, endRe + */ +function processBlameLines(lines) { + var match; + + for (var i = 0, len = lines.length; i < len; i++) { + + if ((match = sha1Re.exec(lines[i]))) { + var sha1 = match[1]; + var srcline = parseInt(match[2], 10); + var resline = parseInt(match[3], 10); + var numlines = parseInt(match[4], 10); + + var c = commits[sha1]; + if (!c) { + c = new Commit(sha1); + commits[sha1] = c; + } + curCommit = c; + + curGroup.srcline = srcline; + curGroup.resline = resline; + curGroup.numlines = numlines; + + } else if ((match = infoRe.exec(lines[i]))) { + var info = match[1]; + var data = match[2]; + switch (info) { + case 'filename': + curCommit.filename = unquote(data); + // 'filename' information terminates the entry + handleLine(curCommit, curGroup); + updateProgressInfo(); + break; + case 'author': + curCommit.author = data; + break; + case 'author-time': + curCommit.authorTime = parseInt(data, 10); + break; + case 'author-tz': + curCommit.authorTimezone = data; + break; + case 'previous': + curCommit.nprevious++; + // store only first 'previous' header + if (!'previous' in curCommit) { + var parts = data.split(' ', 2); + curCommit.previous = parts[0]; + curCommit.file_parent = unquote(parts[1]); + } + break; + case 'boundary': + curCommit.boundary = true; + break; + } // end switch + + } else if ((match = endRe.exec(lines[i]))) { + t_interval_server = match[1]; + cmds_server = match[2]; + + } else if (lines[i] !== '') { + // malformed line + + } // end if (match) + + } // end for (lines) +} + +/** + * Process new data and return pointer to end of processed part + * + * @param {String} unprocessed: new data (from nextReadPos) + * @param {Number} nextReadPos: end of last processed data + * @return {Number} end of processed data (new value for nextReadPos) + */ +function processData(unprocessed, nextReadPos) { + var lastLineEnd = unprocessed.lastIndexOf('\n'); + if (lastLineEnd !== -1) { + var lines = unprocessed.substring(0, lastLineEnd).split('\n'); + nextReadPos += lastLineEnd + 1 /* 1 == '\n'.length */; + + processBlameLines(lines); + } // end if + + return nextReadPos; +} + +/** + * Handle XMLHttpRequest errors + * + * @param {XMLHttpRequest} xhr: XMLHttpRequest object + * + * @globals pollTimer, commits, inProgress + */ +function handleError(xhr) { + errorInfo('Server error: ' + + xhr.status + ' - ' + (xhr.statusText || 'Error contacting server')); + + clearInterval(pollTimer); + commits = {}; // free memory + + inProgress = false; +} + +/** + * Called after XMLHttpRequest finishes (loads) + * + * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused) + * + * @globals pollTimer, commits, inProgress + */ +function responseLoaded(xhr) { + clearInterval(pollTimer); + + fixColorsAndGroups(); + writeTimeInterval(); + commits = {}; // free memory + + inProgress = false; +} + +/** + * handler for XMLHttpRequest onreadystatechange event + * @see startBlame + * + * @globals xhr, inProgress + */ +function handleResponse() { + + /* + * xhr.readyState + * + * Value Constant (W3C) Description + * ------------------------------------------------------------------- + * 0 UNSENT open() has not been called yet. + * 1 OPENED send() has not been called yet. + * 2 HEADERS_RECEIVED send() has been called, and headers + * and status are available. + * 3 LOADING Downloading; responseText holds partial data. + * 4 DONE The operation is complete. + */ + + if (xhr.readyState !== 4 && xhr.readyState !== 3) { + return; + } + + // the server returned error + // try ... catch block is to work around bug in IE8 + try { + if (xhr.readyState === 3 && xhr.status !== 200) { + return; + } + } catch (e) { + return; + } + if (xhr.readyState === 4 && xhr.status !== 200) { + handleError(xhr); + return; + } + + // In konqueror xhr.responseText is sometimes null here... + if (xhr.responseText === null) { + return; + } + + // in case we were called before finished processing + if (inProgress) { + return; + } else { + inProgress = true; + } + + // extract new whole (complete) lines, and process them + while (xhr.prevDataLength !== xhr.responseText.length) { + if (xhr.readyState === 4 && + xhr.prevDataLength === xhr.responseText.length) { + break; + } + + xhr.prevDataLength = xhr.responseText.length; + var unprocessed = xhr.responseText.substring(xhr.nextReadPos); + xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos); + } // end while + + // did we finish work? + if (xhr.readyState === 4 && + xhr.prevDataLength === xhr.responseText.length) { + responseLoaded(xhr); + } + + inProgress = false; +} + +// ============================================================ +// ------------------------------------------------------------ + +/** + * Incrementally update line data in blame_incremental view in gitweb. + * + * @param {String} blamedataUrl: URL to server script generating blame data. + * @param {String} bUrl: partial URL to project, used to generate links. + * + * Called from 'blame_incremental' view after loading table with + * file contents, a base for blame view. + * + * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer +*/ +function startBlame(blamedataUrl, bUrl) { + + xhr = createRequestObject(); + if (!xhr) { + errorInfo('ERROR: XMLHttpRequest not supported'); + return; + } + + t0 = new Date(); + projectUrl = bUrl + (bUrl.indexOf('?') === -1 ? '?' : ';'); + if ((div_progress_bar = document.getElementById('progress_bar'))) { + //div_progress_bar.setAttribute('style', 'width: 100%;'); + div_progress_bar.style.cssText = 'width: 100%;'; + } + totalLines = countLines(); + updateProgressInfo(); + + /* add extra properties to xhr object to help processing response */ + xhr.prevDataLength = -1; // used to detect if we have new data + xhr.nextReadPos = 0; // where unread part of response starts + + xhr.onreadystatechange = handleResponse; + //xhr.onreadystatechange = function () { handleResponse(xhr); }; + + xhr.open('GET', blamedataUrl); + xhr.setRequestHeader('Accept', 'text/plain'); + xhr.send(null); + + // not all browsers call onreadystatechange event on each server flush + // poll response using timer every second to handle this issue + pollTimer = setInterval(xhr.onreadystatechange, 1000); +} + +// end of gitweb.js diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh index 5a734b1b7b..b70b891b62 100644 --- a/t/gitweb-lib.sh +++ b/t/gitweb-lib.sh @@ -19,9 +19,9 @@ our \$site_name = '[localhost]'; our \$site_header = ''; our \$site_footer = ''; our \$home_text = 'indextext.html'; -our @stylesheets = ('file:///$TEST_DIRECTORY/../gitweb/gitweb.css'); -our \$logo = 'file:///$TEST_DIRECTORY/../gitweb/git-logo.png'; -our \$favicon = 'file:///$TEST_DIRECTORY/../gitweb/git-favicon.png'; +our @stylesheets = ('file:///$TEST_DIRECTORY/../gitweb/static/gitweb.css'); +our \$logo = 'file:///$TEST_DIRECTORY/../gitweb/static/git-logo.png'; +our \$favicon = 'file:///$TEST_DIRECTORY/../gitweb/static/git-favicon.png'; our \$projects_list = ''; our \$export_ok = ''; our \$strict_export = ''; -- cgit v1.3 From 45aa9895c5bef3914b6f3707b983f7a53900ebb7 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 5 Jun 2010 23:11:18 +0200 Subject: gitweb: Run in FastCGI mode if gitweb script has .fcgi extension If the name of the script ($SCRIPT_NAME or $SCRIPT_FILENAME CGI environment variable, or __FILE__ literal) ends with '.fcgi' extension, run gitweb in FastCGI mode, as if it was run with '--fastcgi' / '--fcgi' option. This is intended for easy deploying gitweb using FastCGI interface. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b044d18aca..e39ef866f0 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1025,19 +1025,24 @@ 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'; + + my $request_number = 0; + # let each child service 100 requests + our $is_last_request = sub { ++$request_number > 100 }; +} sub evaluate_argv { + my $script_name = $ENV{'SCRIPT_NAME'} || $ENV{'SCRIPT_FILENAME'} || __FILE__; + configure_as_fcgi() + if $script_name =~ /\.fcgi$/; + return unless (@ARGV); require Getopt::Long; Getopt::Long::GetOptions( - 'fastcgi|fcgi|f' => sub { - require CGI::Fast; - our $CGI = 'CGI::Fast'; - - my $request_number = 0; - # let each child service 100 requests - our $is_last_request = sub { ++$request_number > 100 }; - }, + 'fastcgi|fcgi|f' => \&configure_as_fcgi, 'nproc|n=i' => sub { my ($arg, $val) = @_; return unless eval { require FCGI::ProcManager; 1; }; -- cgit v1.3 From ad709ea985b527adb546f4c841fbd480f2c6bdfd Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 13 Jun 2010 00:35:59 +0200 Subject: gitweb: Fix typo in hash key name in %opts in git_header_html The name of the key has to be the same in call site handle_errors_html and in called subroutine that uses it, i.e. git_header_html. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 934aacb614..e108bbc60a 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3220,7 +3220,7 @@ sub git_header_html { } print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires) - unless ($opts{'-no_http_headers'}); + unless ($opts{'-no_http_header'}); my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : ''; print < -- cgit v1.3 From 5ed2ec1041b6aeec81b0f0a9775355a9b2c755a6 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 13 Jun 2010 12:09:32 +0200 Subject: gitweb: Return or exit after done serving request Check if there is a caller in top frame of gitweb, and either 'return' if gitweb code is wrapped in subroutine, or 'exit' if it is not. This should avoid gitweb.cgi: Subroutine git_SOMETHING redefined at gitweb.cgi line NNN warnings in error_log when running gitweb with mod_perl (using ModPerl::Registry handler) Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 2365311d94..9d2b8c3b64 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1000,8 +1000,16 @@ if ($action !~ m/^(?:opml|project_list|project_index)$/ && die_error(400, "Project needed"); } $actions{$action}->(); + DONE_GITWEB: -1; +if (defined caller) { + # wrapped in a subroutine processing requests, + # e.g. mod_perl with ModPerl::Registry, or PSGI with Plack::App::WrapCGI + return; +} else { + # pure CGI script, serving single request + exit; +} ## ====================================================================== ## action links -- cgit v1.3 From 70649945c215b1674611a40eda1d0058118a6d1a Mon Sep 17 00:00:00 2001 From: Jay Soffian Date: Fri, 18 Jun 2010 17:01:25 -0400 Subject: gitweb/Makefile: fix typo in gitweb.min.css rule This typo has been in place since the rule was originally added by 0e6ce21 (Gitweb: add support for minifying gitweb.css). Signed-off-by: Jay Soffian Signed-off-by: Junio C Hamano --- gitweb/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/Makefile b/gitweb/Makefile index f2e1d92fbb..e7dd252773 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -91,7 +91,7 @@ ifdef CSSMIN GITWEB_CSS = gitweb.min.css all:: gitweb.min.css gitweb.min.css: gitweb.css GITWEB-BUILD-OPTIONS - $(QUIET_GEN)$(CSSMIN) <$ >$@ + $(QUIET_GEN)$(CSSMIN) <$< >$@ endif GITWEB_REPLACE = \ -- cgit v1.3 From 869d58813b24c74e84c9388041eafcef40cb51e4 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 5 Jul 2010 20:52:43 +0200 Subject: gitweb: Move evaluate_gitweb_config out of run_request Move evaluate_gitweb_config() and evaluate_git_version() out of run_request() to run(), making them not run one for each request. This changes how git behaves in FastCGI case. This change makes it impossible to have config which changes with request, but I don't think anyone relied on such (hidden action) behavior. While at it, reset timer and number of git commands at beginning of run_request() in new reset_timer() subroutine. This fixes case when gitweb was run using FastCGI interface: time is reported for request, and not for single run of gitweb script. This changes slightly behavior in non-FastCGI case: the number of git commands reported is 1 less (running `git --version` one per gitweb is not counted now). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 9446376535..1f611d22d4 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1027,18 +1027,18 @@ sub dispatch { $actions{$action}->(); } -sub run_request { +sub reset_timer { our $t0 = [Time::HiRes::gettimeofday()] if defined $t0; + our $number_of_git_cmds = 0; +} + +sub run_request { + reset_timer(); evaluate_uri(); - evaluate_gitweb_config(); - evaluate_git_version(); check_loadavg(); - # $projectroot and $projects_list might be set in gitweb config file - $projects_list ||= $projectroot; - evaluate_query_params(); evaluate_path_info(); evaluate_and_validate_params(); @@ -1086,6 +1086,11 @@ sub evaluate_argv { sub run { evaluate_argv(); + evaluate_gitweb_config(); + evaluate_git_version(); + + # $projectroot and $projects_list might be set in gitweb config file + $projects_list ||= $projectroot; $pre_listen_hook->() if $pre_listen_hook; -- cgit v1.3 From 109988f2cb00f78b746d05c40b8a8960bbb12ff8 Mon Sep 17 00:00:00 2001 From: Pavan Kumar Sunkara Date: Thu, 15 Jul 2010 12:59:01 +0530 Subject: gitweb: fix esc_url Earlier, 452e225 (gitweb: fix esc_param, 2009-10-13) fixed CGI escaping rules used in esc_url. A very similar logic exists in esc_param and needs to be fixed the same way. Signed-off-by: Pavan Kumar Sunkara Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c356e95f18..a97ce03444 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1173,8 +1173,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; } -- cgit v1.3 From 61bf126ecb24977b883079942b71ff96174c19fb Mon Sep 17 00:00:00 2001 From: "Alejandro R. Sedeño" Date: Wed, 28 Jul 2010 14:40:53 -0400 Subject: gitweb: move highlight config out of guess_file_syntax() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move highlight config out of guess_file_syntax() so that it can be extended/overridden by system/user configuration. Signed-off-by: Alejandro R. Sedeño Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index cedc357313..e0e9532648 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -232,6 +232,29 @@ our %avatar_size = ( # Leave it undefined (or set to 'undef') to turn off load checking. our $maxload = 300; +# configuration for 'highlight' (http://www.andre-simon.de/) +# match by basename +our %highlight_basename = ( + #'Program' => 'py', + #'Library' => 'py', + 'SConstruct' => 'py', # SCons equivalent of Makefile + 'Makefile' => 'make', +); +# match by extension +our %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), +); + # You define site-wide feature defaults here; override them with # $GITWEB_CONFIG as necessary. our %feature = ( @@ -3316,30 +3339,6 @@ sub blob_contenttype { 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}; -- cgit v1.3 From 7f425db9146b7ec8c67edfd8b49218cc110151ed Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Fri, 30 Jul 2010 22:01:59 -0500 Subject: gitweb: allow configurations that change with each request gitolite's contrib/gitweb/gitweb.conf includes: $ENV{GL_USER} = $cgi->remote_user || "gitweb"; which is useful for setups where a user has to be authenticated to access certain repos. Perhaps other typical configurations change per session in other ways, too. v1.7.2-rc2~6 (gitweb: Move evaluate_gitweb_config out of run_request, 2010-07-05) broke such configurations for a speedup, by loading the configuration once per FastCGI process. Probably in the end there should be a way to specify in the configuration whether a particular installation wants the speedup or the flexibility. But for now it is easier to just undo the relevant change. This partially reverts commit 869d58813b24c74e84c9388041eafcef40cb51e4. Reported-by: Julio Lajara Analysis-by: Jakub Narebski Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index cedc357313..c1e910af6d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1037,8 +1037,12 @@ sub run_request { reset_timer(); evaluate_uri(); + evaluate_gitweb_config(); check_loadavg(); + # $projectroot and $projects_list might be set in gitweb config file + $projects_list ||= $projectroot; + evaluate_query_params(); evaluate_path_info(); evaluate_and_validate_params(); @@ -1086,12 +1090,8 @@ sub evaluate_argv { sub run { evaluate_argv(); - evaluate_gitweb_config(); evaluate_git_version(); - # $projectroot and $projects_list might be set in gitweb config file - $projects_list ||= $projectroot; - $pre_listen_hook->() if $pre_listen_hook; -- cgit v1.3 From 0b45010e76ef2114a530d81da626871de23f467f Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 2 Aug 2010 22:21:47 +0200 Subject: gitweb: Fix typo in run() subroutine Run $post_dispatch_hook->() not $pre_dispatch_hook->() after each request. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index e0e9532648..8b02767271 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1125,7 +1125,7 @@ sub run { run_request(); - $pre_dispatch_hook->() + $post_dispatch_hook->() if $post_dispatch_hook; last REQUEST if ($is_last_request->()); -- cgit v1.3 From 497d9c343902591e98a6612385d529816420ea94 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sat, 7 Aug 2010 16:56:47 -0500 Subject: gitweb: clarify search results page when no matching commit found MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When searching commits for a string that never occurs, the results page looks something like this: projects / foo.git / search \o/ summary | ... | tree [commit] search: [ kfjdkas ] [ ]re first ⋅ prev ⋅ next Merge branch 'maint' Foo: a demonstration project Without a list of hits to compare it to, the header describing the commit named by the hash parameter (usually HEAD) may itself look like a hit. Add some text (“No match.”) to replace the empty list of hits and avoid this confusion. While at it, remove some nearby dead code, left behind from a simplification a few years ago (v1.5.4-rc0~276^2~4, 2007-11-01). Noticed-by: Erick Mattos Signed-off-by: Jonathan Nieder Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index cedc357313..6deec87e5d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -6522,12 +6522,13 @@ sub git_search { $paging_nav .= " ⋅ next"; } - if ($#commitlist >= 100) { - } - git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash); - git_search_grep_body(\@commitlist, 0, 99, $next_link); + if ($page == 0 && !@commitlist) { + print "

No match.

\n"; + } else { + git_search_grep_body(\@commitlist, 0, 99, $next_link); + } } if ($searchtype eq 'pickaxe') { -- cgit v1.3 From 22e5e58a3c75b73764b860907e4d871195f276ac Mon Sep 17 00:00:00 2001 From: Ralf Wildenhues Date: Sun, 22 Aug 2010 13:12:12 +0200 Subject: Typos in code comments, an error message, documentation Signed-off-by: Ralf Wildenhues Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.5.6.3.txt | 2 +- Documentation/RelNotes-1.6.0.2.txt | 2 +- Documentation/RelNotes-1.6.4.3.txt | 2 +- Documentation/RelNotes-1.6.5.4.txt | 2 +- Documentation/RelNotes-1.6.5.7.txt | 2 +- Documentation/RelNotes-1.6.6.txt | 4 ++-- Documentation/RelNotes-1.7.0.txt | 2 +- Documentation/howto/revert-a-faulty-merge.txt | 2 +- builtin/blame.c | 4 ++-- compat/mingw.c | 2 +- compat/nedmalloc/malloc.c.h | 2 +- compat/regex/regex.c | 2 +- contrib/workdir/git-new-workdir | 4 ++-- gitweb/README | 4 ++-- gitweb/gitweb.perl | 12 ++++++------ graph.c | 2 +- pack-check.c | 2 +- 17 files changed, 26 insertions(+), 26 deletions(-) (limited to 'gitweb') diff --git a/Documentation/RelNotes-1.5.6.3.txt b/Documentation/RelNotes-1.5.6.3.txt index 942611299d..f61dd3504a 100644 --- a/Documentation/RelNotes-1.5.6.3.txt +++ b/Documentation/RelNotes-1.5.6.3.txt @@ -4,7 +4,7 @@ GIT v1.5.6.3 Release Notes Fixes since v1.5.6.2 -------------------- -* Setting core.sharerepository to traditional "true" value was supposed to make +* Setting core.sharedrepository to traditional "true" value was supposed to make the repository group writable but should not affect permission for others. However, since 1.5.6, it was broken to drop permission for others when umask is 022, making the repository unreadable by others. diff --git a/Documentation/RelNotes-1.6.0.2.txt b/Documentation/RelNotes-1.6.0.2.txt index 51b32f5d94..e1e24b3295 100644 --- a/Documentation/RelNotes-1.6.0.2.txt +++ b/Documentation/RelNotes-1.6.0.2.txt @@ -17,7 +17,7 @@ Fixes since v1.6.0.1 * Many commands did not use the correct working tree location when used with GIT_WORK_TREE environment settings. -* Some systems needs to use compatibility fnmach and regex libraries +* Some systems need to use compatibility fnmatch and regex libraries independent from each other; the compat/ area has been reorganized to allow this. diff --git a/Documentation/RelNotes-1.6.4.3.txt b/Documentation/RelNotes-1.6.4.3.txt index 4f29babdeb..5643e6537d 100644 --- a/Documentation/RelNotes-1.6.4.3.txt +++ b/Documentation/RelNotes-1.6.4.3.txt @@ -11,7 +11,7 @@ Fixes since v1.6.4.2 been deprecated. * "git fetch" and "git clone" had an extra sanity check to verify the - presense of the corresponding *.pack file before downloading *.idx + presence of the corresponding *.pack file before downloading *.idx file by issuing a HEAD request. Github server however sometimes gave 500 (Internal server error) response to HEAD even if a GET request for *.pack file to the same URL would have succeeded, and broke diff --git a/Documentation/RelNotes-1.6.5.4.txt b/Documentation/RelNotes-1.6.5.4.txt index e42f8b2397..d3a2a3e712 100644 --- a/Documentation/RelNotes-1.6.5.4.txt +++ b/Documentation/RelNotes-1.6.5.4.txt @@ -26,7 +26,7 @@ Fixes since v1.6.5.3 future versions, but not in this release, * "git merge -m ..." added the standard merge message - on its own after user-supplied message, which should have overrided the + on its own after user-supplied message, which should have overridden the standard one. Other minor documentation updates are included. diff --git a/Documentation/RelNotes-1.6.5.7.txt b/Documentation/RelNotes-1.6.5.7.txt index 5b49ea53be..dc5302c21c 100644 --- a/Documentation/RelNotes-1.6.5.7.txt +++ b/Documentation/RelNotes-1.6.5.7.txt @@ -10,7 +10,7 @@ Fixes since v1.6.5.6 an older version of git should just ignore them. Instead we diagnosed it as an error. -* With help.autocorrect set to non-zero value, the logic to guess typoes +* With help.autocorrect set to non-zero value, the logic to guess typos in the subcommand name misfired and ran a random nonsense command. * If a command is run with an absolute path as a pathspec inside a bare diff --git a/Documentation/RelNotes-1.6.6.txt b/Documentation/RelNotes-1.6.6.txt index 04e205c457..c50b59c495 100644 --- a/Documentation/RelNotes-1.6.6.txt +++ b/Documentation/RelNotes-1.6.6.txt @@ -29,7 +29,7 @@ or adjust to the new behaviour, on the day their sysadmin decides to install the new version of git. When we switched from "git-foo" to "git foo" in 1.6.0, even though the change had been advertised and the transition guide had been provided for a very long time, the users procrastinated -during the entire transtion period, and ended up panicking on the day +during the entire transition period, and ended up panicking on the day their sysadmins updated their git installation. We are trying to avoid repeating that unpleasantness in the 1.7.0 release. @@ -94,7 +94,7 @@ users will fare this time. * "git diff" traditionally treated various "ignore whitespace" options only as a way to filter the patch output. "git diff --exit-code -b" exited with non-zero status even if all changes were about changing the - ammount of whitespace and nothing else. and "git diff -b" showed the + amount of whitespace and nothing else. and "git diff -b" showed the "diff --git" header line for such a change without patch text. In 1.7.0, the "ignore whitespaces" will affect the semantics of the diff --git a/Documentation/RelNotes-1.7.0.txt b/Documentation/RelNotes-1.7.0.txt index 43e3f33615..0bb8c0b2a2 100644 --- a/Documentation/RelNotes-1.7.0.txt +++ b/Documentation/RelNotes-1.7.0.txt @@ -202,7 +202,7 @@ release, unless otherwise noted. the branch is fully merged to its upstream branch if it is not merged to the current branch. It now deletes it in such a case. - * "fiter-branch" command incorrectly said --prune-empty and --filter-commit + * "filter-branch" command incorrectly said --prune-empty and --filter-commit were incompatible; the latter should be read as --commit-filter. * When using "git status" or asking "git diff" to compare the work tree diff --git a/Documentation/howto/revert-a-faulty-merge.txt b/Documentation/howto/revert-a-faulty-merge.txt index ff5c0bc27a..6fd711996a 100644 --- a/Documentation/howto/revert-a-faulty-merge.txt +++ b/Documentation/howto/revert-a-faulty-merge.txt @@ -229,7 +229,7 @@ reverting W. Mainline's history would look like this: A---B---C But if you don't actually need to change commit A, then you need some way to -recreate it as a new commit with the same changes in it. The rebase commmand's +recreate it as a new commit with the same changes in it. The rebase command's --no-ff option provides a way to do this: $ git rebase [-i] --no-ff P diff --git a/builtin/blame.c b/builtin/blame.c index 01e62fdeb0..28e3be2ead 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -2376,11 +2376,11 @@ parse_done: * * The remaining are: * - * (1) if dashdash_pos != 0, its either + * (1) if dashdash_pos != 0, it is either * "blame [revisions] -- " or * "blame -- " * - * (2) otherwise, its one of the two: + * (2) otherwise, it is one of the two: * "blame [revisions] " * "blame " * diff --git a/compat/mingw.c b/compat/mingw.c index 9a8e336582..96be8a02cf 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -641,7 +641,7 @@ static char *lookup_prog(const char *dir, const char *cmd, int isexe, int exe_on } /* - * Determines the absolute path of cmd using the the split path in path. + * Determines the absolute path of cmd using the split path in path. * If cmd contains a slash or backslash, no lookup is performed. */ static char *path_lookup(const char *cmd, char **path, int exe_only) diff --git a/compat/nedmalloc/malloc.c.h b/compat/nedmalloc/malloc.c.h index 74c42e3162..87260d2642 100644 --- a/compat/nedmalloc/malloc.c.h +++ b/compat/nedmalloc/malloc.c.h @@ -2069,7 +2069,7 @@ static void init_malloc_global_mutex() { Each freshly allocated chunk must have both cinuse and pinuse set. That is, each allocated chunk borders either a previously allocated and still in-use chunk, or the base of its memory arena. This is - ensured by making all allocations from the the `lowest' part of any + ensured by making all allocations from the `lowest' part of any found chunk. Further, no free chunk physically borders another one, so each free chunk is known to be preceded and followed by either inuse chunks or the ends of memory. diff --git a/compat/regex/regex.c b/compat/regex/regex.c index 556d8ab11f..be851fc502 100644 --- a/compat/regex/regex.c +++ b/compat/regex/regex.c @@ -3122,7 +3122,7 @@ re_match (bufp, string, size, pos, regs) /* re_match_2 matches the compiled pattern in BUFP against the - the (virtual) concatenation of STRING1 and STRING2 (of length SIZE1 + (virtual) concatenation of STRING1 and STRING2 (of length SIZE1 and SIZE2, respectively). We start matching at POS, and stop matching at STOP. diff --git a/contrib/workdir/git-new-workdir b/contrib/workdir/git-new-workdir index 993cacf324..3ad2c0cea5 100755 --- a/contrib/workdir/git-new-workdir +++ b/contrib/workdir/git-new-workdir @@ -54,13 +54,13 @@ then die "destination directory '$new_workdir' already exists." fi -# make sure the the links use full paths +# make sure the links use full paths git_dir=$(cd "$git_dir"; pwd) # create the workdir mkdir -p "$new_workdir/.git" || die "unable to create \"$new_workdir\"!" -# create the links to the original repo. explictly exclude index, HEAD and +# create the links to the original repo. explicitly exclude index, HEAD and # logs/HEAD from the list since they are purely related to the current working # directory, and should not be shared. for x in config refs logs/refs objects info hooks packed-refs remotes rr-cache svn diff --git a/gitweb/README b/gitweb/README index 0e19be8d21..d481198796 100644 --- a/gitweb/README +++ b/gitweb/README @@ -95,7 +95,7 @@ You can specify the following configuration variables when building GIT: in the browser's URL bar and next to site name in bookmarks). Relative to base URI of gitweb. [Default: static/git-favicon.png] * GITWEB_JS - Points to the localtion where you put gitweb.js on your web server + Points to the location where you put gitweb.js on your web server (or to be more generic URI of JavaScript code used by gitweb). Relative to base URI of gitweb. [Default: static/gitweb.js (or static/gitweb.min.js if JSMIN build variable is defined / JavaScript @@ -233,7 +233,7 @@ not include variables usually directly set during build): is false. * $maxload Used to set the maximum load that we will still respond to gitweb queries. - If server load exceed this value then return "503 Service Unavaliable" error. + If server load exceed this value then return "503 Service Unavailable" error. Server load is taken to be 0 if gitweb cannot determine its value. Set it to undefined value to turn it off. The default is 300. diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 6deec87e5d..84261bba34 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -245,7 +245,7 @@ our %feature = ( # 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 + # overridden # # use gitweb_get_feature() to retrieve the value # (an array) or gitweb_check_feature() to check if @@ -1323,7 +1323,7 @@ sub esc_param { return $str; } -# quote unsafe chars in whole URL, so some charactrs cannot be quoted +# quote unsafe chars in whole URL, so some characters cannot be quoted sub esc_url { my $str = shift; return undef unless defined $str; @@ -3782,9 +3782,9 @@ sub git_print_authorship { } # Outputs table rows containing the full author or committer information, -# in the format expected for 'commit' view (& similia). +# in the format expected for 'commit' view (& similar). # Parameters are a commit hash reference, followed by the list of people -# to output information for. If the list is empty it defalts to both +# to output information for. If the list is empty it defaults to both # author and committer. sub git_print_authorship_rows { my $co = shift; @@ -4513,8 +4513,8 @@ sub git_patchset_body { print "\n"; # class="patch" } - # for compact combined (--cc) format, with chunk and patch simpliciaction - # patchset might be empty, but there might be unprocessed raw lines + # for compact combined (--cc) format, with chunk and patch simplification + # the patchset might be empty, but there might be unprocessed raw lines for (++$patch_idx if $patch_number > 0; $patch_idx < @$difftree; ++$patch_idx) { diff --git a/graph.c b/graph.c index ac7c605406..85ab150787 100644 --- a/graph.c +++ b/graph.c @@ -439,7 +439,7 @@ static void graph_update_width(struct git_graph *graph, max_cols++; /* - * We added a column for the the current commit as part of + * We added a column for the current commit as part of * graph->num_parents. If the current commit was already in * graph->columns, then we have double counted it. */ diff --git a/pack-check.c b/pack-check.c index 395fb9527a..9d0cb9a114 100644 --- a/pack-check.c +++ b/pack-check.c @@ -77,7 +77,7 @@ static int verify_packfile(struct packed_git *p, err = error("%s SHA1 checksum mismatch", p->pack_name); if (hashcmp(index_base + index_size - 40, pack_sig)) - err = error("%s SHA1 does not match its inddex", + err = error("%s SHA1 does not match its index", p->pack_name); unuse_pack(w_curs); -- cgit v1.3 From d8a94803842989582989fd5f5c3062c49134ad5b Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Fri, 27 Aug 2010 13:38:16 -0400 Subject: gitweb: Don't die_error in git_tag after already printing headers This fixes an XML error when visiting a nonexistent tag (i.e. "../gitweb.cgi?p=git.git;a=tag;h=refs/tags/BADNAME"). Signed-off-by: Anders Kaseorg Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index d0687f4581..a85e2f6319 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -5191,15 +5191,15 @@ sub git_summary { } sub git_tag { - my $head = git_get_head_hash($project); - git_header_html(); - git_print_page_nav('','', $head,undef,$head); my %tag = parse_tag($hash); if (! %tag) { die_error(404, "Unknown tag object"); } + my $head = git_get_head_hash($project); + git_header_html(); + git_print_page_nav('','', $head,undef,$head); git_print_header_div('commit', esc_html($tag{'name'}), $hash); print "
\n" . "\n" . -- cgit v1.3 From 7ce896b3000a7bd2ea24f02ad3051f43ad351e1f Mon Sep 17 00:00:00 2001 From: Christopher Wilson Date: Tue, 21 Sep 2010 00:25:19 -0700 Subject: Enable highlight executable path as a configuration option Allow build-time/run-time configuration of the highlight executable (must be the one from http://www.andre-simon.de due to assumptions about parameters and output). Defaults to previous behavior which assumes that highlight is available on the server PATH. However, if this is not the case, the path to the highlight executable can be configured at build time as a configuration variable HIGHLIGHT_BIN = /path/to/highlight or at runtime by configuring GITWEB_CONFIG $highlight_bin = /path/to/highlight Signed-off-by: Christopher Wilson Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/Makefile | 4 +++- gitweb/README | 11 ++++++++++- gitweb/gitweb.perl | 9 ++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) mode change 100644 => 100755 gitweb/README (limited to 'gitweb') diff --git a/gitweb/Makefile b/gitweb/Makefile index 2fb7c2d77b..e32ee76309 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -35,6 +35,7 @@ GITWEB_FAVICON = static/git-favicon.png GITWEB_JS = static/gitweb.js GITWEB_SITE_HEADER = GITWEB_SITE_FOOTER = +HIGHLIGHT_BIN = highlight # include user config -include ../config.mak.autogen @@ -129,7 +130,8 @@ GITWEB_REPLACE = \ -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \ -e 's|++GITWEB_JS++|$(GITWEB_JS)|g' \ -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \ - -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' + -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \ + -e 's|++HIGHLIGHT_BIN++|$(HIGHLIGHT_BIN)|g' GITWEB-BUILD-OPTIONS: FORCE @rm -f $@+ diff --git a/gitweb/README b/gitweb/README old mode 100644 new mode 100755 index d481198796..bf3664f2b7 --- a/gitweb/README +++ b/gitweb/README @@ -114,6 +114,11 @@ You can specify the following configuration variables when building GIT: when gitweb.cgi is executed, then the file specified in the environment variable will be loaded instead of the file specified when gitweb.cgi was created. [Default: /etc/gitweb.conf] + * HIGHLIGHT_BIN + Path to the highlight executable to use (must be the one from + http://www.andre-simon.de due to assumptions about parameters and output). + Useful if highlight is not installed on your webserver's PATH. + [Default: highlight] Runtime gitweb configuration @@ -236,7 +241,11 @@ not include variables usually directly set during build): If server load exceed this value then return "503 Service Unavailable" error. Server load is taken to be 0 if gitweb cannot determine its value. Set it to undefined value to turn it off. The default is 300. - + * $highlight_bin + Path to the highlight executable to use (must be the one from + http://www.andre-simon.de due to assumptions about parameters and output). + Useful if highlight is not installed on your webserver's PATH. + [Default: highlight] Projects list file format ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index a85e2f6319..e5910ce8f9 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -165,6 +165,12 @@ our @diff_opts = ('-M'); # taken from git_commit # the gitweb domain. our $prevent_xss = 0; +# Path to the highlight executable to use (must be the one from +# http://www.andre-simon.de due to assumptions about parameters and output). +# Useful if highlight is not installed on your webserver's PATH. +# [Default: highlight] +our $highlight_bin = "++HIGHLIGHT_BIN++"; + # information about snapshot formats that gitweb is capable of serving our %known_snapshot_formats = ( # name => { @@ -3360,7 +3366,8 @@ sub run_highlighter { close $fd or die_error(404, "Reading blob failed"); open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ". - "highlight --xhtml --fragment --syntax $syntax |" + quote_command($highlight_bin). + " --xhtml --fragment --syntax $syntax |" or die_error(500, "Couldn't open file or run syntax highlighter"); return $fd; } -- cgit v1.3 From d48b28418355ef41a9501eb28a82ec0b69e62a17 Mon Sep 17 00:00:00 2001 From: Ævar Arnfjörð Bjarmason Date: Fri, 24 Sep 2010 20:00:52 +0000 Subject: perl: bump the required Perl version to 5.8 from 5.6.[21] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Formalize our dependency on perl 5.8, bumped from 5.6.[12]. We already used the three-arg form of open() which was introduced in 5.6.1, but t/t9700/test.pl explicitly depended on 5.6.2. However git-add--interactive.pl has been failing on the 5.6 line since it was introduced in v1.5.0-rc0~12^2~2 back in 2006 due to this open syntax: sub run_cmd_pipe { my $fh = undef; open($fh, '-|', @_) or die; return <$fh>; } Which when executed dies on "Can't use an undefined value as filehandle reference". Several of our tests also fail on 5.6 (even more when compiled with NO_PERL_MAKEMAKER=1): t2016-checkout-patch.sh t3904-stash-patch.sh t3701-add-interactive.sh t7105-reset-patch.sh t7501-commit.sh t9700-perl-git.sh Our code is bitrotting on 5.6 with no-one interested in fixing it, and pinning us to such an ancient release of Perl is keeping us from using useful features introduced in the 5.8 release. The 5.6 series is now over 10 years old, and the 5.6.2 maintenance release almost 7. 5.8 on the other hand is more than 8 years old. All the modern Unix-like operating systems have now upgraded to it or a later version, and 5.8 packages are available for old IRIX, AIX Solaris and Tru64 systems. Signed-off-by: Ævar Arnfjörð Bjarmason Acked-by: Tor Arntsen Acked-by: Randal L. Schwartz Signed-off-by: Junio C Hamano --- INSTALL | 8 ++++---- git-add--interactive.perl | 1 + git-archimport.perl | 1 + git-cvsexportcommit.perl | 1 + git-cvsimport.perl | 1 + git-cvsserver.perl | 1 + git-difftool.perl | 1 + git-relink.perl | 2 +- git-send-email.perl | 1 + git-svn.perl | 1 + gitweb/gitweb.perl | 1 + perl/Git.pm | 1 + t/t7006/test-terminal.perl | 1 + t/t9700/test.pl | 2 +- 14 files changed, 17 insertions(+), 6 deletions(-) (limited to 'gitweb') diff --git a/INSTALL b/INSTALL index 59200b730e..10a1cba643 100644 --- a/INSTALL +++ b/INSTALL @@ -67,10 +67,10 @@ Issues of note: - A POSIX-compliant shell is required to run many scripts needed for everyday use (e.g. "bisect", "pull"). - - "Perl" is needed to use some of the features (e.g. preparing a - partial commit using "git add -i/-p", interacting with svn - repositories with "git svn"). If you can live without these, use - NO_PERL. + - "Perl" version 5.8 or later is needed to use some of the + features (e.g. preparing a partial commit using "git add -i/-p", + interacting with svn repositories with "git svn"). If you can + live without these, use NO_PERL. - "openssl" library is used by git-imap-send to use IMAP over SSL. If you don't need it, use NO_OPENSSL. diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 27fc79347a..a96fb53156 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -1,5 +1,6 @@ #!/usr/bin/perl -w +use 5.008; use strict; use Git; diff --git a/git-archimport.perl b/git-archimport.perl index 98f3ede566..947638c38d 100755 --- a/git-archimport.perl +++ b/git-archimport.perl @@ -54,6 +54,7 @@ and can contain multiple, unrelated branches. =cut +use 5.008; use strict; use warnings; use Getopt::Std; diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index 59b672213b..9a8188bba7 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -1,5 +1,6 @@ #!/usr/bin/perl -w +use 5.008; use strict; use Getopt::Std; use File::Temp qw(tempdir); diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 9e03eee458..53869fb644 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -13,6 +13,7 @@ # The head revision is on branch "origin" by default. # You can change that with the '-o' option. +use 5.008; use strict; use warnings; use Getopt::Long; diff --git a/git-cvsserver.perl b/git-cvsserver.perl index e9f3037df3..2822bed1fd 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -15,6 +15,7 @@ #### #### +use 5.008; use strict; use warnings; use bytes; diff --git a/git-difftool.perl b/git-difftool.perl index adc42de875..e95e4ad973 100755 --- a/git-difftool.perl +++ b/git-difftool.perl @@ -10,6 +10,7 @@ # # Any arguments that are unknown to this script are forwarded to 'git diff'. +use 5.008; use strict; use warnings; use Cwd qw(abs_path); diff --git a/git-relink.perl b/git-relink.perl index 937c69a748..af2e305fa3 100755 --- a/git-relink.perl +++ b/git-relink.perl @@ -6,7 +6,7 @@ # # Scan two git object-trees, and hardlink any common objects between them. -use 5.006; +use 5.008; use strict; use warnings; use Getopt::Long; diff --git a/git-send-email.perl b/git-send-email.perl index 6dab3bf6a7..314e59e7a9 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -16,6 +16,7 @@ # and second line is the subject of the message. # +use 5.008; use strict; use warnings; use Term::ReadLine; diff --git a/git-svn.perl b/git-svn.perl index 9b046b693f..d2922245aa 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1,6 +1,7 @@ #!/usr/bin/env perl # Copyright (C) 2006, Eric Wong # License: GPL v2 or later +use 5.008; use warnings; use strict; use vars qw/ $AUTHOR $VERSION diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index a85e2f6319..e645d4a821 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -7,6 +7,7 @@ # # This program is licensed under the GPLv2 +use 5.008; use strict; use warnings; use CGI qw(:standard :escapeHTML -nosticky); diff --git a/perl/Git.pm b/perl/Git.pm index 6cb0dd1934..205e48aa3a 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -7,6 +7,7 @@ Git - Perl interface to the Git version control system package Git; +use 5.008; use strict; diff --git a/t/t7006/test-terminal.perl b/t/t7006/test-terminal.perl index 73ff809371..6b5f22ae4a 100755 --- a/t/t7006/test-terminal.perl +++ b/t/t7006/test-terminal.perl @@ -1,4 +1,5 @@ #!/usr/bin/perl +use 5.008; use strict; use warnings; use IO::Pty; diff --git a/t/t9700/test.pl b/t/t9700/test.pl index 671f38db2b..c15ca2d647 100755 --- a/t/t9700/test.pl +++ b/t/t9700/test.pl @@ -1,7 +1,7 @@ #!/usr/bin/perl use lib (split(/:/, $ENV{GITPERLLIB})); -use 5.006002; +use 5.008; use warnings; use strict; -- cgit v1.3 From 8ff76f4c3b4e72ac8259465bdf6e33f4da19170d Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 26 Sep 2010 13:34:56 +0200 Subject: gitweb: Move call to evaluate_git_version after evaluate_gitweb_config Now evaluate_git_version() is inside run_request() to be called for each request, instead of once per starting gitweb; this currently matters only when using FastCGI interface (gitweb.fcgi). This change was done because evaluate_git_version() uses $GIT variable, which can be set / modified by gitweb config file, but the variable is modified this way by gitweb config file used in gitweb tests. Without this change there is spurious extra output from t9500 test when tests are run with '--debug' option. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index a85e2f6319..d521b4cf88 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1061,6 +1061,7 @@ sub run_request { evaluate_uri(); evaluate_gitweb_config(); + evaluate_git_version(); check_loadavg(); # $projectroot and $projects_list might be set in gitweb config file @@ -1113,7 +1114,6 @@ sub evaluate_argv { sub run { evaluate_argv(); - evaluate_git_version(); $pre_listen_hook->() if $pre_listen_hook; -- cgit v1.3 From 958a8467216ca607ca6a24059d5c641347927333 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 26 Sep 2010 15:02:26 +0200 Subject: gitweb/Makefile: Add 'test' and 'test-installed' targets The 'test-installed' target in gitweb/Makefile tests installed gitweb, using the same destination directory that 'install' target uses. The 'test' target is just a convenience wrapper invoking 'gitweb-test' target of t/Makefile. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/Makefile | 11 ++++++++++- t/Makefile | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/Makefile b/gitweb/Makefile index 2fb7c2d77b..f2180741fc 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -143,6 +143,15 @@ gitweb.cgi: gitweb.perl GITWEB-BUILD-OPTIONS chmod +x $@+ && \ mv $@+ $@ +### Testing rules + +test: + $(MAKE) -C ../t gitweb-test + +test-installed: + GITWEB_TEST_INSTALLED='$(DESTDIR_SQ)$(gitwebdir_SQ)' \ + $(MAKE) -C ../t gitweb-test + ### Installation rules install: all @@ -156,5 +165,5 @@ install: all clean: $(RM) gitweb.cgi static/gitweb.min.js static/gitweb.min.css GITWEB-BUILD-OPTIONS -.PHONY: all clean install .FORCE-GIT-VERSION-FILE FORCE +.PHONY: all clean install test test-installed .FORCE-GIT-VERSION-FILE FORCE diff --git a/t/Makefile b/t/Makefile index c7baefb7ea..7aa409ab64 100644 --- a/t/Makefile +++ b/t/Makefile @@ -17,6 +17,7 @@ SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh) TSVN = $(wildcard t91[0-9][0-9]-*.sh) +TGITWEB = $(wildcard t95[0-9][0-9]-*.sh) all: pre-clean $(MAKE) aggregate-results-and-cleanup @@ -46,6 +47,9 @@ full-svn-test: $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=0 LC_ALL=en_US.UTF-8 +gitweb-test: + $(MAKE) $(TGITWEB) + valgrind: GIT_TEST_OPTS=--valgrind $(MAKE) -- cgit v1.3 From 9b93aeb29cea16880926bc150ac31cfb31475a44 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 26 Sep 2010 15:02:24 +0200 Subject: gitweb/Makefile: Include gitweb/config.mak Allow for gitweb-specific Makefile config to reside in config.mak file in the 'gitweb/' subdirectory. This means that gitweb-specific build-time configuration variable can reside in gitweb-specific gitweb/config.mak Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/Makefile | 1 + 1 file changed, 1 insertion(+) (limited to 'gitweb') diff --git a/gitweb/Makefile b/gitweb/Makefile index f2180741fc..df908a18eb 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -39,6 +39,7 @@ GITWEB_SITE_FOOTER = # include user config -include ../config.mak.autogen -include ../config.mak +-include config.mak # determine version ../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE -- cgit v1.3 From 7e00dc58d14a5f8ea229e5a0acf5f019028e3a40 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Wed, 13 Oct 2010 13:33:48 +0200 Subject: gitweb: Fix bug in evaluate_path_info There was bug in parsing "project/:/file" and "project/:/" path_info URLs, with implicit HEAD as 'hash_base'. For such URLs the refname is empty, and before this fix regexp for parsing path_info fragment assumed that it is always non-empty. Refname cannot contain ':', as per 'git check-ref-format'. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index e5910ce8f9..c4d3e0846a 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -780,10 +780,10 @@ sub evaluate_path_info { 'history', ); - # we want to catch + # we want to catch, among others # [$hash_parent_base[:$file_parent]..]$hash_parent[:$file_name] my ($parentrefname, $parentpathname, $refname, $pathname) = - ($path_info =~ /^(?:(.+?)(?::(.+))?\.\.)?(.+?)(?::(.+))?$/); + ($path_info =~ /^(?:(.+?)(?::(.+))?\.\.)?([^:]+?)?(?::(.+))?$/); # first, analyze the 'current' part if (defined $pathname) { -- cgit v1.3 From d0af3734f14410e9c297aa08bf46fdb153a25ccb Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Wed, 13 Oct 2010 13:35:20 +0200 Subject: gitweb: Improve behavior for actionless path_info gitweb URLs Eli Barzilay noticed that http://server/gitweb/project/ link goes to 'shortlog' view, while 'commit' view would be more useful, but that 'shortlog' action is more apropriate for http://server/gitweb/project/.. links. Therefore for the case when we don't have either action, or filename, or parent hash [base] in path_info-based URL, i.e. for http://server/gitweb/project/ link, instead of using 'shortlog' view we allow dispatch() subroutine to detect type of object and use appropriate action (in most case it would be either 'commit' action, or 'tag', or 'tree' for top directory). Requested-by: Eli Barzilay Signed-off-by: Jakub Narebski Tested-by: Eli Barzilay Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c4d3e0846a..8d7e4c5e49 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -819,8 +819,15 @@ sub evaluate_path_info { # hash_base instead. It should also be noted that hand-crafted # links having 'history' as an action and no pathname or hash # set will fail, but that happens regardless of PATH_INFO. - $input_params{'action'} ||= "shortlog"; - if (grep { $_ eq $input_params{'action'} } @wants_base) { + if (defined $parentrefname) { + # if there is parent let the default be 'shortlog' action + # (for http://git.example.com/repo.git/A..B links); if there + # is no parent, dispatch will detect type of object and set + # action appropriately if required (if action is not set) + $input_params{'action'} ||= "shortlog"; + } + if ($input_params{'action'} && + grep { $_ eq $input_params{'action'} } @wants_base) { $input_params{'hash_base'} ||= $refname; } else { $input_params{'hash'} ||= $refname; -- cgit v1.3 From 3962f1d756ab41c1d180e35483d1c8dffe51e0d1 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 9 Nov 2010 19:27:54 +0100 Subject: gitweb: Time::HiRes is in core for Perl 5.8 We say 'use 5.008' at the beginning of the script, therefore there is no need to check if Time::HiRes module is available. We can also import gettimeofday and tv_interval. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 253f41adc9..23f9e79932 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -17,12 +17,10 @@ use Encode; use Fcntl ':mode'; use File::Find qw(); use File::Basename qw(basename); +use Time::HiRes qw(gettimeofday tv_interval); binmode STDOUT, ':utf8'; -our $t0; -if (eval { require Time::HiRes; 1; }) { - $t0 = [Time::HiRes::gettimeofday()]; -} +our $t0 = [ gettimeofday() ]; our $number_of_git_cmds = 0; BEGIN { @@ -1065,7 +1063,7 @@ sub dispatch { } sub reset_timer { - our $t0 = [Time::HiRes::gettimeofday()] + our $t0 = [ gettimeofday() ] if defined $t0; our $number_of_git_cmds = 0; } @@ -3590,7 +3588,7 @@ sub git_footer_html { print "
\n"; print 'This page took '. ''. - Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]). + tv_interval($t0, [ gettimeofday() ]). ' seconds '. ' and '. ''. @@ -5298,7 +5296,7 @@ sub git_blame_common { print 'END'; if (defined $t0 && gitweb_check_feature('timed')) { print ' '. - Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]). + tv_interval($t0, [ gettimeofday() ]). ' '.$number_of_git_cmds; } print "\n"; -- cgit v1.3 From 9e70e15814d645b48e2ed892520b88122c992b30 Mon Sep 17 00:00:00 2001 From: Giuseppe Bilotta Date: Thu, 11 Nov 2010 13:26:08 +0100 Subject: gitweb: use fullname as hash_base in heads link Otherwise, if names are manipulated for display, the link will point to the wrong head. Signed-off-by: Giuseppe Bilotta Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 253f41adc9..77693abb2a 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -4960,7 +4960,7 @@ sub git_heads_body { "
\n" . ""; } -- cgit v1.3 From 60efa2451e46810bdf7da6a31758c779de8ff4f7 Mon Sep 17 00:00:00 2001 From: Giuseppe Bilotta Date: Thu, 11 Nov 2010 13:26:09 +0100 Subject: gitweb: introduce remote_heads feature With this feature enabled, remote heads are retrieved (and displayed) when getting (and displaying) the heads list. Typical usage would be for local repository browsing, e.g. by using git-instaweb (or even a more permanent gitweb setup), to check the repository status and the relation between tracking branches and the originating remotes. Signed-off-by: Giuseppe Bilotta Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 77693abb2a..e1787c2e0e 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -493,6 +493,18 @@ our %feature = ( 'sub' => sub { feature_bool('highlight', @_) }, 'override' => 0, 'default' => [0]}, + + # Enable displaying of remote heads in the heads list + + # To enable system wide have in $GITWEB_CONFIG + # $feature{'remote_heads'}{'default'} = [1]; + # To have project specific config enable override in $GITWEB_CONFIG + # $feature{'remote_heads'}{'override'} = 1; + # and in project config gitweb.remote_heads = 0|1; + 'remote_heads' => { + 'sub' => sub { feature_bool('remote_heads', @_) }, + 'override' => 0, + 'default' => [0]}, ); sub gitweb_get_feature { @@ -3160,10 +3172,12 @@ sub git_get_heads_list { my $limit = shift; my @headslist; + my $remote_heads = gitweb_check_feature('remote_heads'); + open my $fd, '-|', git_cmd(), 'for-each-ref', ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate', '--format=%(objectname) %(refname) %(subject)%00%(committer)', - 'refs/heads' + 'refs/heads', ($remote_heads ? 'refs/remotes' : ()) or return; while (my $line = <$fd>) { my %ref_item; @@ -3174,7 +3188,7 @@ sub git_get_heads_list { my ($committer, $epoch, $tz) = ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/); $ref_item{'fullname'} = $name; - $name =~ s!^refs/heads/!!; + $name =~ s!^refs/(?:head|remote)s/!!; $ref_item{'name'} = $name; $ref_item{'id'} = $hash; -- cgit v1.3 From 9b3f3de16c4a3b8414e5d49f79dda343a4ec1511 Mon Sep 17 00:00:00 2001 From: Giuseppe Bilotta Date: Thu, 11 Nov 2010 13:26:10 +0100 Subject: gitweb: git_get_heads_list accepts an optional list of refs git_get_heads_list(limit, class1, class2, ...) can now be used to retrieve refs/class1, refs/class2 etc. Defaults to ('heads', 'remotes') or ('heads') depending on whether the 'remote_heads' feature is enabled or not. Signed-off-by: Giuseppe Bilotta Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index e1787c2e0e..951bb0d6f0 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3169,15 +3169,18 @@ sub parse_from_to_diffinfo { ## parse to array of hashes functions sub git_get_heads_list { - my $limit = shift; + my ($limit, @classes) = @_; + unless (@classes) { + my $remote_heads = gitweb_check_feature('remote_heads'); + @classes = ('heads', $remote_heads ? 'remotes' : ()); + } + my @patterns = map { "refs/$_" } @classes; my @headslist; - my $remote_heads = gitweb_check_feature('remote_heads'); - open my $fd, '-|', git_cmd(), 'for-each-ref', ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate', '--format=%(objectname) %(refname) %(subject)%00%(committer)', - 'refs/heads', ($remote_heads ? 'refs/remotes' : ()) + @patterns or return; while (my $line = <$fd>) { my %ref_item; -- cgit v1.3 From 00fa6fef631edeb5e0e34b7748c07882ec0e51d7 Mon Sep 17 00:00:00 2001 From: Giuseppe Bilotta Date: Thu, 11 Nov 2010 13:26:11 +0100 Subject: gitweb: separate heads and remotes lists We specialize the 'heads' action to only display local branches, and introduce a 'remotes' action to display the remote branches (only available when the remotes_head feature is enabled). Mirroring this, we also split the heads list in summary view into local and remote lists, each linking to the appropriate action. The git_get_heads_list now defaults to 'heads' only, regardless of whether the remote heads feature is active or not. Signed-off-by: Giuseppe Bilotta Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 951bb0d6f0..9fcbbb2d6c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -719,6 +719,7 @@ our %actions = ( "log" => \&git_log, "patch" => \&git_patch, "patches" => \&git_patches, + "remotes" => \&git_remotes, "rss" => \&git_rss, "atom" => \&git_atom, "search" => \&git_search, @@ -3170,10 +3171,7 @@ sub parse_from_to_diffinfo { sub git_get_heads_list { my ($limit, @classes) = @_; - unless (@classes) { - my $remote_heads = gitweb_check_feature('remote_heads'); - @classes = ('heads', $remote_heads ? 'remotes' : ()); - } + @classes = ('heads') unless @classes; my @patterns = map { "refs/$_" } @classes; my @headslist; @@ -5126,6 +5124,7 @@ sub git_summary { my %co = parse_commit("HEAD"); my %cd = %co ? parse_date($co{'committer_epoch'}, $co{'committer_tz'}) : (); my $head = $co{'id'}; + my $remote_heads = gitweb_check_feature('remote_heads'); my $owner = git_get_project_owner($project); @@ -5134,6 +5133,7 @@ sub git_summary { # there are more ... my @taglist = git_get_tags_list(16); my @headlist = git_get_heads_list(16); + my @remotelist = $remote_heads ? git_get_heads_list(16, 'remotes') : (); my @forklist; my $check_forks = gitweb_check_feature('forks'); @@ -5211,6 +5211,13 @@ sub git_summary { $cgi->a({-href => href(action=>"heads")}, "...")); } + if (@remotelist) { + git_print_header_div('remotes'); + git_heads_body(\@remotelist, $head, 0, 15, + $#remotelist <= 15 ? undef : + $cgi->a({-href => href(action=>"remotes")}, "...")); + } + if (@forklist) { git_print_header_div('forks'); git_project_list_body(\@forklist, 'age', 0, 15, @@ -5525,6 +5532,22 @@ sub git_heads { git_footer_html(); } +sub git_remotes { + gitweb_check_feature('remote_heads') + or die_error(403, "Remote heads view is disabled"); + + my $head = git_get_head_hash($project); + git_header_html(); + git_print_page_nav('','', $head,undef,$head); + git_print_header_div('summary', $project); + + my @remotelist = git_get_heads_list(undef, 'remotes'); + if (@remotelist) { + git_heads_body(\@remotelist, $head); + } + git_footer_html(); +} + sub git_blob_plain { my $type = shift; my $expires; -- cgit v1.3 From 11e7bece1559695faf72803ef8aa52c1d65350f9 Mon Sep 17 00:00:00 2001 From: Giuseppe Bilotta Date: Thu, 11 Nov 2010 13:26:12 +0100 Subject: gitweb: nagivation menu for tags, heads and remotes tags, heads and remotes are all views that inspect a (particular class of) refs, so allow the user to easily switch between them by adding the appropriate navigation submenu to each view. Signed-off-by: Giuseppe Bilotta Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 9fcbbb2d6c..7cd50d4b82 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3733,6 +3733,19 @@ sub git_print_page_nav { "\n"; } +# returns a submenu for the nagivation of the refs views (tags, heads, +# remotes) with the current view disabled and the remotes view only +# available if the feature is enabled +sub format_ref_views { + my ($current) = @_; + my @ref_views = qw{tags heads}; + push @ref_views, 'remotes' if gitweb_check_feature('remote_heads'); + return join " | ", map { + $_ eq $current ? $_ : + $cgi->a({-href => href(action=>$_)}, $_) + } @ref_views +} + sub format_paging_nav { my ($action, $page, $has_next_link) = @_; my $paging_nav; @@ -5509,7 +5522,7 @@ sub git_blame_data { sub git_tags { my $head = git_get_head_hash($project); git_header_html(); - git_print_page_nav('','', $head,undef,$head); + git_print_page_nav('','', $head,undef,$head,format_ref_views('tags')); git_print_header_div('summary', $project); my @tagslist = git_get_tags_list(); @@ -5522,7 +5535,7 @@ sub git_tags { sub git_heads { my $head = git_get_head_hash($project); git_header_html(); - git_print_page_nav('','', $head,undef,$head); + git_print_page_nav('','', $head,undef,$head,format_ref_views('heads')); git_print_header_div('summary', $project); my @headslist = git_get_heads_list(); @@ -5538,7 +5551,7 @@ sub git_remotes { my $head = git_get_head_hash($project); git_header_html(); - git_print_page_nav('','', $head,undef,$head); + git_print_page_nav('','', $head,undef,$head,format_ref_views('remotes')); git_print_header_div('summary', $project); my @remotelist = git_get_heads_list(undef, 'remotes'); -- cgit v1.3 From c7d94cdbe7ed29a0e26645fa4bb30d87755d86aa Mon Sep 17 00:00:00 2001 From: Giuseppe Bilotta Date: Thu, 11 Nov 2010 13:26:13 +0100 Subject: gitweb: allow action specialization in page header An optional -action_extra parameter is given to git_header_html() to identify a variant of the action that is being displayed. For example, this can be used to specify that the remotes view is being used for a specific remote and not to display all remotes. When -action_extra is provided, the action name in the header will be turned into a link to the action without any arguments or parameters, to provide a quick link to the non-specific variant of the action. Signed-off-by: Giuseppe Bilotta Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 7cd50d4b82..c3b89666c5 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3526,7 +3526,15 @@ EOF if (defined $project) { print $cgi->a({-href => href(action=>"summary")}, esc_html($project)); if (defined $action) { - print " / $action"; + my $action_print = $action ; + if (defined $opts{-action_extra}) { + $action_print = $cgi->a({-href => href(action=>$action)}, + $action); + } + print " / $action_print"; + } + if (defined $opts{-action_extra}) { + print " / $opts{-action_extra}"; } print "\n"; } -- cgit v1.3 From bb60776063f0575c1a880cd606b7470e36a7cd86 Mon Sep 17 00:00:00 2001 From: Giuseppe Bilotta Date: Thu, 11 Nov 2010 13:26:14 +0100 Subject: gitweb: remotes view for a single remote When 'remotes' view is passed the 'hash' parameter, interpret it as the name of a remote and limit the view the the heads of that remote. In single-remote view we let the user switch easily to the default remotes view by specifying an -action_extra for the page header and by enabling the 'remotes' link in the reference navigation submenu. Signed-off-by: Giuseppe Bilotta Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c3b89666c5..bf387572bf 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -5558,14 +5558,36 @@ sub git_remotes { or die_error(403, "Remote heads view is disabled"); my $head = git_get_head_hash($project); - git_header_html(); - git_print_page_nav('','', $head,undef,$head,format_ref_views('remotes')); - git_print_header_div('summary', $project); + my $remote = $input_params{'hash'}; + + my @remotelist; + + if (defined $remote) { + # only display the heads in a given remote, stripping the + # remote name which is already visible elsewhere + @remotelist = map { + my $ref = $_ ; + $ref->{'name'} =~ s!^$remote/!!; + $ref + } git_get_heads_list(undef, "remotes/$remote"); + } else { + @remotelist = git_get_heads_list(undef, 'remotes'); + } + + git_header_html(undef, undef, -action_extra => $remote); + git_print_page_nav('', '', $head, undef, $head, + format_ref_views($remote ? '' : 'remotes')); + + if (defined $remote) { + git_print_header_div('remotes', "$remote remote for $project"); + } else { + git_print_header_div('summary', "$project remotes"); + } - my @remotelist = git_get_heads_list(undef, 'remotes'); if (@remotelist) { git_heads_body(\@remotelist, $head); } + git_footer_html(); } -- cgit v1.3 From 0e65699992ddaf68d9d1c30ad8cd1e418cf1bc52 Mon Sep 17 00:00:00 2001 From: Giuseppe Bilotta Date: Thu, 11 Nov 2010 13:26:15 +0100 Subject: gitweb: refactor repository URL printing Factor out the code to display the repository URL(s) from summary view into a format_rep_url() routine. Signed-off-by: Giuseppe Bilotta Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index bf387572bf..6e7a66368d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3797,6 +3797,11 @@ sub git_print_header_div { "\n\n"; } +sub format_repo_url { + my ($name, $url) = @_; + return "\n"; +} + sub print_local_time { print format_local_time(@_); } @@ -5180,7 +5185,7 @@ sub git_summary { @url_list = map { "$_/$project" } @git_base_url_list unless @url_list; foreach my $git_url (@url_list) { next unless $git_url; - print "\n"; + print format_repo_url($url_tag, $git_url); $url_tag = ""; } -- cgit v1.3 From b891d52a644e58a830091463911cb859499ae504 Mon Sep 17 00:00:00 2001 From: Giuseppe Bilotta Date: Thu, 11 Nov 2010 13:26:16 +0100 Subject: gitweb: provide a routine to display (sub)sections The routine puts the given contento into a DIV element, automatically adding a header div. The content can be provided as a standard scalar value (which is used as-is), as a scalar ref (which is HTML-escaped), as a function reference to be executed, or as a file handle to be dumped. Signed-off-by: Giuseppe Bilotta Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 6e7a66368d..64da0cc730 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3802,6 +3802,44 @@ sub format_repo_url { return "\n"; } +# Group output by placing it in a DIV element and adding a header. +# Options for start_div() can be provided by passing a hash reference as the +# first parameter to the function. +# Options to git_print_header_div() can be provided by passing an array +# reference. This must follow the options to start_div if they are present. +# The content can be a scalar, which is output as-is, a scalar reference, which +# is output after html escaping, an IO handle passed either as *handle or +# *handle{IO}, or a function reference. In the latter case all following +# parameters will be taken as argument to the content function call. +sub git_print_section { + my ($div_args, $header_args, $content); + my $arg = shift; + if (ref($arg) eq 'HASH') { + $div_args = $arg; + $arg = shift; + } + if (ref($arg) eq 'ARRAY') { + $header_args = $arg; + $arg = shift; + } + $content = $arg; + + print $cgi->start_div($div_args); + git_print_header_div(@$header_args); + + if (ref($content) eq 'CODE') { + $content->(@_); + } elsif (ref($content) eq 'SCALAR') { + print esc_html($$content); + } elsif (ref($content) eq 'GLOB' or ref($content) eq 'IO::Handle') { + print <$content>; + } elsif (!ref($content) && defined($content)) { + print $content; + } + + print $cgi->end_div; +} + sub print_local_time { print format_local_time(@_); } -- cgit v1.3 From 9d0d42f3450fe34737f92b9bde31ac1cf6deeadb Mon Sep 17 00:00:00 2001 From: Giuseppe Bilotta Date: Thu, 11 Nov 2010 13:26:17 +0100 Subject: gitweb: group remote heads by remote In remote and summary view, display a block for each remote, with the fetch and push URL(s) as well as the list of the remote heads. In summary view, if the number of remotes is higher than a prescribed limit, only display the first remotes and their fetch and push urls, without any heads information and without grouping. Signed-off-by: Giuseppe Bilotta Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 166 +++++++++++++++++++++++++++++++++++++++++------ gitweb/static/gitweb.css | 6 ++ 2 files changed, 152 insertions(+), 20 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 64da0cc730..f4715531f7 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2772,6 +2772,44 @@ sub git_get_last_activity { return (undef, undef); } +# Implementation note: when a single remote is wanted, we cannot use 'git +# remote show -n' because that command always work (assuming it's a remote URL +# if it's not defined), and we cannot use 'git remote show' because that would +# try to make a network roundtrip. So the only way to find if that particular +# remote is defined is to walk the list provided by 'git remote -v' and stop if +# and when we find what we want. +sub git_get_remotes_list { + my $wanted = shift; + my %remotes = (); + + open my $fd, '-|' , git_cmd(), 'remote', '-v'; + return unless $fd; + while (my $remote = <$fd>) { + chomp $remote; + $remote =~ s!\t(.*?)\s+\((\w+)\)$!!; + next if $wanted and not $remote eq $wanted; + my ($url, $key) = ($1, $2); + + $remotes{$remote} ||= { 'heads' => () }; + $remotes{$remote}{$key} = $url; + } + close $fd or return; + return wantarray ? %remotes : \%remotes; +} + +# Takes a hash of remotes as first parameter and fills it by adding the +# available remote heads for each of the indicated remotes. +sub fill_remote_heads { + my $remotes = shift; + my @heads = map { "remotes/$_" } keys %$remotes; + my @remoteheads = git_get_heads_list(undef, @heads); + foreach my $remote (keys %$remotes) { + $remotes->{$remote}{'heads'} = [ grep { + $_->{'name'} =~ s!^$remote/!! + } @remoteheads ]; + } +} + sub git_get_references { my $type = shift || ""; my %refs; @@ -5051,6 +5089,101 @@ sub git_heads_body { print "
" . $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'})}, "shortlog") . " | " . $cgi->a({-href => href(action=>"log", hash=>$ref{'fullname'})}, "log") . " | " . - $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'name'})}, "tree") . + $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'fullname'})}, "tree") . "
\n"; } +# Display a single remote block +sub git_remote_block { + my ($remote, $rdata, $limit, $head) = @_; + + my $heads = $rdata->{'heads'}; + my $fetch = $rdata->{'fetch'}; + my $push = $rdata->{'push'}; + + my $urls_table = "\n" ; + + if (defined $fetch) { + if ($fetch eq $push) { + $urls_table .= format_repo_url("URL", $fetch); + } else { + $urls_table .= format_repo_url("Fetch URL", $fetch); + $urls_table .= format_repo_url("Push URL", $push) if defined $push; + } + } elsif (defined $push) { + $urls_table .= format_repo_url("Push URL", $push); + } else { + $urls_table .= format_repo_url("", "No remote URL"); + } + + $urls_table .= "
\n"; + + my $dots; + if (defined $limit && $limit < @$heads) { + $dots = $cgi->a({-href => href(action=>"remotes", hash=>$remote)}, "..."); + } + + print $urls_table; + git_heads_body($heads, $head, 0, $limit, $dots); +} + +# Display a list of remote names with the respective fetch and push URLs +sub git_remotes_list { + my ($remotedata, $limit) = @_; + print "\n"; + my $alternate = 1; + my @remotes = sort keys %$remotedata; + + my $limited = $limit && $limit < @remotes; + + $#remotes = $limit - 1 if $limited; + + while (my $remote = shift @remotes) { + my $rdata = $remotedata->{$remote}; + my $fetch = $rdata->{'fetch'}; + my $push = $rdata->{'push'}; + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + print ""; + print ""; + + print "\n"; + } + + if ($limited) { + print "\n" . + "\n" . "\n"; + } + + print "
" . + $cgi->a({-href=> href(action=>'remotes', hash=>$remote), + -class=> "list name"},esc_html($remote)) . + "" . + (defined $fetch ? $cgi->a({-href=> $fetch}, "fetch") : "fetch") . + " | " . + (defined $push ? $cgi->a({-href=> $push}, "push") : "push") . + "
" . + $cgi->a({-href => href(action=>"remotes")}, "...") . + "
"; +} + +# Display remote heads grouped by remote, unless there are too many +# remotes, in which case we only display the remote names +sub git_remotes_body { + my ($remotedata, $limit, $head) = @_; + if ($limit and $limit < keys %$remotedata) { + git_remotes_list($remotedata, $limit); + } else { + fill_remote_heads($remotedata); + while (my ($remote, $rdata) = each %$remotedata) { + git_print_section({-class=>"remote", -id=>$remote}, + ["remotes", $remote, $remote], sub { + git_remote_block($remote, $rdata, $limit, $head); + }); + } + } +} + sub git_search_grep_body { my ($commitlist, $from, $to, $extra) = @_; $from = 0 unless defined $from; @@ -5197,7 +5330,7 @@ sub git_summary { # there are more ... my @taglist = git_get_tags_list(16); my @headlist = git_get_heads_list(16); - my @remotelist = $remote_heads ? git_get_heads_list(16, 'remotes') : (); + my %remotedata = $remote_heads ? git_get_remotes_list() : (); my @forklist; my $check_forks = gitweb_check_feature('forks'); @@ -5275,11 +5408,9 @@ sub git_summary { $cgi->a({-href => href(action=>"heads")}, "...")); } - if (@remotelist) { + if (%remotedata) { git_print_header_div('remotes'); - git_heads_body(\@remotelist, $head, 0, 15, - $#remotelist <= 15 ? undef : - $cgi->a({-href => href(action=>"remotes")}, "...")); + git_remotes_body(\%remotedata, 15, $head); } if (@forklist) { @@ -5596,6 +5727,7 @@ sub git_heads { git_footer_html(); } +# used both for single remote view and for list of all the remotes sub git_remotes { gitweb_check_feature('remote_heads') or die_error(403, "Remote heads view is disabled"); @@ -5603,32 +5735,26 @@ sub git_remotes { my $head = git_get_head_hash($project); my $remote = $input_params{'hash'}; - my @remotelist; + my $remotedata = git_get_remotes_list($remote); + die_error(500, "Unable to get remote information") unless defined $remotedata; - if (defined $remote) { - # only display the heads in a given remote, stripping the - # remote name which is already visible elsewhere - @remotelist = map { - my $ref = $_ ; - $ref->{'name'} =~ s!^$remote/!!; - $ref - } git_get_heads_list(undef, "remotes/$remote"); - } else { - @remotelist = git_get_heads_list(undef, 'remotes'); + unless (%$remotedata) { + die_error(404, defined $remote ? + "Remote $remote not found" : + "No remotes found"); } git_header_html(undef, undef, -action_extra => $remote); git_print_page_nav('', '', $head, undef, $head, format_ref_views($remote ? '' : 'remotes')); + fill_remote_heads($remotedata); if (defined $remote) { git_print_header_div('remotes', "$remote remote for $project"); + git_remote_block($remote, $remotedata->{$remote}, undef, $head); } else { git_print_header_div('summary', "$project remotes"); - } - - if (@remotelist) { - git_heads_body(\@remotelist, $head); + git_remotes_body($remotedata, undef, $head); } git_footer_html(); diff --git a/gitweb/static/gitweb.css b/gitweb/static/gitweb.css index 4132aabcdb..79d7eebba7 100644 --- a/gitweb/static/gitweb.css +++ b/gitweb/static/gitweb.css @@ -573,6 +573,12 @@ div.binary { font-style: italic; } +div.remote { + margin: .5em; + border: 1px solid #d9d8d1; + display: inline-block; +} + /* Style definition generated by highlight 2.4.5, http://www.andre-simon.de/ */ /* Highlighting theme definition: */ -- cgit v1.3 From da4b2432cc6fe514dd72431de7be3ff2b4c86d7e Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 25 Nov 2010 19:43:59 +0100 Subject: gitweb: selectable configurations that change with each request Allow selecting whether configuration file should be (re)parsed on each request (the default, for backward compatibility with configurations that change per session, see commit 7f425db (gitweb: allow configurations that change with each request, 2010-07-30)), or whether should it be parsed only once (for performance speedup for persistent environments, though currently only FastCGI is able to make use of it, when flexibility is not important). You can also have configuration file parsed only once, but have parts of configuration (re)evaluated once per each request. This is done by introducing $per_request_config variable: if set to code reference, this code would be run once per request, while config file would be parsed only once. For example gitolite's contrib/gitweb/gitweb.conf fragment mentioned in 7f425db could be rewritten as our $per_request_config = sub { $ENV{GL_USER} = ($cgi && $cgi->remote_user) || "gitweb"; }; to make use of this feature. If $per_request_config is not a code reference, it is taken to be boolean variable, to choose between running config file for each request (flexibility), and running config file only once (performance in persistent environments). The default value for $per_request_config is 1 (true), which means that old configuration that require to change per session (like gitolite's) will keep working. While at it, make it so evaluate_git_version() is run only once. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/README | 7 +++++++ gitweb/gitweb.perl | 24 ++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) (limited to 'gitweb') diff --git a/gitweb/README b/gitweb/README index bf3664f2b7..6646fdaed0 100644 --- a/gitweb/README +++ b/gitweb/README @@ -246,6 +246,13 @@ not include variables usually directly set during build): http://www.andre-simon.de due to assumptions about parameters and output). Useful if highlight is not installed on your webserver's PATH. [Default: highlight] + * $per_request_config + If set to code reference, it would be run once per each request. You can + set parts of configuration that change per session, e.g. by setting it to + sub { $ENV{GL_USER} = $cgi->remote_user || "gitweb"; } + Otherwise it is treated as boolean value: if true gitweb would process + config file once per request, if false it would process config file only + once. The default is true. Projects list file format ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 679f2da3ee..d8e4263ba9 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -601,6 +601,14 @@ sub filter_snapshot_fmts { !$known_snapshot_formats{$_}{'disabled'}} @fmts; } +# If it is set to code reference, it is code that it is to be run once per +# request, allowing updating configurations that change with each request, +# while running other code in config file only once. +# +# Otherwise, if it is false then gitweb would process config file only once; +# if it is true then gitweb config would be run for each request. +our $per_request_config = 1; + our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM); sub evaluate_gitweb_config { our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; @@ -1070,12 +1078,22 @@ sub reset_timer { our $number_of_git_cmds = 0; } +our $first_request = 1; sub run_request { reset_timer(); evaluate_uri(); - evaluate_gitweb_config(); - evaluate_git_version(); + if ($first_request) { + evaluate_gitweb_config(); + evaluate_git_version(); + } + if ($per_request_config) { + if (ref($per_request_config) eq 'CODE') { + $per_request_config->(); + } elsif (!$first_request) { + evaluate_gitweb_config(); + } + } check_loadavg(); # $projectroot and $projects_list might be set in gitweb config file @@ -1129,6 +1147,7 @@ sub evaluate_argv { sub run { evaluate_argv(); + $first_request = 1; $pre_listen_hook->() if $pre_listen_hook; @@ -1141,6 +1160,7 @@ sub run { $post_dispatch_hook->() if $post_dispatch_hook; + $first_request = 0; last REQUEST if ($is_last_request->()); } -- cgit v1.3 From b3f52a9c3a5d02a6d1a3d05fe35deb90b70de510 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 28 Nov 2010 18:19:08 -0600 Subject: gitweb: document $per_request_config better Global variables $my_url, $my_uri and $base_url have subtle interactions that need to be desribed, and can be influenced most cleanly by $per_request_config. Signed-off-by: Jonathan Nieder Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/README | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'gitweb') diff --git a/gitweb/README b/gitweb/README index 6646fdaed0..4a673933ac 100644 --- a/gitweb/README +++ b/gitweb/README @@ -177,13 +177,15 @@ not include variables usually directly set during build): * $my_url, $my_uri Full URL and absolute URL of gitweb script; in earlier versions of gitweb you might have need to set those - variables, now there should be no need to do it. + variables, now there should be no need to do it. See + $per_request_config if you need to set them still. * $base_url Base URL for relative URLs in pages generated by gitweb, (e.g. $logo, $favicon, @stylesheets if they are relative URLs), needed and used only for URLs with nonempty PATH_INFO via . Usually gitweb sets its value correctly, and there is no need to set this variable, e.g. to $my_uri or "/". + See $per_request_config if you need to set it anyway. * $home_link Target of the home link on top of all pages (the first part of view "breadcrumbs"). By default set to absolute URI of a page ($my_uri). @@ -252,7 +254,10 @@ not include variables usually directly set during build): sub { $ENV{GL_USER} = $cgi->remote_user || "gitweb"; } Otherwise it is treated as boolean value: if true gitweb would process config file once per request, if false it would process config file only - once. The default is true. + once. Note: $my_url, $my_uri, and $base_url are overwritten with + their default values before every request, so if you want to change + them, be sure to set this variable to true or a code reference effecting + the desired changes. The default is true. Projects list file format ~~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.3 From 67976c65e0e931ad73ec21e5effe3dc204003df4 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 14 Dec 2010 16:54:31 +0100 Subject: gitweb: Fix handling of whitespace in generated links When creating path_info part of link, don't encode space as '+', because while $cgi->param('foo') translates '+' in query param to ' ', neither $ENV{'PATH_INFO'} nor $cgi->path_info() do. This fixes the issue with pathnames with embedded whitespace and $feature{'pathinfo'} / path_info links. It is done by using newly introduced esc_path_info() instead of esc_url() in href() subroutine. Also while links are more clear not escaping space (' ') characters in generated links, the trailing space must be URI-encoded, otherwise would get discarded. Issue noticed thanks to John 'Warthog9' Hawley. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index e645d4a821..75cef245a2 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1186,7 +1186,7 @@ sub href { $href =~ s,/$,,; # Then add the project name, if present - $href .= "/".esc_url($params{'project'}); + $href .= "/".esc_path_info($params{'project'}); delete $params{'project'}; # since we destructively absorb parameters, we keep this @@ -1196,7 +1196,8 @@ sub href { # 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'; + $href .= "/".esc_path_info($params{'action'}) + unless $params{'action'} eq 'summary'; delete $params{'action'}; } @@ -1206,13 +1207,13 @@ sub href { || $params{'hash_parent'} || $params{'hash'}); if (defined $params{'hash_base'}) { if (defined $params{'hash_parent_base'}) { - $href .= esc_url($params{'hash_parent_base'}); + $href .= esc_path_info($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'}); + $href .= ":/".esc_path_info($params{'file_parent'}); delete $params{'file_parent'}; } } @@ -1220,19 +1221,19 @@ sub href { delete $params{'hash_parent'}; delete $params{'hash_parent_base'}; } elsif (defined $params{'hash_parent'}) { - $href .= esc_url($params{'hash_parent'}). ".."; + $href .= esc_path_info($params{'hash_parent'}). ".."; delete $params{'hash_parent'}; } - $href .= esc_url($params{'hash_base'}); + $href .= esc_path_info($params{'hash_base'}); if (defined $params{'file_name'} && $params{'file_name'} !~ /\.\./) { - $href .= ":/".esc_url($params{'file_name'}); + $href .= ":/".esc_path_info($params{'file_name'}); delete $params{'file_name'}; } delete $params{'hash'}; delete $params{'hash_base'}; } elsif (defined $params{'hash'}) { - $href .= esc_url($params{'hash'}); + $href .= esc_path_info($params{'hash'}); delete $params{'hash'}; } @@ -1265,6 +1266,9 @@ sub href { } $href .= "?" . join(';', @result) if scalar @result; + # final transformation: trailing spaces must be escaped (URI-encoded) + $href =~ s/(\s+)$/CGI::escape($1)/e; + return $href; } @@ -1347,6 +1351,17 @@ sub esc_param { return $str; } +# the quoting rules for path_info fragment are slightly different +sub esc_path_info { + my $str = shift; + return undef unless defined $str; + + # path_info doesn't treat '+' as space (specially), but '?' must be escaped + $str =~ s/([^A-Za-z0-9\-_.~();\/;:@&= +]+)/CGI::escape($1)/eg; + + return $str; +} + # quote unsafe chars in whole URL, so some characters cannot be quoted sub esc_url { my $str = shift; -- cgit v1.3 From 3017ed62f47ce14a959e2d315c434d4980cf4243 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Wed, 15 Dec 2010 00:34:01 +0100 Subject: gitweb: Introduce esc_attr to escape attributes of HTML elements It is needed only to escape attributes of handcrafted HTML elements, and not those generated using CGI.pm subroutines / methods for HTML generation. While at it, add esc_url and esc_html where needed, and prefer to use CGI.pm HTML generating methods than handcrafted HTML code. Most of those are probably unnecessary (could be exploited only by person with write access to gitweb config, or at least access to the repository). This fixes CVE-2010-3906 Reported-by: Emanuele Gentili Helped-by: John 'Warthog9' Hawley Helped-by: Jonathan Nieder Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 2cb832753a..c3a04b1223 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1084,6 +1084,13 @@ sub esc_url { return $str; } +# quote unsafe characters in HTML attributes +sub esc_attr { + + # for XHTML conformance escaping '"' to '"' is not enough + return esc_html(@_); +} + # replace invalid utf8 character with SUBSTITUTION sequence sub esc_html { my $str = shift; @@ -1489,7 +1496,7 @@ sub format_ref_marker { hash=>$dest )}, $name); - $markers .= " " . + $markers .= " " . $link . ""; } } @@ -1573,7 +1580,7 @@ sub git_get_avatar { return $pre_white . "" . $post_white; } else { @@ -2245,7 +2252,7 @@ sub git_show_project_tagcloud { } else { my @tags = sort { $cloud->{$a}->{count} <=> $cloud->{$b}->{count} } keys %$cloud; return '

' . join (', ', map { - "$cloud->{$_}->{topname}" + $cgi->a({-href=>"$home_link?by_tag=$_"}, $cloud->{$_}->{topname}) } splice(@tags, 0, $count)) . '

'; } } @@ -3061,11 +3068,11 @@ EOF # print out each stylesheet that exist, providing backwards capability # for those people who defined $stylesheet in a config file if (defined $stylesheet) { - print ''."\n"; + print ''."\n"; } else { foreach my $stylesheet (@stylesheets) { next unless $stylesheet; - print ''."\n"; + print ''."\n"; } } if (defined $project) { @@ -3078,7 +3085,7 @@ EOF my $type = lc($format); my %link_attr = ( '-rel' => 'alternate', - '-title' => "$project - $href_params{'-title'} - $format feed", + '-title' => esc_attr("$project - $href_params{'-title'} - $format feed"), '-type' => "application/$type+xml" ); @@ -3105,13 +3112,13 @@ EOF } else { printf(''."\n", - $site_name, href(project=>undef, action=>"project_index")); + esc_attr($site_name), href(project=>undef, action=>"project_index")); printf(''."\n", - $site_name, href(project=>undef, action=>"opml")); + esc_attr($site_name), href(project=>undef, action=>"opml")); } if (defined $favicon) { - print qq(\n); + print qq(\n); } print "\n" . @@ -3124,7 +3131,7 @@ EOF print "
\n" . $cgi->a({-href => esc_url($logo_url), -title => $logo_label}, - qq()); + qq()); print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / "; if (defined $project) { print $cgi->a({-href => href(action=>"summary")}, esc_html($project)); @@ -5016,14 +5023,14 @@ sub git_blob { } else { print "
\n" . "

\n" . - "
$hash
\n"; + "
".esc_html($hash)."
\n"; } git_print_page_path($file_name, "blob", $hash_base); print "
\n"; if ($mimetype =~ m!^image/!) { - print qq!$file_name$hash, @@ -5094,7 +5101,7 @@ sub git_tree { undef $hash_base; print "
\n"; print "

\n"; - print "
$hash
\n"; + print "
".esc_html($hash)."
\n"; } if (defined $file_name) { $basedir = $file_name; @@ -5511,7 +5518,7 @@ sub git_blobdiff { git_print_header_div('commit', esc_html($co{'title'}), $hash_base); } else { print "

$formats_nav
\n"; - print "
$hash vs $hash_parent
\n"; + print "
".esc_html("$hash vs $hash_parent")."
\n"; } if (defined $file_name) { git_print_page_path($file_name, "blob", $hash_base); -- cgit v1.3 From 05bb5a2584ca0e1c87a2135a83c5573b9337d06f Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 18 Dec 2010 21:02:13 +0100 Subject: gitweb: Include links to feeds in HTML header only for '200 OK' response To do that, generating ""s to feeds were refactored into print_feed_meta() subroutine, to keep nesting (indent) level in git_header_html() low. This has also the advantage of making code more clear. Signed-off-by: Jakub Narebski Signed-off-by: John 'Warthog9' Hawley Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 89 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 47 insertions(+), 42 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 53253c622d..42cc1da7ff 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3394,6 +3394,51 @@ sub get_page_title { return $title; } +sub print_feed_meta { + 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' => esc_attr("$project - $href_params{'-title'} - $format feed"), + '-type' => "application/$type+xml" + ); + + $href_params{'action'} = $type; + $link_attr{'-href'} = href(%href_params); + print "\n"; + + $href_params{'extra_options'} = '--no-merges'; + $link_attr{'-href'} = href(%href_params); + $link_attr{'-title'} .= ' (no merges)'; + print "\n"; + } + + } else { + printf(''."\n", + esc_attr($site_name), href(project=>undef, action=>"project_index")); + printf(''."\n", + esc_attr($site_name), href(project=>undef, action=>"opml")); + } +} + sub git_header_html { my $status = shift || "200 OK"; my $expires = shift; @@ -3443,48 +3488,8 @@ EOF print ''."\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' => esc_attr("$project - $href_params{'-title'} - $format feed"), - '-type' => "application/$type+xml" - ); - - $href_params{'action'} = $type; - $link_attr{'-href'} = href(%href_params); - print "\n"; - - $href_params{'extra_options'} = '--no-merges'; - $link_attr{'-href'} = href(%href_params); - $link_attr{'-title'} .= ' (no merges)'; - print "\n"; - } - - } else { - printf(''."\n", - esc_attr($site_name), href(project=>undef, action=>"project_index")); - printf(''."\n", - esc_attr($site_name), href(project=>undef, action=>"opml")); - } + print_feed_meta() + if ($status eq '200 OK'); if (defined $favicon) { print qq(\n); } -- cgit v1.3 From 9d9f5e72dc5959750498acc81b3e2c8ca76ad499 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Fri, 3 Sep 2010 19:44:39 -0500 Subject: gitweb: skip logo in atom feed when there is none With v1.5.0-rc0~169 (gitweb: Fix Atom feed : it is $logo, not $logo_url, 2006-12-04), the logo URI to be written to Atom feeds was corrected but the case of no logo forgotten. Acked-by: Eric Wong Signed-off-by: Jonathan Nieder Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 42cc1da7ff..59b2e08b98 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -6870,7 +6870,7 @@ XML if (defined $favicon) { print "" . esc_url($favicon) . "\n"; } - if (defined $logo_url) { + if (defined $logo) { # not twice as wide as tall: 72 x 27 pixels print "" . esc_url($logo) . "\n"; } -- cgit v1.3 From 6822052427353525fe7158398bc6cf4bbd5d1351 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Fri, 3 Sep 2010 19:45:09 -0500 Subject: gitweb: make logo optional Some sites may not want to have a logo at all. While at it, use $cgi->img to simplify this code. (CGI.pm learned most HTML4 tags by version 2.79, so this should be portable to perl 5.8, though I haven't tested.) Signed-off-by: Jonathan Nieder Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 59b2e08b98..10cf97e66f 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3501,10 +3501,15 @@ EOF insert_file($site_header); } - print "
\n" . - $cgi->a({-href => esc_url($logo_url), - -title => $logo_label}, - qq()); + print "
\n"; + if (defined $logo) { + print $cgi->a({-href => esc_url($logo_url), + -title => $logo_label}, + $cgi->img({-src => esc_url($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)); -- cgit v1.3 From 3ce19eb857c46727239eda3b3dd265d725ea8ddd Mon Sep 17 00:00:00 2001 From: Sylvain Rabot Date: Thu, 30 Dec 2010 22:20:28 +0100 Subject: gitweb: add extensions to highlight feature map added: sql, php5, phps, bash, zsh, ksh, mk, make Signed-off-by: Sylvain Rabot Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c65af1a00a..84f2c1df2c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -250,13 +250,14 @@ our %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), + qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl sql make), # alternate extensions, see /etc/highlight/filetypes.conf 'h' => 'c', + map { $_ => 'sh' } qw(bash zsh ksh), map { $_ => 'cpp' } qw(cxx c++ cc), - map { $_ => 'php' } qw(php3 php4), + map { $_ => 'php' } qw(php3 php4 php5 phps), map { $_ => 'pl' } qw(perl pm), # perhaps also 'cgi' - 'mak' => 'make', + map { $_ => 'make'} qw(mak mk), map { $_ => 'xml' } qw(xhtml html htm), ); -- cgit v1.3 From 3ca7353cab4ed6c7efac0c8d7477c87112fc7350 Mon Sep 17 00:00:00 2001 From: Sylvain Rabot Date: Thu, 30 Dec 2010 22:20:29 +0100 Subject: gitweb: remove unnecessary test when closing file descriptor It happens that closing file descriptor fails whereas the blob is perfectly readable. According to perlman the reasons could be: If the file handle came from a piped open, "close" will additionally return false if one of the other system calls involved fails, or if the program exits with non-zero status. (If the only problem was that the program exited non-zero, $! will be set to 0.) Closing a pipe also waits for the process executing on the pipe to complete, in case you want to look at the output of the pipe afterwards, and implicitly puts the exit status value of that command into $?. Prematurely closing the read end of a pipe (i.e. before the process writ- ing to it at the other end has closed it) will result in a SIGPIPE being delivered to the writer. If the other end can't handle that, be sure to read all the data before closing the pipe. In this case we don't mind that close fails. Signed-off-by: Sylvain Rabot Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 84f2c1df2c..4e3bf4a06b 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3465,8 +3465,7 @@ sub run_highlighter { my ($fd, $highlight, $syntax) = @_; return $fd unless ($highlight && defined $syntax); - close $fd - or die_error(404, "Reading blob failed"); + close $fd; open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ". quote_command($highlight_bin). " --xhtml --fragment --syntax $syntax |" -- cgit v1.3 From d2d434beeb03e4ee648ca7ca2a1ea1ed09077306 Mon Sep 17 00:00:00 2001 From: Adam Tkac Date: Thu, 27 Jan 2011 13:51:51 +0100 Subject: Don't pass "--xhtml" to hightlight in gitweb.perl script. The "--xhtml" option is supported only in highlight < 3.0. There is no option to enforce (X)HTML output format compatible with both highlight < 3.0 and highlight >= 3.0. However default output format is HTML so we don't need to explicitly specify it. Signed-off-by: Adam Tkac Helped-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1025c2f716..0779f12d61 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3468,7 +3468,7 @@ sub run_highlighter { close $fd; open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ". quote_command($highlight_bin). - " --xhtml --fragment --syntax $syntax |" + " --fragment --syntax $syntax |" or die_error(500, "Couldn't open file or run syntax highlighter"); return $fd; } -- cgit v1.3 From 2e9c8789b7b0d4d3cd2f951e1694387686e2d740 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 4 Feb 2011 00:20:48 +0100 Subject: gitweb: Mention optional Perl modules in INSTALL Some optional additional Perl modules are required for some of extra features. Mention those in gitweb/INSTALL. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/INSTALL | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'gitweb') diff --git a/gitweb/INSTALL b/gitweb/INSTALL index 823053173c..4964a679b3 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -237,6 +237,12 @@ Requirements - Perl modules: CGI, Encode, Fcntl, File::Find, File::Basename. - web server +The following optional Perl modules are required for extra features + - Digest::MD5 - for gravatar support + - CGI::Fast and FCGI - for running gitweb as FastCGI script + - HTML::TagCloud - for fancy tag cloud in project list view + - HTTP::Date or Time::ParseDate - to support If-Modified-Since for feeds + Example web server configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.3 From 0f54b7d09ac5b87c327a1e8da43bc48be98f2758 Mon Sep 17 00:00:00 2001 From: Ævar Arnfjörð Bjarmason Date: Sat, 19 Feb 2011 15:27:41 +0000 Subject: gitweb/gitweb.perl: remove use of qw(...) as parentheses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using the qw(...) construct as implicit parentheses was deprecated in perl 5.13.5. Change the relevant code in gitweb to not use the deprecated construct. The offending code was introduced in 3562198b by Jakub Narebski. The issue is that perl will now warn about this: $ perl -wE 'for my $i qw(a b) { say $i }' Use of qw(...) as parentheses is deprecated at -e line 1. a b This caused gitweb.perl to warn on perl 5.13.5 and above, and these tests to fail on those perl versions: ./t9501-gitweb-standalone-http-status.sh (Wstat: 256 Tests: 11 Failed: 10) Failed tests: 2-11 Non-zero exit status: 1 ./t9502-gitweb-standalone-parse-output.sh (Wstat: 256 Tests: 10 Failed: 9) Failed tests: 2-10 Non-zero exit status: 1 Signed-off-by: Ævar Arnfjörð Bjarmason Acked-by: Jakub Narębski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0779f12d61..b02372c3e2 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3501,7 +3501,7 @@ sub print_feed_meta { $href_params{'-title'} = 'log'; } - foreach my $format qw(RSS Atom) { + foreach my $format (qw(RSS Atom)) { my $type = lc($format); my %link_attr = ( '-rel' => 'alternate', @@ -3682,7 +3682,7 @@ sub git_footer_html { } $href_params{'-title'} ||= 'log'; - foreach my $format qw(RSS Atom) { + foreach my $format (qw(RSS Atom)) { $href_params{'action'} = lc($format); print $cgi->a({-href => href(%href_params), -title => "$href_params{'-title'} $format feed", -- cgit v1.3 From 98885c2914b22d70d09302528d86f87e795f7485 Mon Sep 17 00:00:00 2001 From: Ævar Arnfjörð Bjarmason Date: Sat, 19 Feb 2011 15:27:42 +0000 Subject: gitweb/gitweb.perl: don't call S_ISREG() with undef MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change S_ISREG($to_mode_oct) to S_ISREG($from_mode_oct) in the branch that handles from modes, not to modes. This logic appears to have been caused by copy/paste programming by Jakub Narebski in e8e41a93. It would be better to rewrite this code not to be duplicated, but I haven't done so. This issue caused a failing test on perl 5.13.9, which has a warning that turned this up: gitweb.perl: Use of uninitialized value in subroutine entry at /home/avar/g/git/t/../gitweb/gitweb.perl line 4415. Which caused the Git test suite to fail on this test: ./t9500-gitweb-standalone-no-errors.sh (Wstat: 256 Tests: 90 Failed: 84) Failed tests: 1-8, 10-36, 38-45, 47-48, 50-88 Non-zero exit status: 1 Reported-by: perl 5.13.9 Signed-off-by: Ævar Arnfjörð Bjarmason Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b02372c3e2..1b9369d1a7 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -4412,7 +4412,7 @@ sub git_difftree_body { } if ($diff->{'from_mode'} ne ('0' x 6)) { $from_mode_oct = oct $diff->{'from_mode'}; - if (S_ISREG($to_mode_oct)) { # only for regular file + if (S_ISREG($from_mode_oct)) { # only for regular file $from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits } $from_file_type = file_type($diff->{'from_mode'}); -- cgit v1.3 From 6affdbe677c4b2b8547bd9aa471464dd31a94a1c Mon Sep 17 00:00:00 2001 From: Kevin Cernekee Date: Wed, 16 Mar 2011 15:34:13 -0700 Subject: gitweb: highlight: replace tabs with spaces Consider the following code fragment: /* * test */ vim ":set list" mode shows that the first character on each line is a tab: ^I/*$ ^I * test$ ^I */$ By default, the "highlight" program will retain the tabs in the HTML output: $ highlight --fragment --syntax c test.c /* * test */ vim list mode: ^I/*$ ^I * test$ ^I */$ In gitweb, this winds up looking something like: 1 /* 2 * test 3 */ I tried both Firefox and Opera and saw the same behavior. The desired output is: 1 /* 2 * test 3 */ This can be accomplished by specifying "--replace-tabs=8" on the highlight command line. Signed-off-by: Kevin Cernekee Acked-by: John 'Warthog9' Hawley Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1b9369d1a7..b04ab8c9bb 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3468,7 +3468,7 @@ sub run_highlighter { close $fd; open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ". quote_command($highlight_bin). - " --fragment --syntax $syntax |" + " --replace-tabs=8 --fragment --syntax $syntax |" or die_error(500, "Couldn't open file or run syntax highlighter"); return $fd; } -- cgit v1.3 From 5e96a847b391eda693b60347bb190dd45fb1721c Mon Sep 17 00:00:00 2001 From: Kevin Cernekee Date: Fri, 18 Mar 2011 17:00:16 +0100 Subject: gitweb: fix #patchNN anchors when path_info is enabled When $feature{'pathinfo'} is used, gitweb script sets the base URL to itself, so that relative links to static files work correctly. It does it by adding something like below to HTML head: This breaks the "patch" anchor links seen on the commitdiff pages, because these links, being relative (), are resolved (computed) relative to the base URL and not relative to current URL, i.e. as: http://HOST/gitweb.cgi#patch1 Instead, they should look like this: http://HOST/gitweb.cgi/myproject.git/commitdiff/35a9811ef9d68eae9afd76bede121da4f89b448c#patch1 Add an "-anchor" parameter to href(), and use href(-anchor=>"patch1") to generate "patch" anchor links, so that the full path is included in the patch link. While at it, convert print "foo"; print "bar"; to print "foo" . "bar"; in the neighborhood of changes. Signed-off-by: Kevin Cernekee Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b04ab8c9bb..f275adbcf1 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1199,11 +1199,15 @@ if (defined caller) { # -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) +# -anchor => ANCHOR - add #ANCHOR to end of URL, implies -replay if used alone sub href { my %params = @_; # default is to use -absolute url() i.e. $my_uri my $href = $params{-full} ? $my_url : $my_uri; + # implicit -replay, must be first of implicit params + $params{-replay} = 1 if (keys %params == 1 && $params{-anchor}); + $params{'project'} = $project unless exists $params{'project'}; if ($params{-replay}) { @@ -1314,6 +1318,10 @@ sub href { # final transformation: trailing spaces must be escaped (URI-encoded) $href =~ s/(\s+)$/CGI::escape($1)/e; + if ($params{-anchor}) { + $href .= "#".esc_param($params{-anchor}); + } + return $href; } @@ -4335,7 +4343,8 @@ sub git_difftree_body { # link to patch $patchno++; print "" . - $cgi->a({-href => "#patch$patchno"}, "patch") . + $cgi->a({-href => href(-anchor=>"patch$patchno")}, + "patch") . " | " . "\n"; } @@ -4432,8 +4441,9 @@ sub git_difftree_body { if ($action eq 'commitdiff') { # link to patch $patchno++; - print $cgi->a({-href => "#patch$patchno"}, "patch"); - print " | "; + print $cgi->a({-href => href(-anchor=>"patch$patchno")}, + "patch") . + " | "; } print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'}, hash_base=>$hash, file_name=>$diff->{'file'})}, @@ -4452,8 +4462,9 @@ sub git_difftree_body { if ($action eq 'commitdiff') { # link to patch $patchno++; - print $cgi->a({-href => "#patch$patchno"}, "patch"); - print " | "; + print $cgi->a({-href => href(-anchor=>"patch$patchno")}, + "patch") . + " | "; } print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'}, hash_base=>$parent, file_name=>$diff->{'file'})}, @@ -4494,7 +4505,8 @@ sub git_difftree_body { if ($action eq 'commitdiff') { # link to patch $patchno++; - print $cgi->a({-href => "#patch$patchno"}, "patch") . + print $cgi->a({-href => href(-anchor=>"patch$patchno")}, + "patch") . " | "; } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) { # "commit" view and modified file (not onlu mode changed) @@ -4539,7 +4551,8 @@ sub git_difftree_body { if ($action eq 'commitdiff') { # link to patch $patchno++; - print $cgi->a({-href => "#patch$patchno"}, "patch") . + print $cgi->a({-href => href(-anchor=>"patch$patchno")}, + "patch") . " | "; } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) { # "commit" view and modified file (not only pure rename or copy) -- cgit v1.3 From 6368d9f1af7a5d4c8af2f11ffdb6af577f1e98f5 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 19 Mar 2011 23:53:55 +0100 Subject: gitweb: Always call parse_date with timezone parameter Timezone is required to correctly set local time, which would be needed for future 'localtime' feature. While at it, remove unnecessary call to the function from git_log_body, as its return value is not used anywhere. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b04ab8c9bb..9dccfb01d6 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -4906,7 +4906,6 @@ sub git_log_body { next if !%co; my $commit = $co{'id'}; my $ref = format_ref_marker($refs, $commit); - my %ad = parse_date($co{'author_epoch'}); git_print_header_div('commit', "$co{'age_string'}" . esc_html($co{'title'}) . $ref, @@ -7064,7 +7063,7 @@ sub git_feed { if (defined($commitlist[0])) { %latest_commit = %{$commitlist[0]}; my $latest_epoch = $latest_commit{'committer_epoch'}; - %latest_date = parse_date($latest_epoch); + %latest_date = parse_date($latest_epoch, $latest_commit{'comitter_tz'}); my $if_modified = $cgi->http('IF_MODIFIED_SINCE'); if (defined $if_modified) { my $since; @@ -7195,7 +7194,7 @@ XML if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) { last; } - my %cd = parse_date($co{'author_epoch'}); + my %cd = parse_date($co{'author_epoch'}, $co{'author_tz'}); # get list of changed files open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, -- cgit v1.3 From 2b1e17237b0b14855ad8337ec001d6d1fd7cca61 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 25 Mar 2011 20:20:49 +0100 Subject: gitweb: Fix handling of fractional timezones in parse_date Fractional timezones, like -0330 (NST used in Canada) or +0430 (Afghanistan, Iran DST), were not handled properly in parse_date; this means values such as 'minute_local' and 'iso-tz' were not generated correctly. This was caused by two mistakes: * sign of timezone was applied only to hour part of offset, and not as it should be also to minutes part (this affected only negative fractional timezones). * 'int $h + $m/60' is 'int($h + $m/60)' and not 'int($h) + $m/60', so fractional part was discarded altogether ($h is hours, $m is minutes, which is always less than 60). Note that positive fractional timezones +0430, +0530 and +1030 can be found as authortime in git.git repository itself. For example http://repo.or.cz/w/git.git/commit/88d50e7 had authortime of "Fri, 8 Jan 2010 18:48:07 +0000 (23:48 +0530)", which is not marked with 'atnight', when "git show 88d50e7" gives correct author date of "Sat Jan 9 00:18:07 2010 +0530". Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 9dccfb01d6..46186ab909 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2913,8 +2913,10 @@ sub parse_date { $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); + my ($tz_sign, $tz_hour, $tz_min) = + ($tz =~ m/^([-+])(\d\d)(\d\d)$/); + $tz_sign = ($tz_sign eq '-' ? -1 : +1); + my $local = $epoch + $tz_sign*((($tz_hour*60) + $tz_min)*60); ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); $date{'hour_local'} = $hour; $date{'minute_local'} = $min; -- cgit v1.3 From 9029ebbe118dddfccae0dcbb921722257aef2ecd Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 1 Apr 2011 21:06:28 +0200 Subject: gitweb: Fix parsing of negative fractional timezones in JavaScript Extract converting numerical timezone in the form of '(+|-)HHMM' to timezoneOffset function, and fix parsing of negative fractional timezones. This is used to format timestamps in 'blame_incremental' view; this complements commit 2b1e172 (gitweb: Fix handling of fractional timezones in parse_date, 2011-03-25). Now gitweb.cgi/git.git/blame_incremental/3fe5489:/contrib/gitview/gitview#l853 and gitweb.cgi/git.git/blame/3fe5489:/contrib/gitview/gitview#l853 show the same correct time in author's local timezone in title (on mouseover) [Aneesh Kumar K.V, 2006-02-24 00:59:42 +0530]. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/static/gitweb.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) (limited to 'gitweb') diff --git a/gitweb/static/gitweb.js b/gitweb/static/gitweb.js index 9c66928c4a..40ec08440b 100644 --- a/gitweb/static/gitweb.js +++ b/gitweb/static/gitweb.js @@ -399,7 +399,24 @@ function fixColorsAndGroups() { * used to extract hours and minutes from timezone info, e.g '-0900' * @constant */ -var tzRe = /^([+-][0-9][0-9])([0-9][0-9])$/; +var tzRe = /^([+-])([0-9][0-9])([0-9][0-9])$/; + +/** + * convert numeric timezone +/-ZZZZ to offset from UTC in seconds + * + * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' + * @returns {Number} offset from UTC in seconds for timezone + * + * @globals tzRe + */ +function timezoneOffset(timezoneInfo) { + var match = tzRe.exec(timezoneInfo); + var tz_sign = (match[1] === '-' ? -1 : +1); + var tz_hour = parseInt(match[2],10); + var tz_min = parseInt(match[3],10); + + return tz_sign*(((tz_hour*60) + tz_min)*60); +} /** * return date in local time formatted in iso-8601 like format @@ -408,14 +425,11 @@ var tzRe = /^([+-][0-9][0-9])([0-9][0-9])$/; * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' * @returns {String} date in local time in iso-8601 like format - * - * @globals tzRe */ function formatDateISOLocal(epoch, timezoneInfo) { - var match = tzRe.exec(timezoneInfo); // date corrected by timezone var localDate = new Date(1000 * (epoch + - (parseInt(match[1],10)*3600 + parseInt(match[2],10)*60))); + timezoneOffset(timezoneInfo))); var localDateStr = // e.g. '2005-08-07' localDate.getUTCFullYear() + '-' + padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' + -- cgit v1.3 From 0353a0c4ec91cf2d0a8e209025aa2e1909d05f19 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Wed, 13 Apr 2011 17:39:40 +0200 Subject: remove doubled words, e.g., s/to to/to/, and fix related typos I found that some doubled words had snuck back into projects from which I'd already removed them, so now there's a "syntax-check" makefile rule in gnulib to help prevent recurrence. Running the command below spotted a few in git, too: git ls-files | xargs perl -0777 -n \ -e 'while (/\b(then?|[iao]n|i[fst]|but|f?or|at|and|[dt])\s+\1\b/gims)' \ -e '{$n=($` =~ tr/\n/\n/ + 1); ($v=$&)=~s/\n/\\n/g;' \ -e 'print "$ARGV:$n:$v\n"}' Signed-off-by: Jim Meyering Signed-off-by: Junio C Hamano --- Documentation/SubmittingPatches | 3 +-- Documentation/git-fetch.txt | 2 +- Documentation/git-pack-objects.txt | 2 +- compat/mingw.c | 2 +- compat/nedmalloc/malloc.c.h | 10 ++++------ gitweb/README | 2 +- sha1_file.c | 2 +- vcs-svn/trp.txt | 2 +- 8 files changed, 11 insertions(+), 14 deletions(-) (limited to 'gitweb') diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index c3b0816ed7..c6a5032912 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -276,7 +276,7 @@ don't hide your real name. If you like, you can put extra tags at the end: -1. "Reported-by:" is used to to credit someone who found the bug that +1. "Reported-by:" is used to credit someone who found the bug that the patch attempts to fix. 2. "Acked-by:" says that the person who is more familiar with the area the patch attempts to modify liked the patch. @@ -608,4 +608,3 @@ following commands: Just make sure to disable line wrapping in the email client (GMail web interface will line wrap no matter what, so you need to use a real IMAP client). - diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index 67d221467d..60ac8d26eb 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -34,7 +34,7 @@ pointed by remote tags that it does not yet have, then fetch those missing tags. If the other end has tags that point at branches you are not interested in, you will not get them. -'git fetch' can fetch from either a single named repository, or +'git fetch' can fetch from either a single named repository, or from several repositories at once if is given and there is a remotes. entry in the configuration file. (See linkgit:git-config[1]). diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 08c89d221c..20c8551d6a 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -115,7 +115,7 @@ base-name:: --honor-pack-keep:: This flag causes an object already in a local pack that - has a .keep file to be ignored, even if it it would have + has a .keep file to be ignored, even if it would have otherwise been packed. --incremental:: diff --git a/compat/mingw.c b/compat/mingw.c index 878b1de97c..4423961768 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1130,7 +1130,7 @@ char **make_augmented_environ(const char *const *vars) /* * Note, this isn't a complete replacement for getaddrinfo. It assumes - * that service contains a numerical port, or that it it is null. It + * that service contains a numerical port, or that it is null. It * does a simple search using gethostbyname, and returns one IPv4 host * if one was found. */ diff --git a/compat/nedmalloc/malloc.c.h b/compat/nedmalloc/malloc.c.h index 87260d2642..ff7c2c4fd8 100644 --- a/compat/nedmalloc/malloc.c.h +++ b/compat/nedmalloc/malloc.c.h @@ -100,7 +100,7 @@ If you don't like either of these options, you can define CORRUPTION_ERROR_ACTION and USAGE_ERROR_ACTION to do anything - else. And if if you are sure that your program using malloc has + else. And if you are sure that your program using malloc has no errors or vulnerabilities, you can define INSECURE to 1, which might (or might not) provide a small performance improvement. @@ -2279,12 +2279,12 @@ nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ of the same size are arranged in a circularly-linked list, with only the oldest chunk (the next to be used, in our FIFO ordering) actually in the tree. (Tree members are distinguished by a non-null - parent pointer.) If a chunk with the same size an an existing node + parent pointer.) If a chunk with the same size as an existing node is inserted, it is linked off the existing node using pointers that work in the same way as fd/bk pointers of small chunks. Each tree contains a power of 2 sized range of chunk sizes (the - smallest is 0x100 <= x < 0x180), which is is divided in half at each + smallest is 0x100 <= x < 0x180), which is divided in half at each tree level, with the chunks in the smaller half of the range (0x100 <= x < 0x140 for the top nose) in the left subtree and the larger half (0x140 <= x < 0x180) in the right subtree. This is, of course, @@ -3943,7 +3943,7 @@ static void* sys_alloc(mstate m, size_t nb) { least-preferred order): 1. A call to MORECORE that can normally contiguously extend memory. (disabled if not MORECORE_CONTIGUOUS or not HAVE_MORECORE or - or main space is mmapped or a previous contiguous call failed) + main space is mmapped or a previous contiguous call failed) 2. A call to MMAP new space (disabled if not HAVE_MMAP). Note that under the default settings, if MORECORE is unable to fulfill a request, and HAVE_MMAP is true, then mmap is @@ -5748,5 +5748,3 @@ History: structure of old version, but most details differ.) */ - - diff --git a/gitweb/README b/gitweb/README index 4a673933ac..a92bde7f14 100644 --- a/gitweb/README +++ b/gitweb/README @@ -29,7 +29,7 @@ You can specify the following configuration variables when building GIT: The filesystem traversing limit for getting the project list; the number is taken as depth relative to the projectroot. It is used when GITWEB_LIST is a directory (or is not set; then project root is used). - Is is meant to speed up project listing on large work trees by limiting + This is meant to speed up project listing on large work trees by limiting search depth. [Default: 2007] * GITWEB_LIST Points to a directory to scan for projects (defaults to project root diff --git a/sha1_file.c b/sha1_file.c index df0edbad1e..889fe71830 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1534,7 +1534,7 @@ static int unpack_object_header(struct packed_git *p, enum object_type type; /* use_pack() assures us we have [base, base + 20) available - * as a range that we can look at at. (Its actually the hash + * as a range that we can look at. (Its actually the hash * size that is assured.) With our object header encoding * the maximum deflated object size is 2^137, which is just * insane, so we know won't exceed what we have been given. diff --git a/vcs-svn/trp.txt b/vcs-svn/trp.txt index 5ca6b42edb..177ebca335 100644 --- a/vcs-svn/trp.txt +++ b/vcs-svn/trp.txt @@ -96,7 +96,7 @@ node_type *foo_search(struct trp_root \*treap, node_type \*key):: node_type *foo_nsearch(struct trp_root \*treap, node_type \*key):: - Like `foo_search`, but if if the key is missing return what + Like `foo_search`, but if the key is missing return what would be key's successor, were key in treap (NULL if no successor). -- cgit v1.3 From 0c8c385ef188bf76b6edcda87a7cc9bc05596fa3 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Tue, 26 Apr 2011 11:32:00 +1000 Subject: gitweb: supply '-n' to gzip for identical output For projects that do not release official archives, gitweb's snapshot feature would be an excellent alternative, and but without the '-n' ('--no-name') argument, gzip includes a timestamp in output which results in different files. Because some systems hash/checksum downloaded files to ensure integrity of the tarball (e.g FreeBSD), it is desirable to produce tarballs in a reproducible way for that purpose. Whilst '--no-name' is more descriptive, the long version of the flag is not supported on all systems. In particular, OpenBSD does not appear to support it. Supply '-n' to gzip to exclude timestamp from output and produce idential output every time. Signed-off-by: Fraser Tweedale Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ee69ea683a..f8db40a1c2 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -186,7 +186,7 @@ our %known_snapshot_formats = ( 'type' => 'application/x-gzip', 'suffix' => '.tar.gz', 'format' => 'tar', - 'compressor' => ['gzip']}, + 'compressor' => ['gzip', '-n']}, 'tbz2' => { 'display' => 'tar.bz2', -- cgit v1.3 From 12b1443c2cf41174311c81667022c6f3149aa542 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 29 Apr 2011 19:51:56 +0200 Subject: gitweb: Restructure projects list generation Extract filtering out forks (which is done if 'forks' feature is enabled) into filter_forks_from_projects_list subroutine, and searching projects (via projects search form, or via content tags) into search_projects_list subroutine. Both are now run _before_ displaying projects, and not while printing; this allow to know upfront if there were any found projects. Gitweb now can and do print 'No such projects found' if user searches for phrase which does not correspond to any project (any repository). This also would allow splitting projects list into pages, if we so desire. Filtering out forks and marking repository (project) as having forks is now consolidated into one subroutine (special case of handling forks in git_get_projects_list only for $projects_list being file is now removed). Forks handling is also cleaned up and simplified. $pr->{'forks'} now contains un-filled list of forks; we can now also detect situation where the way for having forks is prepared, but there are no forks yet. Sorting projects got also refactored in a very straight way (just moving code) into sort_projects_list subroutine. The interaction between forks, content tags and searching is now made more explicit: searching whether by tag, or via search form turns off fork filtering (gitweb searches also forks, and will show all results). If 'ctags' feature is disabled, then searching by tag is too. The t9500 test now includes some basic test for 'forks' and 'ctags' features; the t9502 includes test checking if gitweb correctly filters out forks. Generating list of projects by scanning given directory is now also a bit simplified wrt. handling filtering; it is byproduct of extracting filtering forks to separate subroutine. While at it we now detect that there are no projects and respond with "404 No projects found" also for 'project_index' and 'opml' actions. Helped-by: Jonathan Nieder Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 250 +++++++++++++++++++++--------- t/t9500-gitweb-standalone-no-errors.sh | 49 ++++++ t/t9502-gitweb-standalone-parse-output.sh | 74 +++++++++ 3 files changed, 296 insertions(+), 77 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ee69ea683a..014b33b50a 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2651,21 +2651,23 @@ sub git_get_project_url_list { } sub git_get_projects_list { - my ($filter) = @_; + my $filter = shift || ''; 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" : ''); + my $dir = $projects_list; # remove the trailing "/" $dir =~ s!/+$!!; - my $pfxlen = length("$dir"); - my $pfxdepth = ($dir =~ tr!/!!); + my $pfxlen = length("$projects_list"); + my $pfxdepth = ($projects_list =~ tr!/!!); + # when filtering, search only given subdirectory + if ($filter) { + $dir .= "/$filter"; + $dir =~ s!/+$!!; + } File::Find::find({ follow_fast => 1, # follow symbolic links @@ -2680,14 +2682,14 @@ sub git_get_projects_list { # only directories can be git repositories return unless (-d $_); # don't traverse too deep (Find is super slow on os x) + # $project_maxdepth excludes depth of $projectroot if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) { $File::Find::prune = 1; return; } - my $subdir = substr($File::Find::name, $pfxlen + 1); + my $path = 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; @@ -2700,7 +2702,6 @@ sub git_get_projects_list { # '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>) { @@ -2711,32 +2712,9 @@ sub git_get_projects_list { 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 $filter is rpovided, check if $path begins with $filter + if ($filter && $path !~ m!^\Q$filter\E/!) { + next; } if (check_export_ok("$projectroot/$path")) { my $pr = { @@ -2744,8 +2722,6 @@ sub git_get_projects_list { owner => to_utf8($owner), }; push @list, $pr; - (my $forks_path = $path) =~ s/\.git$//; - $paths{$forks_path}++; } } close $fd; @@ -2753,6 +2729,98 @@ sub git_get_projects_list { return @list; } +# written with help of Tree::Trie module (Perl Artistic License, GPL compatibile) +# as side effects it sets 'forks' field to list of forks for forked projects +sub filter_forks_from_projects_list { + my $projects = shift; + + my %trie; # prefix tree of directories (path components) + # generate trie out of those directories that might contain forks + foreach my $pr (@$projects) { + my $path = $pr->{'path'}; + $path =~ s/\.git$//; # forks of 'repo.git' are in 'repo/' directory + next if ($path =~ m!/$!); # skip non-bare repositories, e.g. 'repo/.git' + next unless ($path); # skip '.git' repository: tests, git-instaweb + next unless (-d $path); # containing directory exists + $pr->{'forks'} = []; # there can be 0 or more forks of project + + # add to trie + my @dirs = split('/', $path); + # walk the trie, until either runs out of components or out of trie + my $ref = \%trie; + while (scalar @dirs && + exists($ref->{$dirs[0]})) { + $ref = $ref->{shift @dirs}; + } + # create rest of trie structure from rest of components + foreach my $dir (@dirs) { + $ref = $ref->{$dir} = {}; + } + # create end marker, store $pr as a data + $ref->{''} = $pr if (!exists $ref->{''}); + } + + # filter out forks, by finding shortest prefix match for paths + my @filtered; + PROJECT: + foreach my $pr (@$projects) { + # trie lookup + my $ref = \%trie; + DIR: + foreach my $dir (split('/', $pr->{'path'})) { + if (exists $ref->{''}) { + # found [shortest] prefix, is a fork - skip it + push @{$ref->{''}{'forks'}}, $pr; + next PROJECT; + } + if (!exists $ref->{$dir}) { + # not in trie, cannot have prefix, not a fork + push @filtered, $pr; + next PROJECT; + } + # If the dir is there, we just walk one step down the trie. + $ref = $ref->{$dir}; + } + # we ran out of trie + # (shouldn't happen: it's either no match, or end marker) + push @filtered, $pr; + } + + return @filtered; +} + +# note: fill_project_list_info must be run first, +# for 'descr_long' and 'ctags' to be filled +sub search_projects_list { + my ($projlist, %opts) = @_; + my $tagfilter = $opts{'tagfilter'}; + my $searchtext = $opts{'searchtext'}; + + return @$projlist + unless ($tagfilter || $searchtext); + + my @projects; + PROJECT: + foreach my $pr (@$projlist) { + + if ($tagfilter) { + next unless ref($pr->{'ctags'}) eq 'HASH'; + next unless + grep { lc($_) eq lc($tagfilter) } keys %{$pr->{'ctags'}}; + } + + if ($searchtext) { + next unless + $pr->{'path'} =~ /$searchtext/ || + $pr->{'descr_long'} =~ /$searchtext/; + } + + push @projects, $pr; + } + + return @projects; +} + our $gitweb_project_owner = undef; sub git_get_project_list_from_file { @@ -4742,7 +4810,7 @@ sub git_patchset_body { # project in the list, removing invalid projects from returned list # NOTE: modifies $projlist, but does not remove entries from it sub fill_project_list_info { - my ($projlist, $check_forks) = @_; + my $projlist = shift; my @projects; my $show_ctags = gitweb_check_feature('ctags'); @@ -4762,23 +4830,36 @@ sub fill_project_list_info { if (!defined $pr->{'owner'}) { $pr->{'owner'} = git_get_project_owner("$pr->{'path'}") || ""; } - if ($check_forks) { - my $pname = $pr->{'path'}; - if (($pname =~ s/\.git$//) && - ($pname !~ /\/$/) && - (-d "$projectroot/$pname")) { - $pr->{'forks'} = "-d $projectroot/$pname"; - } else { - $pr->{'forks'} = 0; - } + if ($show_ctags) { + $pr->{'ctags'} = git_get_project_ctags($pr->{'path'}); } - $show_ctags and $pr->{'ctags'} = git_get_project_ctags($pr->{'path'}); push @projects, $pr; } return @projects; } +sub sort_projects_list { + my ($projlist, $order) = @_; + my @projects; + + my %order_info = ( + project => { key => 'path', type => 'str' }, + descr => { key => 'descr_long', type => 'str' }, + owner => { key => 'owner', type => 'str' }, + age => { key => 'age', type => 'num' } + ); + my $oi = $order_info{$order}; + return @$projlist unless defined $oi; + if ($oi->{'type'} eq 'str') { + @projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @$projlist; + } else { + @projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @$projlist; + } + + return @projects; +} + # print 'sort by' element, generating 'sort by $name' replay link # if that order is not selected sub print_sort_th { @@ -4805,28 +4886,39 @@ sub format_sort_th { sub git_project_list_body { # actually uses global variable $project my ($projlist, $order, $from, $to, $extra, $no_header) = @_; + my @projects = @$projlist; my $check_forks = gitweb_check_feature('forks'); - my @projects = fill_project_list_info($projlist, $check_forks); + my $show_ctags = gitweb_check_feature('ctags'); + my $tagfilter = $show_ctags ? $cgi->param('by_tag') : undef; + $check_forks = undef + if ($tagfilter || $searchtext); + + # filtering out forks before filling info allows to do less work + @projects = filter_forks_from_projects_list(\@projects) + if ($check_forks); + @projects = fill_project_list_info(\@projects); + # searching projects require filling to be run before it + @projects = search_projects_list(\@projects, + 'searchtext' => $searchtext, + 'tagfilter' => $tagfilter) + if ($tagfilter || $searchtext); $order ||= $default_projects_order; $from = 0 unless defined $from; $to = $#projects if (!defined $to || $#projects < $to); - my %order_info = ( - project => { key => 'path', type => 'str' }, - descr => { key => 'descr_long', type => 'str' }, - owner => { key => 'owner', type => 'str' }, - age => { key => 'age', type => 'num' } - ); - my $oi = $order_info{$order}; - if ($oi->{'type'} eq 'str') { - @projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @projects; - } else { - @projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects; + # short circuit + if ($from > $to) { + print "
\n". + "No such projects found
\n". + "Click ".$cgi->a({-href=>href(project=>undef)},"here")." to view all projects
\n". + "
\n
\n"; + return; } - my $show_ctags = gitweb_check_feature('ctags'); + @projects = sort_projects_list(\@projects, $order); + if ($show_ctags) { my %ctags; foreach my $p (@projects) { @@ -4852,32 +4944,26 @@ sub git_project_list_body { "\n"; } my $alternate = 1; - my $tagfilter = $cgi->param('by_tag'); for (my $i = $from; $i <= $to; $i++) { my $pr = $projects[$i]; - next if $tagfilter and $show_ctags and not grep { lc $_ eq lc $tagfilter } keys %{$pr->{'ctags'}}; - next if $searchtext and not $pr->{'path'} =~ /$searchtext/ - and not $pr->{'descr_long'} =~ /$searchtext/; - # Weed out forks or non-matching entries of search - if ($check_forks) { - my $forkbase = $project; $forkbase ||= ''; $forkbase =~ s#\.git$#/#; - $forkbase="^$forkbase" if $forkbase; - next if not $searchtext and not $tagfilter and $show_ctags - and $pr->{'path'} =~ m#$forkbase.*/.*#; # regexp-safe - } - if ($alternate) { print "\n"; } else { print "\n"; } $alternate ^= 1; + if ($check_forks) { print ""; if ($pr->{'forks'}) { - print "\n"; - print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+"); + my $nforks = scalar @{$pr->{'forks'}}; + if ($nforks > 0) { + print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks"), + -title => "$nforks forks"}, "+"); + } else { + print $cgi->span({-title => "$nforks forks"}, "+"); + } } print "\n"; } @@ -5357,7 +5443,10 @@ sub git_forks { } sub git_project_index { - my @projects = git_get_projects_list($project); + my @projects = git_get_projects_list(); + if (!@projects) { + die_error(404, "No projects found"); + } print $cgi->header( -type => 'text/plain', @@ -5399,7 +5488,11 @@ sub git_summary { my $check_forks = gitweb_check_feature('forks'); if ($check_forks) { + # find forks of a project @forklist = git_get_projects_list($project); + # filter out forks of forks + @forklist = filter_forks_from_projects_list(\@forklist) + if (@forklist); } git_header_html(); @@ -7319,6 +7412,9 @@ sub git_atom { sub git_opml { my @list = git_get_projects_list(); + if (!@list) { + die_error(404, "No projects found"); + } print $cgi->header( -type => 'text/xml', diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index afac5b56a8..71ef0acb1b 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -595,4 +595,53 @@ test_expect_success HIGHLIGHT \ git commit -m "Add test.sh" && gitweb_run "p=.git;a=blob;f=test.sh"' +# ---------------------------------------------------------------------- +# forks of projects + +cat >>gitweb_config.perl <<\EOF && +$feature{'forks'}{'default'} = [1]; +EOF + +test_expect_success \ + 'forks: prepare' \ + 'git init --bare foo.git && + git --git-dir=foo.git --work-tree=. add file && + git --git-dir=foo.git --work-tree=. commit -m "Initial commit" && + echo "foo" > foo.git/description && + mkdir -p foo && + (cd foo && + git clone --shared --bare ../foo.git foo-forked.git && + echo "fork of foo" > foo-forked.git/description)' + +test_expect_success \ + 'forks: projects list' \ + 'gitweb_run' + +test_expect_success \ + 'forks: forks action' \ + 'gitweb_run "p=foo.git;a=forks"' + +# ---------------------------------------------------------------------- +# content tags (tag cloud) + +cat >>gitweb_config.perl <<-\EOF && +# we don't test _setting_ content tags, so any true value is good +$feature{'ctags'}{'default'} = ['ctags_script.cgi']; +EOF + +test_expect_success \ + 'ctags: tag cloud in projects list' \ + 'mkdir .git/ctags && + echo "2" > .git/ctags/foo && + echo "1" > .git/ctags/bar && + gitweb_run' + +test_expect_success \ + 'ctags: search projects by existing tag' \ + 'gitweb_run "by_tag=foo"' + +test_expect_success \ + 'ctags: search projects by non existent tag' \ + 'gitweb_run "by_tag=non-existent"' + test_done diff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh index dd83890001..731e64c3ad 100755 --- a/t/t9502-gitweb-standalone-parse-output.sh +++ b/t/t9502-gitweb-standalone-parse-output.sh @@ -112,4 +112,78 @@ test_expect_success 'snapshot: hierarchical branch name (xx/test)' ' ' test_debug 'cat gitweb.headers' +# ---------------------------------------------------------------------- +# forks of projects + +test_expect_success 'forks: setup' ' + git init --bare foo.git && + echo file > file && + git --git-dir=foo.git --work-tree=. add file && + git --git-dir=foo.git --work-tree=. commit -m "Initial commit" && + echo "foo" > foo.git/description && + git clone --bare foo.git foo.bar.git && + echo "foo.bar" > foo.bar.git/description && + git clone --bare foo.git foo_baz.git && + echo "foo_baz" > foo_baz.git/description && + rm -fr foo && + mkdir -p foo && + ( + cd foo && + git clone --shared --bare ../foo.git foo-forked.git && + echo "fork of foo" > foo-forked.git/description + ) +' + +test_expect_success 'forks: not skipped unless "forks" feature enabled' ' + gitweb_run "a=project_list" && + grep -q ">\\.git<" gitweb.body && + grep -q ">foo\\.git<" gitweb.body && + grep -q ">foo_baz\\.git<" gitweb.body && + grep -q ">foo\\.bar\\.git<" gitweb.body && + grep -q ">foo_baz\\.git<" gitweb.body && + grep -q ">foo/foo-forked\\.git<" gitweb.body && + grep -q ">fork of .*<" gitweb.body +' + +cat >>gitweb_config.perl <<\EOF && +$feature{'forks'}{'default'} = [1]; +EOF + +test_expect_success 'forks: forks skipped if "forks" feature enabled' ' + gitweb_run "a=project_list" && + grep -q ">\\.git<" gitweb.body && + grep -q ">foo\\.git<" gitweb.body && + grep -q ">foo_baz\\.git<" gitweb.body && + grep -q ">foo\\.bar\\.git<" gitweb.body && + grep -q ">foo_baz\\.git<" gitweb.body && + grep -v ">foo/foo-forked\\.git<" gitweb.body && + grep -v ">fork of .*<" gitweb.body +' + +test_expect_success 'forks: "forks" action for forked repository' ' + gitweb_run "p=foo.git;a=forks" && + grep -q ">foo/foo-forked\\.git<" gitweb.body && + grep -q ">fork of foo<" gitweb.body +' + +test_expect_success 'forks: can access forked repository' ' + gitweb_run "p=foo/foo-forked.git;a=summary" && + grep -q "200 OK" gitweb.headers && + grep -q ">fork of foo<" gitweb.body +' + +test_expect_success 'forks: project_index lists all projects (incl. forks)' ' + cat >expected <<-\EOF + .git + foo.bar.git + foo.git + foo/foo-forked.git + foo_baz.git + EOF + gitweb_run "a=project_index" && + sed -e "s/ .*//" actual && + test_cmp expected actual +' + + test_done -- cgit v1.3 From 0fa920c35c2617d7c1756b1e2bf5c56a81db3232 Mon Sep 17 00:00:00 2001 From: Sebastien Cevey Date: Fri, 29 Apr 2011 19:51:59 +0200 Subject: gitweb: Split git_project_list_body in two functions Extract the printing of project rows (body/contents of projects list table) on the 'project_list' page into a separate git_project_list_rows function. This makes it easier to reuse the code to print different subsets of the whole project list. [jn: Updated to post restructuring projects list generation] Signed-off-by: Sebastien Cevey Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 89 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 39 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index e81c27d14c..f8d57223d5 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -4946,6 +4946,55 @@ sub format_sort_th { return $sort_th; } +sub git_project_list_rows { + my ($projlist, $from, $to, $check_forks) = @_; + + $from = 0 unless defined $from; + $to = $#$projlist if (!defined $to || $#$projlist < $to); + + my $alternate = 1; + for (my $i = $from; $i <= $to; $i++) { + my $pr = $projlist->[$i]; + + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + + if ($check_forks) { + print ""; + if ($pr->{'forks'}) { + my $nforks = scalar @{$pr->{'forks'}}; + if ($nforks > 0) { + print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks"), + -title => "$nforks forks"}, "+"); + } else { + print $cgi->span({-title => "$nforks forks"}, "+"); + } + } + print "\n"; + } + print "" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), + -class => "list"}, esc_html($pr->{'path'})) . "\n" . + "" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), + -class => "list", -title => $pr->{'descr_long'}}, + esc_html($pr->{'descr'})) . "\n" . + "" . chop_and_escape_str($pr->{'owner'}, 15) . "\n"; + print "{'age'}) . "\">" . + (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "\n" . + "" . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") . + ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') . + "\n" . + "\n"; + } +} + sub git_project_list_body { # actually uses global variable $project my ($projlist, $order, $from, $to, $extra, $no_header) = @_; @@ -5001,47 +5050,9 @@ sub git_project_list_body { print "\n" . # for links "\n"; } - my $alternate = 1; - for (my $i = $from; $i <= $to; $i++) { - my $pr = $projects[$i]; - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; + git_project_list_rows(\@projects, $from, $to, $check_forks); - if ($check_forks) { - print ""; - if ($pr->{'forks'}) { - my $nforks = scalar @{$pr->{'forks'}}; - if ($nforks > 0) { - print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks"), - -title => "$nforks forks"}, "+"); - } else { - print $cgi->span({-title => "$nforks forks"}, "+"); - } - } - print "\n"; - } - print "" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), - -class => "list"}, esc_html($pr->{'path'})) . "\n" . - "" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), - -class => "list", -title => $pr->{'descr_long'}}, - esc_html($pr->{'descr'})) . "\n" . - "" . chop_and_escape_str($pr->{'owner'}, 15) . "\n"; - print "{'age'}) . "\">" . - (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "\n" . - "" . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") . - ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') . - "\n" . - "\n"; - } if (defined $extra) { print "\n"; if ($check_forks) { -- cgit v1.3 From 0368c492d664775924388b18494eeb4690692ab8 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 29 Apr 2011 19:51:57 +0200 Subject: gitweb: Change the way "content tags" ('ctags') are handled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The major change is removing the ability to edit content tags (ctags) in a web browser. The interface was created by gitweb, while actual editing of tags was to be done by external script; the API was not defined, and neither was provided example implementation. Such split is also a bit fragile - interface and implementation have to be kept in sync. Gitweb provided only ability to add tags; you could not edit tags nor delete them. Format of ctags is now described in the comment above git_get_project_ctags subroutine. Gitweb now is more robust with respect to original ctags format; it also accepts two new formats: $GIT_DIR/ctags file, with one content tag per line, and multi-value `gitweb.ctag' config variable. Gathering all ctags of all project is now put in git_gather_all_ctags subroutine, making git_project_list_body more clear. git_populate_project_tagcloud subroutine now generates data used for tag cloud, including generation of ctag link, also in the case HTML::TagCloud module is unavailable. Links are now generated using href() subroutine - this is more robust, as ctags might contain '?', ';' and '=' special characters that need to be escaped in query param. Shown tags are HTML-escaped. The generation of tag cloud in git_show_project_tagcloud in the case when HTML::TagCloud is not available is now changed slightly. The 'content tags' field on project summary page is made more in line with other fields in "projects_list" table. Because one cannot now add new tags from web interface, this field is no longer displayed when there are no content tags for given project. Ctags-issue-Reported-by: Uwe Kleine-König Ctags-issue-Reported-by: Jonathan Nieder Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 141 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 97 insertions(+), 44 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 014b33b50a..60cb772c58 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -412,20 +412,23 @@ our %feature = ( '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. + # Allow gitweb scan project content tags of project repository, + # and display the popular Web 2.0-ish "tag cloud" near the projects + # 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. + # tagging itself; you need to do it externally, outside gitweb. + # The format is described in git_get_project_ctags() subroutine. # 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']; + # $feature{'ctags'}{'default'} = [1]; # Project specific override is not supported. + + # In the future whether ctags editing is enabled might depend + # on the value, but using 1 should always mean no editing of ctags. 'ctags' => { 'override' => 0, 'default' => [0]}, @@ -703,6 +706,7 @@ our @cgi_param_mapping = ( snapshot_format => "sf", extra_options => "opt", search_use_regexp => "sr", + ctag => "by_tag", # this must be last entry (for manipulation from JavaScript) javascript => "js" ); @@ -2572,23 +2576,66 @@ sub git_get_project_description { return $descr; } +# supported formats: +# * $GIT_DIR/ctags/ file (in 'ctags' subdirectory) +# - if its contents is a number, use it as tag weight, +# - otherwise add a tag with weight 1 +# * $GIT_DIR/ctags file, each line is a tag (with weight 1) +# the same value multiple times increases tag weight +# * `gitweb.ctag' multi-valued repo config variable sub git_get_project_ctags { - my $path = shift; + my $project = 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; + $git_dir = "$projectroot/$project"; + if (opendir my $dh, "$git_dir/ctags") { + my @files = grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh); + foreach my $tagfile (@files) { + open my $ct, '<', $tagfile + or next; + my $val = <$ct>; + chomp $val if $val; + close $ct; + + (my $ctag = $tagfile) =~ s#.*/##; + if ($val =~ /\d+/) { + $ctags->{$ctag} = $val; + } else { + $ctags->{$ctag} = 1; + } + } + closedir $dh; + + } elsif (open my $fh, '<', "$git_dir/ctags") { + while (my $line = <$fh>) { + chomp $line; + $ctags->{$line}++ if $line; + } + close $fh; + + } else { + my $taglist = config_to_multi(git_get_project_config('ctag')); + foreach my $tag (@$taglist) { + $ctags->{$tag}++; + } } - closedir $dh; - $ctags; + + return $ctags; +} + +# return hash, where keys are content tags ('ctags'), +# and values are sum of weights of given tag in every project +sub git_gather_all_ctags { + my $projects = shift; + my $ctags = {}; + + foreach my $p (@$projects) { + foreach my $ct (keys %{$p->{'ctags'}}) { + $ctags->{$ct} += $p->{'ctags'}->{$ct}; + } + } + + return $ctags; } sub git_populate_project_tagcloud { @@ -2608,31 +2655,41 @@ sub git_populate_project_tagcloud { my $cloud; if (eval { require HTML::TagCloud; 1; }) { $cloud = HTML::TagCloud->new; - foreach (sort keys %ctags_lc) { + foreach my $ctag (sort keys %ctags_lc) { # Pad the title with spaces so that the cloud looks # less crammed. - my $title = $ctags_lc{$_}->{topname}; + my $title = esc_html($ctags_lc{$ctag}->{topname}); $title =~ s/ / /g; $title =~ s/^/ /g; $title =~ s/$/ /g; - $cloud->add($title, $home_link."?by_tag=".$_, $ctags_lc{$_}->{count}); + $cloud->add($title, href(project=>undef, ctag=>$ctag), + $ctags_lc{$ctag}->{count}); } } else { - $cloud = \%ctags_lc; + $cloud = {}; + foreach my $ctag (keys %ctags_lc) { + my $title = $ctags_lc{$ctag}->{topname}; + $cloud->{$ctag}{count} = $ctags_lc{$ctag}->{count}; + $cloud->{$ctag}{ctag} = + $cgi->a({-href=>href(project=>undef, ctag=>$ctag)}, + esc_html($title, -nbsp=>1)); + } } - $cloud; + return $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 '

' . join (', ', map { - $cgi->a({-href=>"$home_link?by_tag=$_"}, $cloud->{$_}->{topname}) - } splice(@tags, 0, $count)) . '

'; + my @tags = sort { $cloud->{$a}->{'count'} <=> $cloud->{$b}->{'count'} } keys %$cloud; + return + '
' . + join (', ', map { + $cloud->{$_}->{'ctag'} + } splice(@tags, 0, $count)) . + '
'; } } @@ -4920,13 +4977,8 @@ sub git_project_list_body { @projects = sort_projects_list(\@projects, $order); if ($show_ctags) { - my %ctags; - foreach my $p (@projects) { - foreach my $ct (keys %{$p->{'ctags'}}) { - $ctags{$ct} += $p->{'ctags'}->{$ct}; - } - } - my $cloud = git_populate_project_tagcloud(\%ctags); + my $ctags = git_gather_all_ctags(\@projects); + my $cloud = git_populate_project_tagcloud($ctags); print git_show_project_tagcloud($cloud, 64); } @@ -5521,13 +5573,14 @@ sub git_summary { my $show_ctags = gitweb_check_feature('ctags'); if ($show_ctags) { my $ctags = git_get_project_ctags($project); - my $cloud = git_populate_project_tagcloud($ctags); - print "Content tags:
"; - print "\n" unless %$ctags; - print "
Add:
"; - print "\n" if %$ctags; - print git_show_project_tagcloud($cloud, 48); - print ""; + if (%$ctags) { + # without ability to add tags, don't show if there are none + my $cloud = git_populate_project_tagcloud($ctags); + print "" . + "content tags" . + "".git_show_project_tagcloud($cloud, 48)."" . + "\n"; + } } print "\n"; -- cgit v1.3 From e4e3b32bd2317ab73f03d692ab3f7231e2e6787d Mon Sep 17 00:00:00 2001 From: Sebastien Cevey Date: Fri, 29 Apr 2011 19:52:00 +0200 Subject: gitweb: Modularized git_get_project_description to be more generic Introduce a git_get_file_or_project_config utility function to retrieve a repository variable either from a plain text file in the $GIT_DIR or else from 'gitweb.$variable' in the repository config (e.g. 'description'). This would be used in next commit to retrieve category for a project, which is to be stored in the same way as project description. Signed-off-by: Sebastien Cevey Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index f8d57223d5..e8685acea3 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2562,18 +2562,26 @@ sub git_get_path_by_hash { ## ...................................................................... ## git utility functions, directly accessing git repository -sub git_get_project_description { - my $path = shift; +# get the value of config variable either from file named as the variable +# itself in the repository ($GIT_DIR/$name file), or from gitweb.$name +# configuration variable in the repository config file. +sub git_get_file_or_project_config { + my ($path, $name) = @_; $git_dir = "$projectroot/$path"; - open my $fd, '<', "$git_dir/description" - or return git_get_project_config('description'); - my $descr = <$fd>; + open my $fd, '<', "$git_dir/$name" + or return git_get_project_config($name); + my $conf = <$fd>; close $fd; - if (defined $descr) { - chomp $descr; + if (defined $conf) { + chomp $conf; } - return $descr; + return $conf; +} + +sub git_get_project_description { + my $path = shift; + return git_get_file_or_project_config($path, 'description'); } # supported formats: -- cgit v1.3 From 4b9447f98ef8699450fa0276d212167a25667d7f Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 29 Apr 2011 19:51:58 +0200 Subject: gitweb: Mark matched 'ctag' / contents tag (?by_tag=foo) It might have been hard to discover that current view is limited to projects with given content tag (ctag), as it was distinquished only in gitweb URL. Mark matched contents tag in the tag cloud using "match" class, for easier discovery. This commit introduces a bit of further code duplication in git_populate_project_tagcloud(). Signed-off-by: Jakub Narebski Acked-by: Petr Baudis Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 60cb772c58..e81c27d14c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2653,6 +2653,7 @@ sub git_populate_project_tagcloud { } my $cloud; + my $matched = $cgi->param('by_tag'); if (eval { require HTML::TagCloud; 1; }) { $cloud = HTML::TagCloud->new; foreach my $ctag (sort keys %ctags_lc) { @@ -2662,17 +2663,22 @@ sub git_populate_project_tagcloud { $title =~ s/ / /g; $title =~ s/^/ /g; $title =~ s/$/ /g; + if (defined $matched && $matched eq $ctag) { + $title = qq($title); + } $cloud->add($title, href(project=>undef, ctag=>$ctag), $ctags_lc{$ctag}->{count}); } } else { $cloud = {}; foreach my $ctag (keys %ctags_lc) { - my $title = $ctags_lc{$ctag}->{topname}; + my $title = esc_html($ctags_lc{$ctag}->{topname}, -nbsp=>1); + if (defined $matched && $matched eq $ctag) { + $title = qq($title); + } $cloud->{$ctag}{count} = $ctags_lc{$ctag}->{count}; $cloud->{$ctag}{ctag} = - $cgi->a({-href=>href(project=>undef, ctag=>$ctag)}, - esc_html($title, -nbsp=>1)); + $cgi->a({-href=>href(project=>undef, ctag=>$ctag)}, $title); } } return $cloud; -- cgit v1.3 From d940c9015d1a770b4f8ed8e9b38cf3d16f47a6cd Mon Sep 17 00:00:00 2001 From: Sebastien Cevey Date: Fri, 29 Apr 2011 19:52:01 +0200 Subject: gitweb: Optional grouping of projects by category This adds the $projects_list_group_categories option which, if enabled, will result in grouping projects by category on the project list page. The category is specified for each project by the $GIT_DIR/category file or the 'gitweb.category' variable in its configuration file. By default, projects are put in the $project_list_default_category category. Note: - Categories are always sorted alphabetically, with projects in each category sorted according to the globally selected $order. - When displaying a subset of all the projects (page limiting), the category headers are only displayed for projects present on the page. The feature is inspired from Sham Chukoury's patch for the XMMS2 gitweb, but has been rewritten for the current gitweb code. The CSS for categories is inspired from Gustavo Sverzut Barbieri's patch to group projects by path. Thanks to Florian Ragwitz for Perl tips. [jn: Updated to post restructuring projects list generation, fixed bugs, added very basic test in t9500 that there are no warnings from Perl.] Signed-off-by: Sebastien Cevey Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/README | 16 +++++++++ gitweb/gitweb.perl | 62 ++++++++++++++++++++++++++++++++-- gitweb/static/gitweb.css | 7 ++++ t/t9500-gitweb-standalone-no-errors.sh | 8 +++++ 4 files changed, 90 insertions(+), 3 deletions(-) (limited to 'gitweb') diff --git a/gitweb/README b/gitweb/README index a92bde7f14..a3a697bf55 100644 --- a/gitweb/README +++ b/gitweb/README @@ -207,6 +207,15 @@ not include variables usually directly set during build): full description is available as 'title' attribute (usually shown on mouseover). By default set to 25, which might be too small if you use long project descriptions. + * $projects_list_group_categories + Enables the grouping of projects by category on the project list page. + The category of a project is determined by the $GIT_DIR/category + file or the 'gitweb.category' variable in its repository configuration. + Disabled by default. + * $project_list_default_category + Default category for projects for which none is specified. If set + to the empty string, such projects will remain uncategorized and + listed at the top, above categorized projects. * @git_base_url_list List of git base URLs used for URL to where fetch project from, shown in project summary page. Full URL is "$git_base_url/$project". @@ -314,6 +323,13 @@ You can use the following files in repository: from the template during repository creation. You can use the gitweb.description repo configuration variable, but the file takes precedence. + * category (or gitweb.category) + Singe line category of a project, used to group projects if + $projects_list_group_categories is enabled. By default (file and + configuration variable absent), uncategorized projects are put in + the $project_list_default_category category. You can use the + gitweb.category repo configuration variable, but the file takes + precedence. * cloneurl (or multiple-valued gitweb.url) File with repository URL (used for clone and fetch), one per line. Displayed in the project summary page. You can use multiple-valued diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index e8685acea3..f78fdd7c5f 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -115,6 +115,14 @@ our $projects_list = "++GITWEB_LIST++"; # the width (in characters) of the projects list "Description" column our $projects_list_description_width = 25; +# group projects by category on the projects list +# (enabled if this variable evaluates to true) +our $projects_list_group_categories = 0; + +# default category if none specified +# (leave the empty string for no category) +our $project_list_default_category = ""; + # default order of projects list # valid values are none, project, descr, owner, and age our $default_projects_order = "project"; @@ -2584,6 +2592,12 @@ sub git_get_project_description { return git_get_file_or_project_config($path, 'description'); } +sub git_get_project_category { + my $path = shift; + return git_get_file_or_project_config($path, 'category'); +} + + # supported formats: # * $GIT_DIR/ctags/ file (in 'ctags' subdirectory) # - if its contents is a number, use it as tag weight, @@ -4877,8 +4891,9 @@ sub git_patchset_body { # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -# fills project list info (age, description, owner, forks) for each -# project in the list, removing invalid projects from returned list +# fills project list info (age, description, owner, category, forks) +# for each project in the list, removing invalid projects from +# returned list # NOTE: modifies $projlist, but does not remove entries from it sub fill_project_list_info { my $projlist = shift; @@ -4904,6 +4919,12 @@ sub fill_project_list_info { if ($show_ctags) { $pr->{'ctags'} = git_get_project_ctags($pr->{'path'}); } + if ($projects_list_group_categories && !defined $pr->{'category'}) { + my $cat = git_get_project_category($pr->{'path'}) || + $project_list_default_category; + $pr->{'category'} = to_utf8($cat); + } + push @projects, $pr; } @@ -4931,6 +4952,23 @@ sub sort_projects_list { return @projects; } +# returns a hash of categories, containing the list of project +# belonging to each category +sub build_projlist_by_category { + my ($projlist, $from, $to) = @_; + my %categories; + + $from = 0 unless defined $from; + $to = $#$projlist if (!defined $to || $#$projlist < $to); + + for (my $i = $from; $i <= $to; $i++) { + my $pr = $projlist->[$i]; + push @{$categories{ $pr->{'category'} }}, $pr; + } + + return wantarray ? %categories : \%categories; +} + # print 'sort by' element, generating 'sort by $name' replay link # if that order is not selected sub print_sort_th { @@ -5059,7 +5097,25 @@ sub git_project_list_body { "\n"; } - git_project_list_rows(\@projects, $from, $to, $check_forks); + if ($projects_list_group_categories) { + # only display categories with projects in the $from-$to window + @projects = sort {$a->{'category'} cmp $b->{'category'}} @projects[$from..$to]; + my %categories = build_projlist_by_category(\@projects, $from, $to); + foreach my $cat (sort keys %categories) { + unless ($cat eq "") { + print "\n"; + if ($check_forks) { + print "\n"; + } + print "".esc_html($cat)."\n"; + print "\n"; + } + + git_project_list_rows($categories{$cat}, undef, undef, $check_forks); + } + } else { + git_project_list_rows(\@projects, $from, $to, $check_forks); + } if (defined $extra) { print "\n"; diff --git a/gitweb/static/gitweb.css b/gitweb/static/gitweb.css index 79d7eebba7..4df2d163c9 100644 --- a/gitweb/static/gitweb.css +++ b/gitweb/static/gitweb.css @@ -295,6 +295,13 @@ td.current_head { text-decoration: underline; } +td.category { + background-color: #d9d8d1; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + font-weight: bold; +} + table.diff_tree span.file_status.new { color: #008000; } diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 71ef0acb1b..f5648a6694 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -644,4 +644,12 @@ test_expect_success \ 'ctags: search projects by non existent tag' \ 'gitweb_run "by_tag=non-existent"' +# ---------------------------------------------------------------------- +# categories + +test_expect_success \ + 'categories: projects list, only default category' \ + 'echo "\$projects_list_group_categories = 1;" >>gitweb_config.perl && + gitweb_run' + test_done -- cgit v1.3 From 9a86dd571058a511e60af35206a31017d873e54a Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:01 +0200 Subject: gitweb: Split JavaScript for maintability, combining on build Split originally single gitweb.js file into smaller files, each dealing with single issue / area of responsibility. This move should make gitweb's JavaScript code easier to maintain. For better webapp performance it is recommended[1][2][3] to combine JavaScript files. Do it during build time (in gitweb/Makefile), by straight concatenation of files into gitweb.js file (which is now ignored as being generated). This means that there are no changes to gitweb script itself - it still uses gitweb.js or gitweb.min.js, but now generated. [1]: http://developer.yahoo.com/performance/rules.html "Minimize HTTP Requests" section [2]: http://code.google.com/speed/articles/include-scripts-properly.html "1. Combine external JavaScript files" [3]: http://javascript-reference.info/speed-up-your-javascript-load-time.htm "Combine Your Files" section. See also new gitweb/static/js/README file. Inspired-by-patch-by: John 'Warthog9' Hawley Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- .gitignore | 1 + gitweb/Makefile | 16 +- gitweb/static/gitweb.js | 889 ------------------------------- gitweb/static/js/README | 20 + gitweb/static/js/blame_incremental.js | 687 ++++++++++++++++++++++++ gitweb/static/js/javascript-detection.js | 43 ++ gitweb/static/js/lib/common-lib.js | 187 +++++++ 7 files changed, 953 insertions(+), 890 deletions(-) delete mode 100644 gitweb/static/gitweb.js create mode 100644 gitweb/static/js/README create mode 100644 gitweb/static/js/blame_incremental.js create mode 100644 gitweb/static/js/javascript-detection.js create mode 100644 gitweb/static/js/lib/common-lib.js (limited to 'gitweb') diff --git a/.gitignore b/.gitignore index 2cf3ca5808..9e9500540e 100644 --- a/.gitignore +++ b/.gitignore @@ -158,6 +158,7 @@ /gitk-git/gitk-wish /gitweb/GITWEB-BUILD-OPTIONS /gitweb/gitweb.cgi +/gitweb/static/gitweb.js /gitweb/static/gitweb.min.* /test-chmtime /test-ctype diff --git a/gitweb/Makefile b/gitweb/Makefile index 0a6ac00631..0baa9df9f6 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -86,7 +86,7 @@ ifndef V endif endif -all:: gitweb.cgi +all:: gitweb.cgi static/gitweb.js GITWEB_PROGRAMS = gitweb.cgi @@ -112,6 +112,15 @@ endif GITWEB_FILES += static/git-logo.png static/git-favicon.png +# JavaScript files that are composed (concatenated) to form gitweb.js +# +# js/lib/common-lib.js should be always first, then js/lib/*.js, +# then the rest of files; js/gitweb.js should be last (if it exists) +GITWEB_JSLIB_FILES += static/js/lib/common-lib.js +GITWEB_JSLIB_FILES += static/js/javascript-detection.js +GITWEB_JSLIB_FILES += static/js/blame_incremental.js + + GITWEB_REPLACE = \ -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ -e 's|++GIT_BINDIR++|$(bindir)|g' \ @@ -146,6 +155,11 @@ gitweb.cgi: gitweb.perl GITWEB-BUILD-OPTIONS chmod +x $@+ && \ mv $@+ $@ +static/gitweb.js: $(GITWEB_JSLIB_FILES) + $(QUIET_GEN)$(RM) $@ $@+ && \ + cat $^ >$@+ && \ + mv $@+ $@ + ### Testing rules test: diff --git a/gitweb/static/gitweb.js b/gitweb/static/gitweb.js deleted file mode 100644 index 40ec08440b..0000000000 --- a/gitweb/static/gitweb.js +++ /dev/null @@ -1,889 +0,0 @@ -// Copyright (C) 2007, Fredrik Kuivinen -// 2007, Petr Baudis -// 2008-2009, Jakub Narebski - -/** - * @fileOverview JavaScript code for gitweb (git web interface). - * @license GPLv2 or later - */ - -/* ============================================================ */ -/* functions for generic gitweb actions and views */ - -/** - * used to check if link has 'js' query parameter already (at end), - * and other reasons to not add 'js=1' param at the end of link - * @constant - */ -var jsExceptionsRe = /[;?]js=[01]$/; - -/** - * Add '?js=1' or ';js=1' to the end of every link in the document - * that doesn't have 'js' query parameter set already. - * - * Links with 'js=1' lead to JavaScript version of given action, if it - * exists (currently there is only 'blame_incremental' for 'blame') - * - * @globals jsExceptionsRe - */ -function fixLinks() { - var allLinks = document.getElementsByTagName("a") || document.links; - for (var i = 0, len = allLinks.length; i < len; i++) { - var link = allLinks[i]; - if (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/; - link.href += - (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1'; - } - } -} - - -/* ============================================================ */ - -/* - * This code uses DOM methods instead of (nonstandard) innerHTML - * to modify page. - * - * innerHTML is non-standard IE extension, though supported by most - * browsers; however Firefox up to version 1.5 didn't implement it in - * a strict mode (application/xml+xhtml mimetype). - * - * Also my simple benchmarks show that using elem.firstChild.data = - * 'content' is slightly faster than elem.innerHTML = 'content'. It - * is however more fragile (text element fragment must exists), and - * less feature-rich (we cannot add HTML). - * - * Note that DOM 2 HTML is preferred over generic DOM 2 Core; the - * equivalent using DOM 2 Core is usually shown in comments. - */ - - -/* ============================================================ */ -/* generic utility functions */ - - -/** - * pad number N with nonbreakable spaces on the left, to WIDTH characters - * example: padLeftStr(12, 3, '\u00A0') == '\u00A012' - * ('\u00A0' is nonbreakable space) - * - * @param {Number|String} input: number to pad - * @param {Number} width: visible width of output - * @param {String} str: string to prefix to string, e.g. '\u00A0' - * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR - */ -function padLeftStr(input, width, str) { - var prefix = ''; - - width -= input.toString().length; - while (width > 0) { - prefix += str; - width--; - } - return prefix + input; -} - -/** - * Pad INPUT on the left to SIZE width, using given padding character CH, - * for example padLeft('a', 3, '_') is '__a'. - * - * @param {String} input: input value converted to string. - * @param {Number} width: desired length of output. - * @param {String} ch: single character to prefix to string. - * - * @returns {String} Modified string, at least SIZE length. - */ -function padLeft(input, width, ch) { - var s = input + ""; - while (s.length < width) { - s = ch + s; - } - return s; -} - -/** - * Create XMLHttpRequest object in cross-browser way - * @returns XMLHttpRequest object, or null - */ -function createRequestObject() { - try { - return new XMLHttpRequest(); - } catch (e) {} - try { - return window.createRequest(); - } catch (e) {} - try { - return new ActiveXObject("Msxml2.XMLHTTP"); - } catch (e) {} - try { - return new ActiveXObject("Microsoft.XMLHTTP"); - } catch (e) {} - - return null; -} - - -/* ============================================================ */ -/* utility/helper functions (and variables) */ - -var xhr; // XMLHttpRequest object -var projectUrl; // partial query + separator ('?' or ';') - -// 'commits' is an associative map. It maps SHA1s to Commit objects. -var commits = {}; - -/** - * constructor for Commit objects, used in 'blame' - * @class Represents a blamed commit - * @param {String} sha1: SHA-1 identifier of a commit - */ -function Commit(sha1) { - if (this instanceof Commit) { - this.sha1 = sha1; - this.nprevious = 0; /* number of 'previous', effective parents */ - } else { - return new Commit(sha1); - } -} - -/* ............................................................ */ -/* progress info, timing, error reporting */ - -var blamedLines = 0; -var totalLines = '???'; -var div_progress_bar; -var div_progress_info; - -/** - * Detects how many lines does a blamed file have, - * This information is used in progress info - * - * @returns {Number|String} Number of lines in file, or string '...' - */ -function countLines() { - var table = - document.getElementById('blame_table') || - document.getElementsByTagName('table')[0]; - - if (table) { - return table.getElementsByTagName('tr').length - 1; // for header - } else { - return '...'; - } -} - -/** - * update progress info and length (width) of progress bar - * - * @globals div_progress_info, div_progress_bar, blamedLines, totalLines - */ -function updateProgressInfo() { - if (!div_progress_info) { - div_progress_info = document.getElementById('progress_info'); - } - if (!div_progress_bar) { - div_progress_bar = document.getElementById('progress_bar'); - } - if (!div_progress_info && !div_progress_bar) { - return; - } - - var percentage = Math.floor(100.0*blamedLines/totalLines); - - if (div_progress_info) { - div_progress_info.firstChild.data = blamedLines + ' / ' + totalLines + - ' (' + padLeftStr(percentage, 3, '\u00A0') + '%)'; - } - - if (div_progress_bar) { - //div_progress_bar.setAttribute('style', 'width: '+percentage+'%;'); - div_progress_bar.style.width = percentage + '%'; - } -} - - -var t_interval_server = ''; -var cmds_server = ''; -var t0 = new Date(); - -/** - * write how much it took to generate data, and to run script - * - * @globals t0, t_interval_server, cmds_server - */ -function writeTimeInterval() { - var info_time = document.getElementById('generating_time'); - if (!info_time || !t_interval_server) { - return; - } - var t1 = new Date(); - info_time.firstChild.data += ' + (' + - t_interval_server + ' sec server blame_data / ' + - (t1.getTime() - t0.getTime())/1000 + ' sec client JavaScript)'; - - var info_cmds = document.getElementById('generating_cmd'); - if (!info_time || !cmds_server) { - return; - } - info_cmds.firstChild.data += ' + ' + cmds_server; -} - -/** - * show an error message alert to user within page (in prohress info area) - * @param {String} str: plain text error message (no HTML) - * - * @globals div_progress_info - */ -function errorInfo(str) { - if (!div_progress_info) { - div_progress_info = document.getElementById('progress_info'); - } - if (div_progress_info) { - div_progress_info.className = 'error'; - div_progress_info.firstChild.data = str; - } -} - -/* ............................................................ */ -/* coloring rows during blame_data (git blame --incremental) run */ - -/** - * used to extract N from 'colorN', where N is a number, - * @constant - */ -var colorRe = /\bcolor([0-9]*)\b/; - -/** - * return N if , otherwise return null - * (some browsers require CSS class names to begin with letter) - * - * @param {HTMLElement} tr: table row element to check - * @param {String} tr.className: 'class' attribute of tr element - * @returns {Number|null} N if tr.className == 'colorN', otherwise null - * - * @globals colorRe - */ -function getColorNo(tr) { - if (!tr) { - return null; - } - var className = tr.className; - if (className) { - var match = colorRe.exec(className); - if (match) { - return parseInt(match[1], 10); - } - } - return null; -} - -var colorsFreq = [0, 0, 0]; -/** - * return one of given possible colors (curently least used one) - * example: chooseColorNoFrom(2, 3) returns 2 or 3 - * - * @param {Number[]} arguments: one or more numbers - * assumes that 1 <= arguments[i] <= colorsFreq.length - * @returns {Number} Least used color number from arguments - * @globals colorsFreq - */ -function chooseColorNoFrom() { - // choose the color which is least used - var colorNo = arguments[0]; - for (var i = 1; i < arguments.length; i++) { - if (colorsFreq[arguments[i]-1] < colorsFreq[colorNo-1]) { - colorNo = arguments[i]; - } - } - colorsFreq[colorNo-1]++; - return colorNo; -} - -/** - * given two neigbour elements, find color which would be different - * from color of both of neighbours; used to 3-color blame table - * - * @param {HTMLElement} tr_prev - * @param {HTMLElement} tr_next - * @returns {Number} color number N such that - * colorN != tr_prev.className && colorN != tr_next.className - */ -function findColorNo(tr_prev, tr_next) { - var color_prev = getColorNo(tr_prev); - var color_next = getColorNo(tr_next); - - - // neither of neighbours has color set - // THEN we can use any of 3 possible colors - if (!color_prev && !color_next) { - return chooseColorNoFrom(1,2,3); - } - - // either both neighbours have the same color, - // or only one of neighbours have color set - // THEN we can use any color except given - var color; - if (color_prev === color_next) { - color = color_prev; // = color_next; - } else if (!color_prev) { - color = color_next; - } else if (!color_next) { - color = color_prev; - } - if (color) { - return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1); - } - - // neighbours have different colors - // THEN there is only one color left - return (3 - ((color_prev + color_next) % 3)); -} - -/* ............................................................ */ -/* coloring rows like 'blame' after 'blame_data' finishes */ - -/** - * returns true if given row element (tr) is first in commit group - * to be used only after 'blame_data' finishes (after processing) - * - * @param {HTMLElement} tr: table row - * @returns {Boolean} true if TR is first in commit group - */ -function isStartOfGroup(tr) { - return tr.firstChild.className === 'sha1'; -} - -/** - * change colors to use zebra coloring (2 colors) instead of 3 colors - * concatenate neighbour commit groups belonging to the same commit - * - * @globals colorRe - */ -function fixColorsAndGroups() { - var colorClasses = ['light', 'dark']; - var linenum = 1; - var tr, prev_group; - var colorClass = 0; - var table = - document.getElementById('blame_table') || - document.getElementsByTagName('table')[0]; - - while ((tr = document.getElementById('l'+linenum))) { - // index origin is 0, which is table header; start from 1 - //while ((tr = table.rows[linenum])) { // <- it is slower - if (isStartOfGroup(tr, linenum, document)) { - if (prev_group && - prev_group.firstChild.firstChild.href === - tr.firstChild.firstChild.href) { - // we have to concatenate groups - var prev_rows = prev_group.firstChild.rowSpan || 1; - var curr_rows = tr.firstChild.rowSpan || 1; - prev_group.firstChild.rowSpan = prev_rows + curr_rows; - //tr.removeChild(tr.firstChild); - tr.deleteCell(0); // DOM2 HTML way - } else { - colorClass = (colorClass + 1) % 2; - prev_group = tr; - } - } - var tr_class = tr.className; - tr.className = tr_class.replace(colorRe, colorClasses[colorClass]); - linenum++; - } -} - -/* ............................................................ */ -/* time and data */ - -/** - * used to extract hours and minutes from timezone info, e.g '-0900' - * @constant - */ -var tzRe = /^([+-])([0-9][0-9])([0-9][0-9])$/; - -/** - * convert numeric timezone +/-ZZZZ to offset from UTC in seconds - * - * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' - * @returns {Number} offset from UTC in seconds for timezone - * - * @globals tzRe - */ -function timezoneOffset(timezoneInfo) { - var match = tzRe.exec(timezoneInfo); - var tz_sign = (match[1] === '-' ? -1 : +1); - var tz_hour = parseInt(match[2],10); - var tz_min = parseInt(match[3],10); - - return tz_sign*(((tz_hour*60) + tz_min)*60); -} - -/** - * return date in local time formatted in iso-8601 like format - * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200' - * - * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' - * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' - * @returns {String} date in local time in iso-8601 like format - */ -function formatDateISOLocal(epoch, timezoneInfo) { - // date corrected by timezone - var localDate = new Date(1000 * (epoch + - timezoneOffset(timezoneInfo))); - var localDateStr = // e.g. '2005-08-07' - localDate.getUTCFullYear() + '-' + - padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' + - padLeft(localDate.getUTCDate(), 2, '0'); - var localTimeStr = // e.g. '21:49:46' - padLeft(localDate.getUTCHours(), 2, '0') + ':' + - padLeft(localDate.getUTCMinutes(), 2, '0') + ':' + - padLeft(localDate.getUTCSeconds(), 2, '0'); - - return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo; -} - -/* ............................................................ */ -/* unquoting/unescaping filenames */ - -/**#@+ - * @constant - */ -var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g; -var octEscRe = /^[0-7]{1,3}$/; -var maybeQuotedRe = /^\"(.*)\"$/; -/**#@-*/ - -/** - * unquote maybe git-quoted filename - * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a a' - * - * @param {String} str: git-quoted string - * @returns {String} Unquoted and unescaped string - * - * @globals escCodeRe, octEscRe, maybeQuotedRe - */ -function unquote(str) { - function unq(seq) { - var es = { - // character escape codes, aka escape sequences (from C) - // replacements are to some extent JavaScript specific - t: "\t", // tab (HT, TAB) - n: "\n", // newline (NL) - r: "\r", // return (CR) - f: "\f", // form feed (FF) - b: "\b", // backspace (BS) - a: "\x07", // alarm (bell) (BEL) - e: "\x1B", // escape (ESC) - v: "\v" // vertical tab (VT) - }; - - if (seq.search(octEscRe) !== -1) { - // octal char sequence - return String.fromCharCode(parseInt(seq, 8)); - } else if (seq in es) { - // C escape sequence, aka character escape code - return es[seq]; - } - // quoted ordinary character - return seq; - } - - var match = str.match(maybeQuotedRe); - if (match) { - str = match[1]; - // perhaps str = eval('"'+str+'"'); would be enough? - str = str.replace(escCodeRe, - function (substr, p1, offset, s) { return unq(p1); }); - } - return str; -} - -/* ============================================================ */ -/* main part: parsing response */ - -/** - * Function called for each blame entry, as soon as it finishes. - * It updates page via DOM manipulation, adding sha1 info, etc. - * - * @param {Commit} commit: blamed commit - * @param {Object} group: object representing group of lines, - * which blame the same commit (blame entry) - * - * @globals blamedLines - */ -function handleLine(commit, group) { - /* - This is the structure of the HTML fragment we are working - with: - - -
- 123 - # times (my ext3 doesn't). - - */ - - var resline = group.resline; - - // format date and time string only once per commit - if (!commit.info) { - /* e.g. 'Kay Sievers, 2005-08-07 21:49:46 +0200' */ - commit.info = commit.author + ', ' + - formatDateISOLocal(commit.authorTime, commit.authorTimezone); - } - - // color depends on group of lines, not only on blamed commit - var colorNo = findColorNo( - document.getElementById('l'+(resline-1)), - document.getElementById('l'+(resline+group.numlines)) - ); - - // loop over lines in commit group - for (var i = 0; i < group.numlines; i++, resline++) { - var tr = document.getElementById('l'+resline); - if (!tr) { - break; - } - /* - - - 123 - # times (my ext3 doesn't). - - */ - var td_sha1 = tr.firstChild; - var a_sha1 = td_sha1.firstChild; - var a_linenr = td_sha1.nextSibling.firstChild; - - /* */ - var tr_class = ''; - if (colorNo !== null) { - tr_class = 'color'+colorNo; - } - if (commit.boundary) { - tr_class += ' boundary'; - } - if (commit.nprevious === 0) { - tr_class += ' no-previous'; - } else if (commit.nprevious > 1) { - tr_class += ' multiple-previous'; - } - tr.className = tr_class; - - /* ? */ - if (i === 0) { - td_sha1.title = commit.info; - td_sha1.rowSpan = group.numlines; - - a_sha1.href = projectUrl + 'a=commit;h=' + commit.sha1; - if (a_sha1.firstChild) { - a_sha1.firstChild.data = commit.sha1.substr(0, 8); - } else { - a_sha1.appendChild( - document.createTextNode(commit.sha1.substr(0, 8))); - } - if (group.numlines >= 2) { - var fragment = document.createDocumentFragment(); - var br = document.createElement("br"); - var match = commit.author.match(/\b([A-Z])\B/g); - if (match) { - var text = document.createTextNode( - match.join('')); - } - if (br && text) { - var elem = fragment || td_sha1; - elem.appendChild(br); - elem.appendChild(text); - if (fragment) { - td_sha1.appendChild(fragment); - } - } - } - } else { - //tr.removeChild(td_sha1); // DOM2 Core way - tr.deleteCell(0); // DOM2 HTML way - } - - /* 123 */ - var linenr_commit = - ('previous' in commit ? commit.previous : commit.sha1); - var linenr_filename = - ('file_parent' in commit ? commit.file_parent : commit.filename); - a_linenr.href = projectUrl + 'a=blame_incremental' + - ';hb=' + linenr_commit + - ';f=' + encodeURIComponent(linenr_filename) + - '#l' + (group.srcline + i); - - blamedLines++; - - //updateProgressInfo(); - } -} - -// ---------------------------------------------------------------------- - -var inProgress = false; // are we processing response - -/**#@+ - * @constant - */ -var sha1Re = /^([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/; -var infoRe = /^([a-z-]+) ?(.*)/; -var endRe = /^END ?([^ ]*) ?(.*)/; -/**@-*/ - -var curCommit = new Commit(); -var curGroup = {}; - -var pollTimer = null; - -/** - * Parse output from 'git blame --incremental [...]', received via - * XMLHttpRequest from server (blamedataUrl), and call handleLine - * (which updates page) as soon as blame entry is completed. - * - * @param {String[]} lines: new complete lines from blamedata server - * - * @globals commits, curCommit, curGroup, t_interval_server, cmds_server - * @globals sha1Re, infoRe, endRe - */ -function processBlameLines(lines) { - var match; - - for (var i = 0, len = lines.length; i < len; i++) { - - if ((match = sha1Re.exec(lines[i]))) { - var sha1 = match[1]; - var srcline = parseInt(match[2], 10); - var resline = parseInt(match[3], 10); - var numlines = parseInt(match[4], 10); - - var c = commits[sha1]; - if (!c) { - c = new Commit(sha1); - commits[sha1] = c; - } - curCommit = c; - - curGroup.srcline = srcline; - curGroup.resline = resline; - curGroup.numlines = numlines; - - } else if ((match = infoRe.exec(lines[i]))) { - var info = match[1]; - var data = match[2]; - switch (info) { - case 'filename': - curCommit.filename = unquote(data); - // 'filename' information terminates the entry - handleLine(curCommit, curGroup); - updateProgressInfo(); - break; - case 'author': - curCommit.author = data; - break; - case 'author-time': - curCommit.authorTime = parseInt(data, 10); - break; - case 'author-tz': - curCommit.authorTimezone = data; - break; - case 'previous': - curCommit.nprevious++; - // store only first 'previous' header - if (!'previous' in curCommit) { - var parts = data.split(' ', 2); - curCommit.previous = parts[0]; - curCommit.file_parent = unquote(parts[1]); - } - break; - case 'boundary': - curCommit.boundary = true; - break; - } // end switch - - } else if ((match = endRe.exec(lines[i]))) { - t_interval_server = match[1]; - cmds_server = match[2]; - - } else if (lines[i] !== '') { - // malformed line - - } // end if (match) - - } // end for (lines) -} - -/** - * Process new data and return pointer to end of processed part - * - * @param {String} unprocessed: new data (from nextReadPos) - * @param {Number} nextReadPos: end of last processed data - * @return {Number} end of processed data (new value for nextReadPos) - */ -function processData(unprocessed, nextReadPos) { - var lastLineEnd = unprocessed.lastIndexOf('\n'); - if (lastLineEnd !== -1) { - var lines = unprocessed.substring(0, lastLineEnd).split('\n'); - nextReadPos += lastLineEnd + 1 /* 1 == '\n'.length */; - - processBlameLines(lines); - } // end if - - return nextReadPos; -} - -/** - * Handle XMLHttpRequest errors - * - * @param {XMLHttpRequest} xhr: XMLHttpRequest object - * - * @globals pollTimer, commits, inProgress - */ -function handleError(xhr) { - errorInfo('Server error: ' + - xhr.status + ' - ' + (xhr.statusText || 'Error contacting server')); - - clearInterval(pollTimer); - commits = {}; // free memory - - inProgress = false; -} - -/** - * Called after XMLHttpRequest finishes (loads) - * - * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused) - * - * @globals pollTimer, commits, inProgress - */ -function responseLoaded(xhr) { - clearInterval(pollTimer); - - fixColorsAndGroups(); - writeTimeInterval(); - commits = {}; // free memory - - inProgress = false; -} - -/** - * handler for XMLHttpRequest onreadystatechange event - * @see startBlame - * - * @globals xhr, inProgress - */ -function handleResponse() { - - /* - * xhr.readyState - * - * Value Constant (W3C) Description - * ------------------------------------------------------------------- - * 0 UNSENT open() has not been called yet. - * 1 OPENED send() has not been called yet. - * 2 HEADERS_RECEIVED send() has been called, and headers - * and status are available. - * 3 LOADING Downloading; responseText holds partial data. - * 4 DONE The operation is complete. - */ - - if (xhr.readyState !== 4 && xhr.readyState !== 3) { - return; - } - - // the server returned error - // try ... catch block is to work around bug in IE8 - try { - if (xhr.readyState === 3 && xhr.status !== 200) { - return; - } - } catch (e) { - return; - } - if (xhr.readyState === 4 && xhr.status !== 200) { - handleError(xhr); - return; - } - - // In konqueror xhr.responseText is sometimes null here... - if (xhr.responseText === null) { - return; - } - - // in case we were called before finished processing - if (inProgress) { - return; - } else { - inProgress = true; - } - - // extract new whole (complete) lines, and process them - while (xhr.prevDataLength !== xhr.responseText.length) { - if (xhr.readyState === 4 && - xhr.prevDataLength === xhr.responseText.length) { - break; - } - - xhr.prevDataLength = xhr.responseText.length; - var unprocessed = xhr.responseText.substring(xhr.nextReadPos); - xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos); - } // end while - - // did we finish work? - if (xhr.readyState === 4 && - xhr.prevDataLength === xhr.responseText.length) { - responseLoaded(xhr); - } - - inProgress = false; -} - -// ============================================================ -// ------------------------------------------------------------ - -/** - * Incrementally update line data in blame_incremental view in gitweb. - * - * @param {String} blamedataUrl: URL to server script generating blame data. - * @param {String} bUrl: partial URL to project, used to generate links. - * - * Called from 'blame_incremental' view after loading table with - * file contents, a base for blame view. - * - * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer -*/ -function startBlame(blamedataUrl, bUrl) { - - xhr = createRequestObject(); - if (!xhr) { - errorInfo('ERROR: XMLHttpRequest not supported'); - return; - } - - t0 = new Date(); - projectUrl = bUrl + (bUrl.indexOf('?') === -1 ? '?' : ';'); - if ((div_progress_bar = document.getElementById('progress_bar'))) { - //div_progress_bar.setAttribute('style', 'width: 100%;'); - div_progress_bar.style.cssText = 'width: 100%;'; - } - totalLines = countLines(); - updateProgressInfo(); - - /* add extra properties to xhr object to help processing response */ - xhr.prevDataLength = -1; // used to detect if we have new data - xhr.nextReadPos = 0; // where unread part of response starts - - xhr.onreadystatechange = handleResponse; - //xhr.onreadystatechange = function () { handleResponse(xhr); }; - - xhr.open('GET', blamedataUrl); - xhr.setRequestHeader('Accept', 'text/plain'); - xhr.send(null); - - // not all browsers call onreadystatechange event on each server flush - // poll response using timer every second to handle this issue - pollTimer = setInterval(xhr.onreadystatechange, 1000); -} - -// end of gitweb.js diff --git a/gitweb/static/js/README b/gitweb/static/js/README new file mode 100644 index 0000000000..f8460ed32f --- /dev/null +++ b/gitweb/static/js/README @@ -0,0 +1,20 @@ +GIT web interface (gitweb) - JavaScript +======================================= + +This directory holds JavaScript code used by gitweb (GIT web interface). +Scripts from there would be concatenated together in the order specified +by gitweb/Makefile into gitweb/static/gitweb.js, during building of +gitweb/gitweb.cgi (during gitweb building). The resulting file (or its +minification) would then be installed / deployed together with gitweb. + +Scripts in 'lib/' subdirectory compose generic JavaScript library, +providing features required by gitweb but in no way limited to gitweb +only. In the future those scripts could be replaced by some JavaScript +library / framework, like e.g. jQuery, YUI, Prototype, MooTools, Dojo, +ExtJS, Script.aculo.us or SproutCore. + +All scripts that manipulate gitweb output should be put outside 'lib/', +directly in this directory ('gitweb/static/js/'). Those scripts would +have to be rewritten if gitweb moves to using some JavaScript library. + +See also comments in gitweb/Makefile. diff --git a/gitweb/static/js/blame_incremental.js b/gitweb/static/js/blame_incremental.js new file mode 100644 index 0000000000..f63f78b9ec --- /dev/null +++ b/gitweb/static/js/blame_incremental.js @@ -0,0 +1,687 @@ +// Copyright (C) 2007, Fredrik Kuivinen +// 2007, Petr Baudis +// 2008-2011, Jakub Narebski + +/** + * @fileOverview JavaScript side of Ajax-y 'blame_incremental' view in gitweb + * @license GPLv2 or later + */ + + +/* + * This code uses DOM methods instead of (nonstandard) innerHTML + * to modify page. + * + * innerHTML is non-standard IE extension, though supported by most + * browsers; however Firefox up to version 1.5 didn't implement it in + * a strict mode (application/xml+xhtml mimetype). + * + * Also my simple benchmarks show that using elem.firstChild.data = + * 'content' is slightly faster than elem.innerHTML = 'content'. It + * is however more fragile (text element fragment must exists), and + * less feature-rich (we cannot add HTML). + * + * Note that DOM 2 HTML is preferred over generic DOM 2 Core; the + * equivalent using DOM 2 Core is usually shown in comments. + */ + + +/* ============================================================ */ +/* utility/helper functions (and variables) */ + +var xhr; // XMLHttpRequest object +var projectUrl; // partial query + separator ('?' or ';') + +// 'commits' is an associative map. It maps SHA1s to Commit objects. +var commits = {}; + +/** + * constructor for Commit objects, used in 'blame' + * @class Represents a blamed commit + * @param {String} sha1: SHA-1 identifier of a commit + */ +function Commit(sha1) { + if (this instanceof Commit) { + this.sha1 = sha1; + this.nprevious = 0; /* number of 'previous', effective parents */ + } else { + return new Commit(sha1); + } +} + +/* ............................................................ */ +/* progress info, timing, error reporting */ + +var blamedLines = 0; +var totalLines = '???'; +var div_progress_bar; +var div_progress_info; + +/** + * Detects how many lines does a blamed file have, + * This information is used in progress info + * + * @returns {Number|String} Number of lines in file, or string '...' + */ +function countLines() { + var table = + document.getElementById('blame_table') || + document.getElementsByTagName('table')[0]; + + if (table) { + return table.getElementsByTagName('tr').length - 1; // for header + } else { + return '...'; + } +} + +/** + * update progress info and length (width) of progress bar + * + * @globals div_progress_info, div_progress_bar, blamedLines, totalLines + */ +function updateProgressInfo() { + if (!div_progress_info) { + div_progress_info = document.getElementById('progress_info'); + } + if (!div_progress_bar) { + div_progress_bar = document.getElementById('progress_bar'); + } + if (!div_progress_info && !div_progress_bar) { + return; + } + + var percentage = Math.floor(100.0*blamedLines/totalLines); + + if (div_progress_info) { + div_progress_info.firstChild.data = blamedLines + ' / ' + totalLines + + ' (' + padLeftStr(percentage, 3, '\u00A0') + '%)'; + } + + if (div_progress_bar) { + //div_progress_bar.setAttribute('style', 'width: '+percentage+'%;'); + div_progress_bar.style.width = percentage + '%'; + } +} + + +var t_interval_server = ''; +var cmds_server = ''; +var t0 = new Date(); + +/** + * write how much it took to generate data, and to run script + * + * @globals t0, t_interval_server, cmds_server + */ +function writeTimeInterval() { + var info_time = document.getElementById('generating_time'); + if (!info_time || !t_interval_server) { + return; + } + var t1 = new Date(); + info_time.firstChild.data += ' + (' + + t_interval_server + ' sec server blame_data / ' + + (t1.getTime() - t0.getTime())/1000 + ' sec client JavaScript)'; + + var info_cmds = document.getElementById('generating_cmd'); + if (!info_time || !cmds_server) { + return; + } + info_cmds.firstChild.data += ' + ' + cmds_server; +} + +/** + * show an error message alert to user within page (in prohress info area) + * @param {String} str: plain text error message (no HTML) + * + * @globals div_progress_info + */ +function errorInfo(str) { + if (!div_progress_info) { + div_progress_info = document.getElementById('progress_info'); + } + if (div_progress_info) { + div_progress_info.className = 'error'; + div_progress_info.firstChild.data = str; + } +} + +/* ............................................................ */ +/* coloring rows during blame_data (git blame --incremental) run */ + +/** + * used to extract N from 'colorN', where N is a number, + * @constant + */ +var colorRe = /\bcolor([0-9]*)\b/; + +/** + * return N if , otherwise return null + * (some browsers require CSS class names to begin with letter) + * + * @param {HTMLElement} tr: table row element to check + * @param {String} tr.className: 'class' attribute of tr element + * @returns {Number|null} N if tr.className == 'colorN', otherwise null + * + * @globals colorRe + */ +function getColorNo(tr) { + if (!tr) { + return null; + } + var className = tr.className; + if (className) { + var match = colorRe.exec(className); + if (match) { + return parseInt(match[1], 10); + } + } + return null; +} + +var colorsFreq = [0, 0, 0]; +/** + * return one of given possible colors (curently least used one) + * example: chooseColorNoFrom(2, 3) returns 2 or 3 + * + * @param {Number[]} arguments: one or more numbers + * assumes that 1 <= arguments[i] <= colorsFreq.length + * @returns {Number} Least used color number from arguments + * @globals colorsFreq + */ +function chooseColorNoFrom() { + // choose the color which is least used + var colorNo = arguments[0]; + for (var i = 1; i < arguments.length; i++) { + if (colorsFreq[arguments[i]-1] < colorsFreq[colorNo-1]) { + colorNo = arguments[i]; + } + } + colorsFreq[colorNo-1]++; + return colorNo; +} + +/** + * given two neigbour elements, find color which would be different + * from color of both of neighbours; used to 3-color blame table + * + * @param {HTMLElement} tr_prev + * @param {HTMLElement} tr_next + * @returns {Number} color number N such that + * colorN != tr_prev.className && colorN != tr_next.className + */ +function findColorNo(tr_prev, tr_next) { + var color_prev = getColorNo(tr_prev); + var color_next = getColorNo(tr_next); + + + // neither of neighbours has color set + // THEN we can use any of 3 possible colors + if (!color_prev && !color_next) { + return chooseColorNoFrom(1,2,3); + } + + // either both neighbours have the same color, + // or only one of neighbours have color set + // THEN we can use any color except given + var color; + if (color_prev === color_next) { + color = color_prev; // = color_next; + } else if (!color_prev) { + color = color_next; + } else if (!color_next) { + color = color_prev; + } + if (color) { + return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1); + } + + // neighbours have different colors + // THEN there is only one color left + return (3 - ((color_prev + color_next) % 3)); +} + +/* ............................................................ */ +/* coloring rows like 'blame' after 'blame_data' finishes */ + +/** + * returns true if given row element (tr) is first in commit group + * to be used only after 'blame_data' finishes (after processing) + * + * @param {HTMLElement} tr: table row + * @returns {Boolean} true if TR is first in commit group + */ +function isStartOfGroup(tr) { + return tr.firstChild.className === 'sha1'; +} + +/** + * change colors to use zebra coloring (2 colors) instead of 3 colors + * concatenate neighbour commit groups belonging to the same commit + * + * @globals colorRe + */ +function fixColorsAndGroups() { + var colorClasses = ['light', 'dark']; + var linenum = 1; + var tr, prev_group; + var colorClass = 0; + var table = + document.getElementById('blame_table') || + document.getElementsByTagName('table')[0]; + + while ((tr = document.getElementById('l'+linenum))) { + // index origin is 0, which is table header; start from 1 + //while ((tr = table.rows[linenum])) { // <- it is slower + if (isStartOfGroup(tr, linenum, document)) { + if (prev_group && + prev_group.firstChild.firstChild.href === + tr.firstChild.firstChild.href) { + // we have to concatenate groups + var prev_rows = prev_group.firstChild.rowSpan || 1; + var curr_rows = tr.firstChild.rowSpan || 1; + prev_group.firstChild.rowSpan = prev_rows + curr_rows; + //tr.removeChild(tr.firstChild); + tr.deleteCell(0); // DOM2 HTML way + } else { + colorClass = (colorClass + 1) % 2; + prev_group = tr; + } + } + var tr_class = tr.className; + tr.className = tr_class.replace(colorRe, colorClasses[colorClass]); + linenum++; + } +} + + +/* ============================================================ */ +/* main part: parsing response */ + +/** + * Function called for each blame entry, as soon as it finishes. + * It updates page via DOM manipulation, adding sha1 info, etc. + * + * @param {Commit} commit: blamed commit + * @param {Object} group: object representing group of lines, + * which blame the same commit (blame entry) + * + * @globals blamedLines + */ +function handleLine(commit, group) { + /* + This is the structure of the HTML fragment we are working + with: + + + + 123 + # times (my ext3 doesn't). + + */ + + var resline = group.resline; + + // format date and time string only once per commit + if (!commit.info) { + /* e.g. 'Kay Sievers, 2005-08-07 21:49:46 +0200' */ + commit.info = commit.author + ', ' + + formatDateISOLocal(commit.authorTime, commit.authorTimezone); + } + + // color depends on group of lines, not only on blamed commit + var colorNo = findColorNo( + document.getElementById('l'+(resline-1)), + document.getElementById('l'+(resline+group.numlines)) + ); + + // loop over lines in commit group + for (var i = 0; i < group.numlines; i++, resline++) { + var tr = document.getElementById('l'+resline); + if (!tr) { + break; + } + /* + + + 123 + # times (my ext3 doesn't). + + */ + var td_sha1 = tr.firstChild; + var a_sha1 = td_sha1.firstChild; + var a_linenr = td_sha1.nextSibling.firstChild; + + /* */ + var tr_class = ''; + if (colorNo !== null) { + tr_class = 'color'+colorNo; + } + if (commit.boundary) { + tr_class += ' boundary'; + } + if (commit.nprevious === 0) { + tr_class += ' no-previous'; + } else if (commit.nprevious > 1) { + tr_class += ' multiple-previous'; + } + tr.className = tr_class; + + /* ? */ + if (i === 0) { + td_sha1.title = commit.info; + td_sha1.rowSpan = group.numlines; + + a_sha1.href = projectUrl + 'a=commit;h=' + commit.sha1; + if (a_sha1.firstChild) { + a_sha1.firstChild.data = commit.sha1.substr(0, 8); + } else { + a_sha1.appendChild( + document.createTextNode(commit.sha1.substr(0, 8))); + } + if (group.numlines >= 2) { + var fragment = document.createDocumentFragment(); + var br = document.createElement("br"); + var match = commit.author.match(/\b([A-Z])\B/g); + if (match) { + var text = document.createTextNode( + match.join('')); + } + if (br && text) { + var elem = fragment || td_sha1; + elem.appendChild(br); + elem.appendChild(text); + if (fragment) { + td_sha1.appendChild(fragment); + } + } + } + } else { + //tr.removeChild(td_sha1); // DOM2 Core way + tr.deleteCell(0); // DOM2 HTML way + } + + /* 123 */ + var linenr_commit = + ('previous' in commit ? commit.previous : commit.sha1); + var linenr_filename = + ('file_parent' in commit ? commit.file_parent : commit.filename); + a_linenr.href = projectUrl + 'a=blame_incremental' + + ';hb=' + linenr_commit + + ';f=' + encodeURIComponent(linenr_filename) + + '#l' + (group.srcline + i); + + blamedLines++; + + //updateProgressInfo(); + } +} + +// ---------------------------------------------------------------------- + +var inProgress = false; // are we processing response + +/**#@+ + * @constant + */ +var sha1Re = /^([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/; +var infoRe = /^([a-z-]+) ?(.*)/; +var endRe = /^END ?([^ ]*) ?(.*)/; +/**@-*/ + +var curCommit = new Commit(); +var curGroup = {}; + +var pollTimer = null; + +/** + * Parse output from 'git blame --incremental [...]', received via + * XMLHttpRequest from server (blamedataUrl), and call handleLine + * (which updates page) as soon as blame entry is completed. + * + * @param {String[]} lines: new complete lines from blamedata server + * + * @globals commits, curCommit, curGroup, t_interval_server, cmds_server + * @globals sha1Re, infoRe, endRe + */ +function processBlameLines(lines) { + var match; + + for (var i = 0, len = lines.length; i < len; i++) { + + if ((match = sha1Re.exec(lines[i]))) { + var sha1 = match[1]; + var srcline = parseInt(match[2], 10); + var resline = parseInt(match[3], 10); + var numlines = parseInt(match[4], 10); + + var c = commits[sha1]; + if (!c) { + c = new Commit(sha1); + commits[sha1] = c; + } + curCommit = c; + + curGroup.srcline = srcline; + curGroup.resline = resline; + curGroup.numlines = numlines; + + } else if ((match = infoRe.exec(lines[i]))) { + var info = match[1]; + var data = match[2]; + switch (info) { + case 'filename': + curCommit.filename = unquote(data); + // 'filename' information terminates the entry + handleLine(curCommit, curGroup); + updateProgressInfo(); + break; + case 'author': + curCommit.author = data; + break; + case 'author-time': + curCommit.authorTime = parseInt(data, 10); + break; + case 'author-tz': + curCommit.authorTimezone = data; + break; + case 'previous': + curCommit.nprevious++; + // store only first 'previous' header + if (!'previous' in curCommit) { + var parts = data.split(' ', 2); + curCommit.previous = parts[0]; + curCommit.file_parent = unquote(parts[1]); + } + break; + case 'boundary': + curCommit.boundary = true; + break; + } // end switch + + } else if ((match = endRe.exec(lines[i]))) { + t_interval_server = match[1]; + cmds_server = match[2]; + + } else if (lines[i] !== '') { + // malformed line + + } // end if (match) + + } // end for (lines) +} + +/** + * Process new data and return pointer to end of processed part + * + * @param {String} unprocessed: new data (from nextReadPos) + * @param {Number} nextReadPos: end of last processed data + * @return {Number} end of processed data (new value for nextReadPos) + */ +function processData(unprocessed, nextReadPos) { + var lastLineEnd = unprocessed.lastIndexOf('\n'); + if (lastLineEnd !== -1) { + var lines = unprocessed.substring(0, lastLineEnd).split('\n'); + nextReadPos += lastLineEnd + 1 /* 1 == '\n'.length */; + + processBlameLines(lines); + } // end if + + return nextReadPos; +} + +/** + * Handle XMLHttpRequest errors + * + * @param {XMLHttpRequest} xhr: XMLHttpRequest object + * + * @globals pollTimer, commits, inProgress + */ +function handleError(xhr) { + errorInfo('Server error: ' + + xhr.status + ' - ' + (xhr.statusText || 'Error contacting server')); + + clearInterval(pollTimer); + commits = {}; // free memory + + inProgress = false; +} + +/** + * Called after XMLHttpRequest finishes (loads) + * + * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused) + * + * @globals pollTimer, commits, inProgress + */ +function responseLoaded(xhr) { + clearInterval(pollTimer); + + fixColorsAndGroups(); + writeTimeInterval(); + commits = {}; // free memory + + inProgress = false; +} + +/** + * handler for XMLHttpRequest onreadystatechange event + * @see startBlame + * + * @globals xhr, inProgress + */ +function handleResponse() { + + /* + * xhr.readyState + * + * Value Constant (W3C) Description + * ------------------------------------------------------------------- + * 0 UNSENT open() has not been called yet. + * 1 OPENED send() has not been called yet. + * 2 HEADERS_RECEIVED send() has been called, and headers + * and status are available. + * 3 LOADING Downloading; responseText holds partial data. + * 4 DONE The operation is complete. + */ + + if (xhr.readyState !== 4 && xhr.readyState !== 3) { + return; + } + + // the server returned error + // try ... catch block is to work around bug in IE8 + try { + if (xhr.readyState === 3 && xhr.status !== 200) { + return; + } + } catch (e) { + return; + } + if (xhr.readyState === 4 && xhr.status !== 200) { + handleError(xhr); + return; + } + + // In konqueror xhr.responseText is sometimes null here... + if (xhr.responseText === null) { + return; + } + + // in case we were called before finished processing + if (inProgress) { + return; + } else { + inProgress = true; + } + + // extract new whole (complete) lines, and process them + while (xhr.prevDataLength !== xhr.responseText.length) { + if (xhr.readyState === 4 && + xhr.prevDataLength === xhr.responseText.length) { + break; + } + + xhr.prevDataLength = xhr.responseText.length; + var unprocessed = xhr.responseText.substring(xhr.nextReadPos); + xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos); + } // end while + + // did we finish work? + if (xhr.readyState === 4 && + xhr.prevDataLength === xhr.responseText.length) { + responseLoaded(xhr); + } + + inProgress = false; +} + +// ============================================================ +// ------------------------------------------------------------ + +/** + * Incrementally update line data in blame_incremental view in gitweb. + * + * @param {String} blamedataUrl: URL to server script generating blame data. + * @param {String} bUrl: partial URL to project, used to generate links. + * + * Called from 'blame_incremental' view after loading table with + * file contents, a base for blame view. + * + * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer +*/ +function startBlame(blamedataUrl, bUrl) { + + xhr = createRequestObject(); + if (!xhr) { + errorInfo('ERROR: XMLHttpRequest not supported'); + return; + } + + t0 = new Date(); + projectUrl = bUrl + (bUrl.indexOf('?') === -1 ? '?' : ';'); + if ((div_progress_bar = document.getElementById('progress_bar'))) { + //div_progress_bar.setAttribute('style', 'width: 100%;'); + div_progress_bar.style.cssText = 'width: 100%;'; + } + totalLines = countLines(); + updateProgressInfo(); + + /* add extra properties to xhr object to help processing response */ + xhr.prevDataLength = -1; // used to detect if we have new data + xhr.nextReadPos = 0; // where unread part of response starts + + xhr.onreadystatechange = handleResponse; + //xhr.onreadystatechange = function () { handleResponse(xhr); }; + + xhr.open('GET', blamedataUrl); + xhr.setRequestHeader('Accept', 'text/plain'); + xhr.send(null); + + // not all browsers call onreadystatechange event on each server flush + // poll response using timer every second to handle this issue + pollTimer = setInterval(xhr.onreadystatechange, 1000); +} + +/* end of blame_incremental.js */ diff --git a/gitweb/static/js/javascript-detection.js b/gitweb/static/js/javascript-detection.js new file mode 100644 index 0000000000..93dd2bdd91 --- /dev/null +++ b/gitweb/static/js/javascript-detection.js @@ -0,0 +1,43 @@ +// Copyright (C) 2007, Fredrik Kuivinen +// 2007, Petr Baudis +// 2008-2011, Jakub Narebski + +/** + * @fileOverview Detect if JavaScript is enabled, and pass it to server-side + * @license GPLv2 or later + */ + + +/* ============================================================ */ +/* Manipulating links */ + +/** + * used to check if link has 'js' query parameter already (at end), + * and other reasons to not add 'js=1' param at the end of link + * @constant + */ +var jsExceptionsRe = /[;?]js=[01]$/; + +/** + * Add '?js=1' or ';js=1' to the end of every link in the document + * that doesn't have 'js' query parameter set already. + * + * Links with 'js=1' lead to JavaScript version of given action, if it + * exists (currently there is only 'blame_incremental' for 'blame') + * + * To be used as `window.onload` handler + * + * @globals jsExceptionsRe + */ +function fixLinks() { + var allLinks = document.getElementsByTagName("a") || document.links; + for (var i = 0, len = allLinks.length; i < len; i++) { + var link = allLinks[i]; + if (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/; + link.href += + (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1'; + } + } +} + +/* end of javascript-detection.js */ diff --git a/gitweb/static/js/lib/common-lib.js b/gitweb/static/js/lib/common-lib.js new file mode 100644 index 0000000000..38f3b9e144 --- /dev/null +++ b/gitweb/static/js/lib/common-lib.js @@ -0,0 +1,187 @@ +// Copyright (C) 2007, Fredrik Kuivinen +// 2007, Petr Baudis +// 2008-2011, Jakub Narebski + +/** + * @fileOverview Generic JavaScript code (helper functions) + * @license GPLv2 or later + */ + + +/* ============================================================ */ +/* ............................................................ */ +/* Padding */ + +/** + * pad number N with nonbreakable spaces on the left, to WIDTH characters + * example: padLeftStr(12, 3, '\u00A0') == '\u00A012' + * ('\u00A0' is nonbreakable space) + * + * @param {Number|String} input: number to pad + * @param {Number} width: visible width of output + * @param {String} str: string to prefix to string, e.g. '\u00A0' + * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR + */ +function padLeftStr(input, width, str) { + var prefix = ''; + + width -= input.toString().length; + while (width > 0) { + prefix += str; + width--; + } + return prefix + input; +} + +/** + * Pad INPUT on the left to SIZE width, using given padding character CH, + * for example padLeft('a', 3, '_') is '__a'. + * + * @param {String} input: input value converted to string. + * @param {Number} width: desired length of output. + * @param {String} ch: single character to prefix to string. + * + * @returns {String} Modified string, at least SIZE length. + */ +function padLeft(input, width, ch) { + var s = input + ""; + while (s.length < width) { + s = ch + s; + } + return s; +} + + +/* ............................................................ */ +/* Ajax */ + +/** + * Create XMLHttpRequest object in cross-browser way + * @returns XMLHttpRequest object, or null + */ +function createRequestObject() { + try { + return new XMLHttpRequest(); + } catch (e) {} + try { + return window.createRequest(); + } catch (e) {} + try { + return new ActiveXObject("Msxml2.XMLHTTP"); + } catch (e) {} + try { + return new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) {} + + return null; +} + + +/* ............................................................ */ +/* time and data */ + +/** + * used to extract hours and minutes from timezone info, e.g '-0900' + * @constant + */ +var tzRe = /^([+-])([0-9][0-9])([0-9][0-9])$/; + +/** + * convert numeric timezone +/-ZZZZ to offset from UTC in seconds + * + * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' + * @returns {Number} offset from UTC in seconds for timezone + * + * @globals tzRe + */ +function timezoneOffset(timezoneInfo) { + var match = tzRe.exec(timezoneInfo); + var tz_sign = (match[1] === '-' ? -1 : +1); + var tz_hour = parseInt(match[2],10); + var tz_min = parseInt(match[3],10); + + return tz_sign*(((tz_hour*60) + tz_min)*60); +} + +/** + * return date in local time formatted in iso-8601 like format + * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200' + * + * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' + * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' + * @returns {String} date in local time in iso-8601 like format + */ +function formatDateISOLocal(epoch, timezoneInfo) { + // date corrected by timezone + var localDate = new Date(1000 * (epoch + + timezoneOffset(timezoneInfo))); + var localDateStr = // e.g. '2005-08-07' + localDate.getUTCFullYear() + '-' + + padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' + + padLeft(localDate.getUTCDate(), 2, '0'); + var localTimeStr = // e.g. '21:49:46' + padLeft(localDate.getUTCHours(), 2, '0') + ':' + + padLeft(localDate.getUTCMinutes(), 2, '0') + ':' + + padLeft(localDate.getUTCSeconds(), 2, '0'); + + return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo; +} + + +/* ............................................................ */ +/* unquoting/unescaping filenames */ + +/**#@+ + * @constant + */ +var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g; +var octEscRe = /^[0-7]{1,3}$/; +var maybeQuotedRe = /^\"(.*)\"$/; +/**#@-*/ + +/** + * unquote maybe git-quoted filename + * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a a' + * + * @param {String} str: git-quoted string + * @returns {String} Unquoted and unescaped string + * + * @globals escCodeRe, octEscRe, maybeQuotedRe + */ +function unquote(str) { + function unq(seq) { + var es = { + // character escape codes, aka escape sequences (from C) + // replacements are to some extent JavaScript specific + t: "\t", // tab (HT, TAB) + n: "\n", // newline (NL) + r: "\r", // return (CR) + f: "\f", // form feed (FF) + b: "\b", // backspace (BS) + a: "\x07", // alarm (bell) (BEL) + e: "\x1B", // escape (ESC) + v: "\v" // vertical tab (VT) + }; + + if (seq.search(octEscRe) !== -1) { + // octal char sequence + return String.fromCharCode(parseInt(seq, 8)); + } else if (seq in es) { + // C escape sequence, aka character escape code + return es[seq]; + } + // quoted ordinary character + return seq; + } + + var match = str.match(maybeQuotedRe); + if (match) { + str = match[1]; + // perhaps str = eval('"'+str+'"'); would be enough? + str = str.replace(escCodeRe, + function (substr, p1, offset, s) { return unq(p1); }); + } + return str; +} + +/* end of common-lib.js */ -- cgit v1.3 From e2895de4d8efef52e835731aaaaa9467e2110e62 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:02 +0200 Subject: gitweb.js: Update and improve comments in JavaScript files This consists of adding a few extra explanation, fixing descriptions of functions to match names of parameters in code, adding a few separators, and fixing spelling -- while at it spell 'neighbor' using American spelling (and not as 'neighbour'). This is post-split cleanup. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/static/js/blame_incremental.js | 22 +++++++++++----------- gitweb/static/js/lib/common-lib.js | 12 ++++++++---- 2 files changed, 19 insertions(+), 15 deletions(-) (limited to 'gitweb') diff --git a/gitweb/static/js/blame_incremental.js b/gitweb/static/js/blame_incremental.js index f63f78b9ec..676da6b590 100644 --- a/gitweb/static/js/blame_incremental.js +++ b/gitweb/static/js/blame_incremental.js @@ -7,7 +7,7 @@ * @license GPLv2 or later */ - +/* ============================================================ */ /* * This code uses DOM methods instead of (nonstandard) innerHTML * to modify page. @@ -26,7 +26,7 @@ */ -/* ============================================================ */ +/* ............................................................ */ /* utility/helper functions (and variables) */ var xhr; // XMLHttpRequest object @@ -132,7 +132,7 @@ function writeTimeInterval() { } /** - * show an error message alert to user within page (in prohress info area) + * show an error message alert to user within page (in progress info area) * @param {String} str: plain text error message (no HTML) * * @globals div_progress_info @@ -182,7 +182,7 @@ function getColorNo(tr) { var colorsFreq = [0, 0, 0]; /** - * return one of given possible colors (curently least used one) + * return one of given possible colors (currently least used one) * example: chooseColorNoFrom(2, 3) returns 2 or 3 * * @param {Number[]} arguments: one or more numbers @@ -203,8 +203,8 @@ function chooseColorNoFrom() { } /** - * given two neigbour elements, find color which would be different - * from color of both of neighbours; used to 3-color blame table + * given two neighbor elements, find color which would be different + * from color of both of neighbors; used to 3-color blame table * * @param {HTMLElement} tr_prev * @param {HTMLElement} tr_next @@ -216,14 +216,14 @@ function findColorNo(tr_prev, tr_next) { var color_next = getColorNo(tr_next); - // neither of neighbours has color set + // neither of neighbors has color set // THEN we can use any of 3 possible colors if (!color_prev && !color_next) { return chooseColorNoFrom(1,2,3); } - // either both neighbours have the same color, - // or only one of neighbours have color set + // either both neighbors have the same color, + // or only one of neighbors have color set // THEN we can use any color except given var color; if (color_prev === color_next) { @@ -237,7 +237,7 @@ function findColorNo(tr_prev, tr_next) { return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1); } - // neighbours have different colors + // neighbors have different colors // THEN there is only one color left return (3 - ((color_prev + color_next) % 3)); } @@ -258,7 +258,7 @@ function isStartOfGroup(tr) { /** * change colors to use zebra coloring (2 colors) instead of 3 colors - * concatenate neighbour commit groups belonging to the same commit + * concatenate neighbor commit groups belonging to the same commit * * @globals colorRe */ diff --git a/gitweb/static/js/lib/common-lib.js b/gitweb/static/js/lib/common-lib.js index 38f3b9e144..6a6d2000c4 100644 --- a/gitweb/static/js/lib/common-lib.js +++ b/gitweb/static/js/lib/common-lib.js @@ -13,14 +13,17 @@ /* Padding */ /** - * pad number N with nonbreakable spaces on the left, to WIDTH characters + * pad INPUT on the left with STR that is assumed to have visible + * width of single character (for example nonbreakable spaces), + * to WIDTH characters + * * example: padLeftStr(12, 3, '\u00A0') == '\u00A012' * ('\u00A0' is nonbreakable space) * * @param {Number|String} input: number to pad * @param {Number} width: visible width of output * @param {String} str: string to prefix to string, e.g. '\u00A0' - * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR + * @returns {String} INPUT prefixed with STR x (WIDTH - INPUT.length) */ function padLeftStr(input, width, str) { var prefix = ''; @@ -34,7 +37,7 @@ function padLeftStr(input, width, str) { } /** - * Pad INPUT on the left to SIZE width, using given padding character CH, + * Pad INPUT on the left to WIDTH, using given padding character CH, * for example padLeft('a', 3, '_') is '__a'. * * @param {String} input: input value converted to string. @@ -140,7 +143,8 @@ var maybeQuotedRe = /^\"(.*)\"$/; /**#@-*/ /** - * unquote maybe git-quoted filename + * unquote maybe C-quoted filename (as used by git, i.e. it is + * in double quotes '"' if there is any escape character used) * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a a' * * @param {String} str: git-quoted string -- cgit v1.3 From 4dfa207eec44e0d8869c4978cf3d9861aaf40091 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:03 +0200 Subject: gitweb.js: Provide default values for padding in padLeftStr and padLeft This means that one can use padLeft(4, 2) and it would be equivalent to runing padLeft(4, 2, '0'), and it would return '04' i.e. '4' padded with '0' to width 2, to be used e.g. in formatting date and time. This should make those functions easier to use. Current code doesn't yet make use of this feature. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/static/js/lib/common-lib.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'gitweb') diff --git a/gitweb/static/js/lib/common-lib.js b/gitweb/static/js/lib/common-lib.js index 6a6d2000c4..c45454ead2 100644 --- a/gitweb/static/js/lib/common-lib.js +++ b/gitweb/static/js/lib/common-lib.js @@ -22,11 +22,14 @@ * * @param {Number|String} input: number to pad * @param {Number} width: visible width of output - * @param {String} str: string to prefix to string, e.g. '\u00A0' + * @param {String} str: string to prefix to string, defaults to '\u00A0' * @returns {String} INPUT prefixed with STR x (WIDTH - INPUT.length) */ function padLeftStr(input, width, str) { var prefix = ''; + if (typeof str === 'undefined') { + ch = '\u00A0'; // using ' ' doesn't work in all browsers + } width -= input.toString().length; while (width > 0) { @@ -38,16 +41,21 @@ function padLeftStr(input, width, str) { /** * Pad INPUT on the left to WIDTH, using given padding character CH, - * for example padLeft('a', 3, '_') is '__a'. + * for example padLeft('a', 3, '_') is '__a' + * padLeft(4, 2) is '04' (same as padLeft(4, 2, '0')) * * @param {String} input: input value converted to string. * @param {Number} width: desired length of output. - * @param {String} ch: single character to prefix to string. + * @param {String} ch: single character to prefix to string, defaults to '0'. * * @returns {String} Modified string, at least SIZE length. */ function padLeft(input, width, ch) { var s = input + ""; + if (typeof ch === 'undefined') { + ch = '0'; + } + while (s.length < width) { s = ch + s; } -- cgit v1.3 From 54b1479a770b240d71bbd000fb40e470987bac59 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:04 +0200 Subject: gitweb.js: Extract and improve datetime handling Move formatDateISOLocal(epoch, timezone) function (and also helper timezoneOffset(timezoneInfo) function it requires) from common-lib.js to datetime.js Add new functions: * localTimezoneOffset - to get browser timezone offset in seconds * localTimezoneInfo - to get browser timezone in '(+|-)HHMM' format * formatTimezoneInfo - turn offset in hours and minutes into '(+|-)HHMM' * parseRFC2822Date - to parse RFC-2822 dates that gitweb uses into epoch * formatDateRFC2882 - like formatDateISOLocal, only RFC-2822 format All those functions are meant to be used in future commit 'gitweb: javascript ability to adjust time based on timezone' An alternative would be to use e.g. Datejs (http://www.datejs.com) library, or JavaScript framework that has date formatting (perhaps as a plugin). While at it escape '-' in character class inside tzRe regexp, as recommended by JSLint (http://www.jslint.com). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/Makefile | 1 + gitweb/static/js/lib/common-lib.js | 51 ------------ gitweb/static/js/lib/datetime.js | 161 +++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 51 deletions(-) create mode 100644 gitweb/static/js/lib/datetime.js (limited to 'gitweb') diff --git a/gitweb/Makefile b/gitweb/Makefile index 0baa9df9f6..403265a9b2 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -117,6 +117,7 @@ GITWEB_FILES += static/git-logo.png static/git-favicon.png # js/lib/common-lib.js should be always first, then js/lib/*.js, # then the rest of files; js/gitweb.js should be last (if it exists) GITWEB_JSLIB_FILES += static/js/lib/common-lib.js +GITWEB_JSLIB_FILES += static/js/lib/datetime.js GITWEB_JSLIB_FILES += static/js/javascript-detection.js GITWEB_JSLIB_FILES += static/js/blame_incremental.js diff --git a/gitweb/static/js/lib/common-lib.js b/gitweb/static/js/lib/common-lib.js index c45454ead2..d6b0c0d46a 100644 --- a/gitweb/static/js/lib/common-lib.js +++ b/gitweb/static/js/lib/common-lib.js @@ -88,57 +88,6 @@ function createRequestObject() { } -/* ............................................................ */ -/* time and data */ - -/** - * used to extract hours and minutes from timezone info, e.g '-0900' - * @constant - */ -var tzRe = /^([+-])([0-9][0-9])([0-9][0-9])$/; - -/** - * convert numeric timezone +/-ZZZZ to offset from UTC in seconds - * - * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' - * @returns {Number} offset from UTC in seconds for timezone - * - * @globals tzRe - */ -function timezoneOffset(timezoneInfo) { - var match = tzRe.exec(timezoneInfo); - var tz_sign = (match[1] === '-' ? -1 : +1); - var tz_hour = parseInt(match[2],10); - var tz_min = parseInt(match[3],10); - - return tz_sign*(((tz_hour*60) + tz_min)*60); -} - -/** - * return date in local time formatted in iso-8601 like format - * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200' - * - * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' - * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' - * @returns {String} date in local time in iso-8601 like format - */ -function formatDateISOLocal(epoch, timezoneInfo) { - // date corrected by timezone - var localDate = new Date(1000 * (epoch + - timezoneOffset(timezoneInfo))); - var localDateStr = // e.g. '2005-08-07' - localDate.getUTCFullYear() + '-' + - padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' + - padLeft(localDate.getUTCDate(), 2, '0'); - var localTimeStr = // e.g. '21:49:46' - padLeft(localDate.getUTCHours(), 2, '0') + ':' + - padLeft(localDate.getUTCMinutes(), 2, '0') + ':' + - padLeft(localDate.getUTCSeconds(), 2, '0'); - - return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo; -} - - /* ............................................................ */ /* unquoting/unescaping filenames */ diff --git a/gitweb/static/js/lib/datetime.js b/gitweb/static/js/lib/datetime.js new file mode 100644 index 0000000000..b3dcedb141 --- /dev/null +++ b/gitweb/static/js/lib/datetime.js @@ -0,0 +1,161 @@ +// Copyright (C) 2007, Fredrik Kuivinen +// 2007, Petr Baudis +// 2008-2011, Jakub Narebski + +/** + * @fileOverview Datetime manipulation: parsing and formatting + * @license GPLv2 or later + */ + + +/* ............................................................ */ +/* parsing and retrieving datetime related information */ + +/** + * used to extract hours and minutes from timezone info, e.g '-0900' + * @constant + */ +var tzRe = /^([+\-])([0-9][0-9])([0-9][0-9])$/; + +/** + * convert numeric timezone +/-ZZZZ to offset from UTC in seconds + * + * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' + * @returns {Number} offset from UTC in seconds for timezone + * + * @globals tzRe + */ +function timezoneOffset(timezoneInfo) { + var match = tzRe.exec(timezoneInfo); + var tz_sign = (match[1] === '-' ? -1 : +1); + var tz_hour = parseInt(match[2],10); + var tz_min = parseInt(match[3],10); + + return tz_sign*(((tz_hour*60) + tz_min)*60); +} + +/** + * return local (browser) timezone as offset from UTC in seconds + * + * @returns {Number} offset from UTC in seconds for local timezone + */ +function localTimezoneOffset() { + // getTimezoneOffset returns the time-zone offset from UTC, + // in _minutes_, for the current locale + return ((new Date()).getTimezoneOffset() * -60); +} + +/** + * return local (browser) timezone as numeric timezone '(+|-)HHMM' + * + * @returns {String} locat timezone as -/+ZZZZ + */ +function localTimezoneInfo() { + var tzOffsetMinutes = (new Date()).getTimezoneOffset() * -1; + + return formatTimezoneInfo(0, tzOffsetMinutes); +} + + +/** + * Parse RFC-2822 date into a Unix timestamp (into epoch) + * + * @param {String} date: date in RFC-2822 format, e.g. 'Thu, 21 Dec 2000 16:01:07 +0200' + * @returns {Number} epoch i.e. seconds since '00:00:00 1970-01-01 UTC' + */ +function parseRFC2822Date(date) { + // Date.parse accepts the IETF standard (RFC 1123 Section 5.2.14 and elsewhere) + // date syntax, which is defined in RFC 2822 (obsoletes RFC 822) + // and returns number of _milli_seconds since January 1, 1970, 00:00:00 UTC + return Date.parse(date) / 1000; +} + + +/* ............................................................ */ +/* formatting date */ + +/** + * format timezone offset as numerical timezone '(+|-)HHMM' or '(+|-)HH:MM' + * + * @param {Number} hours: offset in hours, e.g. 2 for '+0200' + * @param {Number} [minutes] offset in minutes, e.g. 30 for '-4030'; + * it is split into hours if not 0 <= minutes < 60, + * for example 1200 would give '+0100'; + * defaults to 0 + * @param {String} [sep] separator between hours and minutes part, + * default is '', might be ':' for W3CDTF (rfc-3339) + * @returns {String} timezone in '(+|-)HHMM' or '(+|-)HH:MM' format + */ +function formatTimezoneInfo(hours, minutes, sep) { + minutes = minutes || 0; // to be able to use formatTimezoneInfo(hh) + sep = sep || ''; // default format is +/-ZZZZ + + if (minutes < 0 || minutes > 59) { + hours = minutes > 0 ? Math.floor(minutes / 60) : Math.ceil(minutes / 60); + minutes = Math.abs(minutes - 60*hours); // sign of minutes is sign of hours + // NOTE: this works correctly because there is no UTC-00:30 timezone + } + + var tzSign = hours >= 0 ? '+' : '-'; + if (hours < 0) { + hours = -hours; // sign is stored in tzSign + } + + return tzSign + padLeft(hours, 2, '0') + sep + padLeft(minutes, 2, '0'); +} + +/** + * return date in local time formatted in iso-8601 like format + * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200' + * + * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' + * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' + * @returns {String} date in local time in iso-8601 like format + */ +function formatDateISOLocal(epoch, timezoneInfo) { + // date corrected by timezone + var localDate = new Date(1000 * (epoch + + timezoneOffset(timezoneInfo))); + var localDateStr = // e.g. '2005-08-07' + localDate.getUTCFullYear() + '-' + + padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' + + padLeft(localDate.getUTCDate(), 2, '0'); + var localTimeStr = // e.g. '21:49:46' + padLeft(localDate.getUTCHours(), 2, '0') + ':' + + padLeft(localDate.getUTCMinutes(), 2, '0') + ':' + + padLeft(localDate.getUTCSeconds(), 2, '0'); + + return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo; +} + +/** + * return date in local time formatted in rfc-2822 format + * e.g. 'Thu, 21 Dec 2000 16:01:07 +0200' + * + * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' + * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' + * @param {Boolean} [padDay] e.g. 'Sun, 07 Aug' if true, 'Sun, 7 Aug' otherwise + * @returns {String} date in local time in rfc-2822 format + */ +function formatDateRFC2882(epoch, timezoneInfo, padDay) { + // A short textual representation of a month, three letters + var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + // A textual representation of a day, three letters + var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + // date corrected by timezone + var localDate = new Date(1000 * (epoch + + timezoneOffset(timezoneInfo))); + var localDateStr = // e.g. 'Sun, 7 Aug 2005' or 'Sun, 07 Aug 2005' + days[localDate.getUTCDay()] + ', ' + + (padDay ? padLeft(localDate.getUTCDate(),2,'0') : localDate.getUTCDate()) + ' ' + + months[localDate.getUTCMonth()] + ' ' + + localDate.getUTCFullYear(); + var localTimeStr = // e.g. '21:49:46' + padLeft(localDate.getUTCHours(), 2, '0') + ':' + + padLeft(localDate.getUTCMinutes(), 2, '0') + ':' + + padLeft(localDate.getUTCSeconds(), 2, '0'); + + return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo; +} + +/* end of datetime.js */ -- cgit v1.3 From fcce886bfb4671c4c17a3f99cb7caf0f7ab94e16 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:05 +0200 Subject: gitweb.js: Introduce code to handle cookies from JavaScript Introduced gitweb/static/js/cookies.js file provides functions for setting, getting and deleting cookies. Code taken from subsection "Cookies in JavaScript" of "Professional JavaScript for Web Developers" by Nicholas C. Zakas and from cookie plugin for jQuery (dual licensed under the MIT and GPL licenses). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/Makefile | 1 + gitweb/static/js/lib/cookies.js | 114 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 gitweb/static/js/lib/cookies.js (limited to 'gitweb') diff --git a/gitweb/Makefile b/gitweb/Makefile index 403265a9b2..7dd1dee55a 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -118,6 +118,7 @@ GITWEB_FILES += static/git-logo.png static/git-favicon.png # then the rest of files; js/gitweb.js should be last (if it exists) GITWEB_JSLIB_FILES += static/js/lib/common-lib.js GITWEB_JSLIB_FILES += static/js/lib/datetime.js +GITWEB_JSLIB_FILES += static/js/lib/cookies.js GITWEB_JSLIB_FILES += static/js/javascript-detection.js GITWEB_JSLIB_FILES += static/js/blame_incremental.js diff --git a/gitweb/static/js/lib/cookies.js b/gitweb/static/js/lib/cookies.js new file mode 100644 index 0000000000..72b51cd1b4 --- /dev/null +++ b/gitweb/static/js/lib/cookies.js @@ -0,0 +1,114 @@ +/** + * @fileOverview Accessing cookies from JavaScript + * @license GPLv2 or later + */ + +/* + * Based on subsection "Cookies in JavaScript" of "Professional + * JavaScript for Web Developers" by Nicholas C. Zakas and cookie + * plugin from jQuery (dual licensed under the MIT and GPL licenses) + */ + + +/** + * Create a cookie with the given name and value, + * and other optional parameters. + * + * @example + * setCookie('foo', 'bar'); // will be deleted when browser exits + * setCookie('foo', 'bar', { expires: new Date(Date.parse('Jan 1, 2012')) }); + * setCookie('foo', 'bar', { expires: 7 }); // 7 days = 1 week + * setCookie('foo', 'bar', { expires: 14, path: '/' }); + * + * @param {String} sName: Unique name of a cookie (letters, numbers, underscores). + * @param {String} sValue: The string value stored in a cookie. + * @param {Object} [options] An object literal containing key/value pairs + * to provide optional cookie attributes. + * @param {String|Number|Date} [options.expires] Either literal string to be used as cookie expires, + * or an integer specifying the expiration date from now on in days, + * or a Date object to be used as cookie expiration date. + * If a negative value is specified or a date in the past), + * the cookie will be deleted. + * If set to null or omitted, the cookie will be a session cookie + * and will not be retained when the the browser exits. + * @param {String} [options.path] Restrict access of a cookie to particular directory + * (default: path of page that created the cookie). + * @param {String} [options.domain] Override what web sites are allowed to access cookie + * (default: domain of page that created the cookie). + * @param {Boolean} [options.secure] If true, the secure attribute of the cookie will be set + * and the cookie would be accessible only from secure sites + * (cookie transmission will require secure protocol like HTTPS). + */ +function setCookie(sName, sValue, options) { + options = options || {}; + if (sValue === null) { + sValue = ''; + option.expires = 'delete'; + } + + var sCookie = sName + '=' + encodeURIComponent(sValue); + + if (options.expires) { + var oExpires = options.expires, sDate; + if (oExpires === 'delete') { + sDate = 'Thu, 01 Jan 1970 00:00:00 GMT'; + } else if (typeof oExpires === 'string') { + sDate = oExpires; + } else { + var oDate; + if (typeof oExpires === 'number') { + oDate = new Date(); + oDate.setTime(oDate.getTime() + (oExpires * 24 * 60 * 60 * 1000)); // days to ms + } else { + oDate = oExpires; + } + sDate = oDate.toGMTString(); + } + sCookie += '; expires=' + sDate; + } + + if (options.path) { + sCookie += '; path=' + (options.path); + } + if (options.domain) { + sCookie += '; domain=' + (options.domain); + } + if (options.secure) { + sCookie += '; secure'; + } + document.cookie = sCookie; +} + +/** + * Get the value of a cookie with the given name. + * + * @param {String} sName: Unique name of a cookie (letters, numbers, underscores) + * @returns {String|null} The string value stored in a cookie + */ +function getCookie(sName) { + var sRE = '(?:; )?' + sName + '=([^;]*);?'; + var oRE = new RegExp(sRE); + if (oRE.test(document.cookie)) { + return decodeURIComponent(RegExp['$1']); + } else { + return null; + } +} + +/** + * Delete cookie with given name + * + * @param {String} sName: Unique name of a cookie (letters, numbers, underscores) + * @param {Object} [options] An object literal containing key/value pairs + * to provide optional cookie attributes. + * @param {String} [options.path] Must be the same as when setting a cookie + * @param {String} [options.domain] Must be the same as when setting a cookie + */ +function deleteCookie(sName, options) { + options = options || {}; + options.expires = 'delete'; + + setCookie(sName, '', options); +} + +/* end of cookies.js */ -- cgit v1.3 From 1cae3ee70dd9e389a83c494fbc95aa63c01128a1 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:06 +0200 Subject: gitweb.js: Provide getElementsByClassName method (if it not exists) The code is simplified and does not support full specification of native getElementsByClassName method, but implements just subset that would be enough for gitweb, supporting only single class name. Signed-off-by: John 'Warthog9' Hawley Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/static/js/lib/common-lib.js | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) (limited to 'gitweb') diff --git a/gitweb/static/js/lib/common-lib.js b/gitweb/static/js/lib/common-lib.js index d6b0c0d46a..b37139152d 100644 --- a/gitweb/static/js/lib/common-lib.js +++ b/gitweb/static/js/lib/common-lib.js @@ -88,6 +88,57 @@ function createRequestObject() { } +/* ............................................................ */ +/* Support for legacy browsers */ + +/** + * Provides getElementsByClassName method, if there is no native + * implementation of this method. + * + * NOTE that there are limits and differences compared to native + * getElementsByClassName as defined by e.g.: + * https://developer.mozilla.org/en/DOM/document.getElementsByClassName + * http://www.whatwg.org/specs/web-apps/current-work/multipage/dom.html#dom-getelementsbyclassname + * http://www.whatwg.org/specs/web-apps/current-work/multipage/dom.html#dom-document-getelementsbyclassname + * + * Namely, this implementation supports only single class name as + * argument and not set of space-separated tokens representing classes, + * it returns Array of nodes rather than live NodeList, and has + * additional optional argument where you can limit search to given tags + * (via getElementsByTagName). + * + * Based on + * http://code.google.com/p/getelementsbyclassname/ + * http://www.dustindiaz.com/getelementsbyclass/ + * http://stackoverflow.com/questions/1818865/do-we-have-getelementsbyclassname-in-javascript + * + * See also http://ejohn.org/blog/getelementsbyclassname-speed-comparison/ + * + * @param {String} class: name of _single_ class to find + * @param {String} [taghint] limit search to given tags + * @returns {Node[]} array of matching elements + */ +if (!('getElementsByClassName' in document)) { + document.getElementsByClassName = function (classname, taghint) { + taghint = taghint || "*"; + var elements = (taghint === "*" && document.all) ? + document.all : + document.getElementsByTagName(taghint); + var pattern = new RegExp("(^|\\s)" + classname + "(\\s|$)"); + var matches= []; + for (var i = 0, j = 0, n = elements.length; i < n; i++) { + var el= elements[i]; + if (el.className && pattern.test(el.className)) { + // matches.push(el); + matches[j] = el; + j++; + } + } + return matches; + }; +} // end if + + /* ............................................................ */ /* unquoting/unescaping filenames */ -- cgit v1.3 From 256b7b4883231ae9c7f24f2cb4170f61e00704e1 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:07 +0200 Subject: gitweb: Refactor generating of long dates into format_timestamp_html It is pure refactoring and doesn't change gitweb output, though this could potentially affect 'summary', 'log', and 'commit'-like views ('commit', 'commitdiff', 'tag'). Remove print_local_time and format_local_time, as their use is now replaced (indirectly) by using format_timestamp_html. While at it improve whitespace formatting. Inspired-by-code-by: Kevin Cernekee Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ee69ea683a..7329db289c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3938,22 +3938,21 @@ sub git_print_section { print $cgi->end_div; } -sub print_local_time { - print format_local_time(@_); -} +sub format_timestamp_html { + my ($date, %opts) = @_; + my $strtime = $date->{'rfc2822'}; -sub format_local_time { - my $localtime = ''; - my %date = @_; - if ($date{'hour_local'} < 6) { - $localtime .= sprintf(" (%02d:%02d %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 $strtime unless $opts{'-localtime'}; + + my $localtime_format = '(%02d:%02d %s)'; + if ($date->{'hour_local'} < 6) { + $localtime_format = '(%02d:%02d %s)'; } + $strtime .= ' ' . + sprintf($localtime_format, + $date->{'hour_local'}, $date->{'minute_local'}, $date->{'tz_local'}); - return $localtime; + return $strtime; } # Outputs the author name and date in long form @@ -3966,10 +3965,9 @@ sub git_print_authorship { my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'}); print "<$tag class=\"author_date\">" . format_search_author($author, "author", esc_html($author)) . - " [$ad{'rfc2822'}"; - print_local_time(%ad) if ($opts{-localtime}); - print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1) - . "\n"; + " [".format_timestamp_html(\%ad, %opts)."]". + git_get_avatar($co->{'author_email'}, -pad_before => 1) . + "\n"; } # Outputs table rows containing the full author or committer information, @@ -3986,16 +3984,16 @@ sub git_print_authorship_rows { my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"}); print "$who" . format_search_author($co->{"${who}_name"}, $who, - esc_html($co->{"${who}_name"})) . " " . + esc_html($co->{"${who}_name"})) . " " . format_search_author($co->{"${who}_email"}, $who, - esc_html("<" . $co->{"${who}_email"} . ">")) . + esc_html("<" . $co->{"${who}_email"} . ">")) . "" . git_get_avatar($co->{"${who}_email"}, -size => 'double') . "\n" . "" . - " $wd{'rfc2822'}"; - print_local_time(%wd); - print "" . + "" . + format_timestamp_html(\%wd, -localtime=>1) . + "" . "\n"; } } @@ -5410,7 +5408,8 @@ sub git_summary { "description" . esc_html($descr) . "\n" . "owner" . esc_html($owner) . "\n"; if (defined $cd{'rfc2822'}) { - print "last change$cd{'rfc2822'}\n"; + print "last change" . + "".format_timestamp_html(\%cd)."\n"; } # use per project git URL list in $projectroot/$project/cloneurl -- cgit v1.3 From ce71b07632c71c2e6f4732abd3db6d59fb1bb924 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:08 +0200 Subject: gitweb: Unify the way long timestamp is displayed format_timestamp_html loses its "-localtime => 1" option, and now always print the local time (in author/comitter/tagger local timezone), with "atnight" warning if needed. This means that both 'summary' and 'log' views now display localtime. In the case of 'log' view this can be thought as an improvement, as now one can easily see which commits in a series are made "atnight" and should be examined closer. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 7329db289c..67bcfe894e 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3939,11 +3939,9 @@ sub git_print_section { } sub format_timestamp_html { - my ($date, %opts) = @_; + my $date = shift; my $strtime = $date->{'rfc2822'}; - return $strtime unless $opts{'-localtime'}; - my $localtime_format = '(%02d:%02d %s)'; if ($date->{'hour_local'} < 6) { $localtime_format = '(%02d:%02d %s)'; @@ -3965,7 +3963,7 @@ sub git_print_authorship { my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'}); print "<$tag class=\"author_date\">" . format_search_author($author, "author", esc_html($author)) . - " [".format_timestamp_html(\%ad, %opts)."]". + " [".format_timestamp_html(\%ad)."]". git_get_avatar($co->{'author_email'}, -pad_before => 1) . "\n"; } @@ -3992,7 +3990,7 @@ sub git_print_authorship_rows { "\n" . "" . "" . - format_timestamp_html(\%wd, -localtime=>1) . + format_timestamp_html(\%wd) . "" . "\n"; } -- cgit v1.3 From 291e52bd19877dc8fdd77079ba4c4326f114c461 Mon Sep 17 00:00:00 2001 From: John 'Warthog9' Hawley Date: Thu, 28 Apr 2011 21:04:09 +0200 Subject: gitweb: JavaScript ability to adjust time based on timezone This patch is based on Kevin Cernekee's patch series entitled "gitweb: introduce localtime feature". While Kevin's patch changed the server side output so that the timezone was output from gitweb itself, this has a number of drawbacks, in particular with respect to gitweb-caching. This patch takes the same basic goal, display the appropriate times in a given common timezone, and implements it in JavaScript. This requires adding / using a new class, "datetime", to be able to find elements to be adjusted from JavaScript. Appropriate dates are wrapped in a span with this class. Timezone to be used can be retrieved from "gitweb_tz" cookie, though currently there is no way to set / manipulate this cookie from gitweb; this is left for later commit. Valid timezones, currently, are: "utc", "local" (which means that timezone is taken from browser), and "+/-ZZZZ" numeric timezone as in RFC-2822. Default timezone is "local" (currently not configurable, left for later commit). Fallback (should JavaScript not be enabled) is to treat dates as they have been and display them, only, in UTC. Pages affected: * 'summary' view, "last change" field (commit time from latest change) * 'log' view, author time * 'commit' and 'commitdiff' views, author/committer time * 'tag' view, tagger time Based-on-code-from: Kevin Cernekee Signed-off-by: John 'Warthog9' Hawley Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/Makefile | 1 + gitweb/gitweb.perl | 11 +++++-- gitweb/static/js/adjust-timezone.js | 60 +++++++++++++++++++++++++++++++++++++ gitweb/static/js/lib/datetime.js | 15 ++++++++++ 4 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 gitweb/static/js/adjust-timezone.js (limited to 'gitweb') diff --git a/gitweb/Makefile b/gitweb/Makefile index 7dd1dee55a..5d20515fba 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -120,6 +120,7 @@ GITWEB_JSLIB_FILES += static/js/lib/common-lib.js GITWEB_JSLIB_FILES += static/js/lib/datetime.js GITWEB_JSLIB_FILES += static/js/lib/cookies.js GITWEB_JSLIB_FILES += static/js/javascript-detection.js +GITWEB_JSLIB_FILES += static/js/adjust-timezone.js GITWEB_JSLIB_FILES += static/js/blame_incremental.js diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 67bcfe894e..6651946f54 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3732,9 +3732,14 @@ sub git_footer_html { qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!. qq! "!. href() .qq!");\n!. qq!\n!; - } elsif (gitweb_check_feature('javascript-actions')) { + } else { print qq!\n!; } @@ -3940,7 +3945,7 @@ sub git_print_section { sub format_timestamp_html { my $date = shift; - my $strtime = $date->{'rfc2822'}; + my $strtime = ''.$date->{'rfc2822'}.''; my $localtime_format = '(%02d:%02d %s)'; if ($date->{'hour_local'} < 6) { diff --git a/gitweb/static/js/adjust-timezone.js b/gitweb/static/js/adjust-timezone.js new file mode 100644 index 0000000000..1577d780f0 --- /dev/null +++ b/gitweb/static/js/adjust-timezone.js @@ -0,0 +1,60 @@ +// Copyright (C) 2011, John 'Warthog9' Hawley +// 2011, Jakub Narebski + +/** + * @fileOverview Manipulate dates in gitweb output, adjusting timezone + * @license GPLv2 or later + */ + +/** + * Get common timezone and adjust dates to use this common timezone. + * + * This function is called during onload event (added to window.onload). + * + * @param {String} tzDefault: default timezone, if there is no cookie + * @param {String} tzCookieName: name of cookie to store timezone + * @param {String} tzClassName: denotes elements with date to be adjusted + */ +function onloadTZSetup(tzDefault, tzCookieName, tzClassName) { + var tzCookie = getCookie(tzCookieName); + var tz = tzCookie ? tzCookie : tzDefault; + + // server-side of gitweb produces datetime in UTC, + // so if tz is 'utc' there is no need for changes + if (tz !== 'utc') { + fixDatetimeTZ(tz, tzClassName); + } +} + + +/** + * Replace RFC-2822 dates contained in SPAN elements with tzClassName + * CSS class with equivalent dates in given timezone. + * + * @param {String} tz: numeric timezone in '(-|+)HHMM' format, or 'utc', or 'local' + * @param {String} tzClassName: specifies elements to be changed + */ +function fixDatetimeTZ(tz, tzClassName) { + // sanity check, method should be ensured by common-lib.js + if (!document.getElementsByClassName) { + return; + } + + // translate to timezone in '(-|+)HHMM' format + tz = normalizeTimezoneInfo(tz); + + // NOTE: result of getElementsByClassName should probably be cached + var classesFound = document.getElementsByClassName(tzClassName, "span"); + for (var i = 0, len = classesFound.length; i < len; i++) { + var curElement = classesFound[i]; + + // we use *.firstChild.data (W3C DOM) instead of *.innerHTML + // as the latter doesn't always work everywhere in every browser + var epoch = parseRFC2822Date(curElement.firstChild.data); + var adjusted = formatDateRFC2882(epoch, tz); + + curElement.firstChild.data = adjusted; + } +} + +/* end of adjust-timezone.js */ diff --git a/gitweb/static/js/lib/datetime.js b/gitweb/static/js/lib/datetime.js index b3dcedb141..f78c60a912 100644 --- a/gitweb/static/js/lib/datetime.js +++ b/gitweb/static/js/lib/datetime.js @@ -104,6 +104,21 @@ function formatTimezoneInfo(hours, minutes, sep) { return tzSign + padLeft(hours, 2, '0') + sep + padLeft(minutes, 2, '0'); } +/** + * translate 'utc' and 'local' to numerical timezone + * @param {String} timezoneInfo: might be 'utc' or 'local' (browser) + */ +function normalizeTimezoneInfo(timezoneInfo) { + switch (timezoneInfo) { + case 'utc': + return '+0000'; + case 'local': // 'local' is browser timezone + return localTimezoneInfo(); + } + return timezoneInfo; +} + + /** * return date in local time formatted in iso-8601 like format * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200' -- cgit v1.3 From 2ae8da2552f43802476676bb86b037e9028b7a7c Mon Sep 17 00:00:00 2001 From: John 'Warthog9' Hawley Date: Thu, 28 Apr 2011 21:04:10 +0200 Subject: gitweb.js: Add UI for selecting common timezone to display dates This will modify HTML, add CSS rules and add DOM event handlers so that clicking on any date (the common part, not the localtime part) will display a drop down menu to choose the timezone to change to. Currently menu displays only the following timezones: utc local -1200 -1100 ... +1100 +1200 +1300 +1400 In timezone selection menu each timezone is +1hr to the previous. The code is capable of handling fractional timezones, but those have not been added to the menu. All changes are saved to a cookie, so page changes and closing / reopening browser retains the last known timezone setting used. [jn: Changed from innerHTML to DOM, moved to event delegation for onclick to trigger menu, added close button and cookie refreshing] Helped-by: Kevin Cernekee Signed-off-by: John 'Warthog9' Hawley Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 3 +- gitweb/static/gitweb.css | 33 ++++ gitweb/static/js/adjust-timezone.js | 298 ++++++++++++++++++++++++++++++++++-- gitweb/static/js/lib/common-lib.js | 27 +++- 4 files changed, 345 insertions(+), 16 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 6651946f54..b1e80ef87b 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3738,7 +3738,8 @@ sub git_footer_html { (gitweb_check_feature('javascript-actions') ? qq! fixLinks();\n! : ''). # last parameter to onloadTZSetup must be CSS class used by format_timestamp_html - qq! onloadTZSetup('local', 'gitweb_tz', 'datetime');\n!. + qq! var tz_cookie = { name: 'gitweb_tz', expires: 14, path: '/' };\n!. # in days + qq! onloadTZSetup('local', tz_cookie, 'datetime');\n!. qq!};\n!. qq!\n!; } diff --git a/gitweb/static/gitweb.css b/gitweb/static/gitweb.css index 79d7eebba7..8dd093563e 100644 --- a/gitweb/static/gitweb.css +++ b/gitweb/static/gitweb.css @@ -579,6 +579,39 @@ div.remote { display: inline-block; } +/* JavaScript-based timezone manipulation */ + +.popup { /* timezone selection UI */ + position: absolute; + /* "top: 0; right: 0;" would be better, if not for bugs in browsers */ + top: 0; left: 0; + border: 1px solid; + padding: 2px; + background-color: #f0f0f0; + font-style: normal; + color: #000000; + cursor: auto; +} + +.close-button { /* close timezone selection UI without selecting */ + /* float doesn't work within absolutely positioned container, + * if width of container is not set explicitly */ + /* float: right; */ + position: absolute; + top: 0px; right: 0px; + border: 1px solid green; + margin: 1px 1px 1px 1px; + padding-bottom: 2px; + width: 12px; + height: 10px; + font-size: 9px; + font-weight: bold; + text-align: center; + background-color: #fff0f0; + cursor: pointer; +} + + /* Style definition generated by highlight 2.4.5, http://www.andre-simon.de/ */ /* Highlighting theme definition: */ diff --git a/gitweb/static/js/adjust-timezone.js b/gitweb/static/js/adjust-timezone.js index 1577d780f0..0c67779500 100644 --- a/gitweb/static/js/adjust-timezone.js +++ b/gitweb/static/js/adjust-timezone.js @@ -7,34 +7,51 @@ */ /** - * Get common timezone and adjust dates to use this common timezone. + * Get common timezone, add UI for changing timezones, and adjust + * dates to use requested common timezone. * * This function is called during onload event (added to window.onload). * * @param {String} tzDefault: default timezone, if there is no cookie - * @param {String} tzCookieName: name of cookie to store timezone + * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone + * @param {String} tzCookieInfo.name: name of cookie to store timezone * @param {String} tzClassName: denotes elements with date to be adjusted */ -function onloadTZSetup(tzDefault, tzCookieName, tzClassName) { - var tzCookie = getCookie(tzCookieName); - var tz = tzCookie ? tzCookie : tzDefault; +function onloadTZSetup(tzDefault, tzCookieInfo, tzClassName) { + var tzCookieTZ = getCookie(tzCookieInfo.name, tzCookieInfo); + var tz = tzDefault; + + if (tzCookieTZ) { + // set timezone to value saved in a cookie + tz = tzCookieTZ; + // refresh cookie, so its expiration counts from last use of gitweb + setCookie(tzCookieInfo.name, tzCookieTZ, tzCookieInfo); + } + + // add UI for changing timezone + addChangeTZ(tz, tzCookieInfo, tzClassName); // server-side of gitweb produces datetime in UTC, // so if tz is 'utc' there is no need for changes - if (tz !== 'utc') { - fixDatetimeTZ(tz, tzClassName); - } + var nochange = tz === 'utc'; + + // adjust dates to use specified common timezone + fixDatetimeTZ(tz, tzClassName, nochange); } +/* ...................................................................... */ +/* Changing dates to use requested timezone */ + /** * Replace RFC-2822 dates contained in SPAN elements with tzClassName * CSS class with equivalent dates in given timezone. * * @param {String} tz: numeric timezone in '(-|+)HHMM' format, or 'utc', or 'local' * @param {String} tzClassName: specifies elements to be changed + * @param {Boolean} nochange: markup for timezone change, but don't change it */ -function fixDatetimeTZ(tz, tzClassName) { +function fixDatetimeTZ(tz, tzClassName, nochange) { // sanity check, method should be ensured by common-lib.js if (!document.getElementsByClassName) { return; @@ -48,13 +65,266 @@ function fixDatetimeTZ(tz, tzClassName) { for (var i = 0, len = classesFound.length; i < len; i++) { var curElement = classesFound[i]; - // we use *.firstChild.data (W3C DOM) instead of *.innerHTML - // as the latter doesn't always work everywhere in every browser - var epoch = parseRFC2822Date(curElement.firstChild.data); - var adjusted = formatDateRFC2882(epoch, tz); + curElement.title = 'Click to change timezone'; + if (!nochange) { + // we use *.firstChild.data (W3C DOM) instead of *.innerHTML + // as the latter doesn't always work everywhere in every browser + var epoch = parseRFC2822Date(curElement.firstChild.data); + var adjusted = formatDateRFC2882(epoch, tz); + + curElement.firstChild.data = adjusted; + } + } +} + + +/* ...................................................................... */ +/* Adding triggers, generating timezone menu, displaying and hiding */ + +/** + * Adds triggers for UI to change common timezone used for dates in + * gitweb output: it marks up and/or creates item to click to invoke + * timezone change UI, creates timezone UI fragment to be attached, + * and installs appropriate onclick trigger (via event delegation). + * + * @param {String} tzSelected: pre-selected timezone, + * 'utc' or 'local' or '(-|+)HHMM' + * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone + * @param {String} tzClassName: specifies elements to install trigger + */ +function addChangeTZ(tzSelected, tzCookieInfo, tzClassName) { + // make link to timezone UI discoverable + addCssRule('.'+tzClassName + ':hover', + 'text-decoration: underline; cursor: help;'); + + // create form for selecting timezone (to be saved in a cookie) + var tzSelectFragment = document.createDocumentFragment(); + tzSelectFragment = createChangeTZForm(tzSelectFragment, + tzSelected, tzCookieInfo, tzClassName); + + // event delegation handler for timezone selection UI (clicking on entry) + // see http://www.nczonline.net/blog/2009/06/30/event-delegation-in-javascript/ + // assumes that there is no existing document.onclick handler + document.onclick = function onclickHandler(event) { + //IE doesn't pass in the event object + event = event || window.event; + + //IE uses srcElement as the target + var target = event.target || event.srcElement; + + switch (target.className) { + case tzClassName: + // don't display timezone menu if it is already displayed + if (tzSelectFragment.childNodes.length > 0) { + displayChangeTZForm(target, tzSelectFragment); + } + break; + } // end switch + }; +} + +/** + * Create DocumentFragment with UI for changing common timezone in + * which dates are shown in. + * + * @param {DocumentFragment} documentFragment: where attach UI + * @param {String} tzSelected: default (pre-selected) timezone + * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone + * @returns {DocumentFragment} + */ +function createChangeTZForm(documentFragment, tzSelected, tzCookieInfo, tzClassName) { + var div = document.createElement("div"); + div.className = 'popup'; + + /* '
X
' */ + var closeButton = document.createElement('div'); + closeButton.className = 'close-button'; + closeButton.title = '(click on this box to close)'; + closeButton.appendChild(document.createTextNode('X')); + closeButton.onclick = closeTZFormHandler(documentFragment, tzClassName); + div.appendChild(closeButton); + + /* 'Select timezone:
' */ + div.appendChild(document.createTextNode('Select timezone: ')); + var br = document.createElement('br'); + br.clear = 'all'; + div.appendChild(br); + + /* '' */ + var select = document.createElement("select"); + select.name = "tzoffset"; + //select.style.clear = 'all'; + select.appendChild(generateTZOptions(tzSelected)); + select.onchange = selectTZHandler(documentFragment, tzCookieInfo, tzClassName); + div.appendChild(select); + + documentFragment.appendChild(div); + + return documentFragment; +} + + +/** + * Hide (remove from DOM) timezone change UI, ensuring that it is not + * garbage collected and that it can be re-enabled later. + * + * @param {DocumentFragment} documentFragment: contains detached UI + * @param {HTMLSelectElement} target: select element inside of UI + * @param {String} tzClassName: specifies element where UI was installed + * @returns {DocumentFragment} documentFragment + */ +function removeChangeTZForm(documentFragment, target, tzClassName) { + // find containing element, where we appended timezone selection UI + // `target' is somewhere inside timezone menu + var container = target.parentNode, popup = target; + while (container && + container.className !== tzClassName) { + popup = container; + container = container.parentNode; + } + // safety check if we found correct container, + // and if it isn't deleted already + if (!container || !popup || + container.className !== tzClassName || + popup.className !== 'popup') { + return documentFragment; + } - curElement.firstChild.data = adjusted; + // timezone selection UI was appended as last child + // see also displayChangeTZForm function + var removed = popup.parentNode.removeChild(popup); + if (documentFragment.firstChild !== removed) { // the only child + // re-append it so it would be available for next time + documentFragment.appendChild(removed); } + // all of inline style was added by this script + // it is not really needed to remove it, but it is a good practice + container.removeAttribute('style'); + + return documentFragment; +} + + +/** + * Display UI for changing common timezone for dates in gitweb output. + * To be used from 'onclick' event handler. + * + * @param {HTMLElement} target: where to install/display UI + * @param {DocumentFragment} tzSelectFragment: timezone selection UI + */ +function displayChangeTZForm(target, tzSelectFragment) { + // for absolute positioning to be related to target element + target.style.position = 'relative'; + target.style.display = 'inline-block'; + + // show/display UI for changing timezone + target.appendChild(tzSelectFragment); +} + + +/* ...................................................................... */ +/* List of timezones for timezone selection menu */ + +/** + * Generate list of timezones for creating timezone select UI + * + * @returns {Object[]} list of e.g. { value: '+0100', descr: 'GMT+01:00' } + */ +function generateTZList() { + var timezones = [ + { value: "utc", descr: "UTC/GMT"}, + { value: "local", descr: "Local (per browser)"} + ]; + + // generate all full hour timezones (no fractional timezones) + for (var x = -12, idx = timezones.length; x <= +14; x++, idx++) { + var hours = (x >= 0 ? '+' : '-') + padLeft(x >=0 ? x : -x, 2); + timezones[idx] = { value: hours + '00', descr: 'UTC' + hours + ':00'}; + if (x === 0) { + timezones[idx].descr = 'UTC\u00B100:00'; // 'UTC±00:00' + } + } + + return timezones; +} + +/** + * Generate elements for timezone select UI + * + * @param {String} tzSelected: default timezone + * @returns {DocumentFragment} list of options elements to appendChild + */ +function generateTZOptions(tzSelected) { + var elems = document.createDocumentFragment(); + var timezones = generateTZList(); + + for (var i = 0, len = timezones.length; i < len; i++) { + var tzone = timezones[i]; + var option = document.createElement("option"); + if (tzone.value === tzSelected) { + option.defaultSelected = true; + } + option.value = tzone.value; + option.appendChild(document.createTextNode(tzone.descr)); + + elems.appendChild(option); + } + + return elems; +} + + +/* ...................................................................... */ +/* Event handlers and/or their generators */ + +/** + * Create event handler that select timezone and closes timezone select UI. + * To be used as $('select[name="tzselect"]').onchange handler. + * + * @param {DocumentFragment} tzSelectFragment: timezone selection UI + * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone + * @param {String} tzCookieInfo.name: name of cookie to save result of selection + * @param {String} tzClassName: specifies element where UI was installed + * @returns {Function} event handler + */ +function selectTZHandler(tzSelectFragment, tzCookieInfo, tzClassName) { + //return function selectTZ(event) { + return function (event) { + event = event || window.event; + var target = event.target || event.srcElement; + + var selected = target.options.item(target.selectedIndex); + removeChangeTZForm(tzSelectFragment, target, tzClassName); + + if (selected) { + selected.defaultSelected = true; + setCookie(tzCookieInfo.name, selected.value, tzCookieInfo); + fixDatetimeTZ(selected.value, tzClassName); + } + }; +} + +/** + * Create event handler that closes timezone select UI. + * To be used e.g. as $('.closebutton').onclick handler. + * + * @param {DocumentFragment} tzSelectFragment: timezone selection UI + * @param {String} tzClassName: specifies element where UI was installed + * @returns {Function} event handler + */ +function closeTZFormHandler(tzSelectFragment, tzClassName) { + //return function closeTZForm(event) { + return function (event) { + event = event || window.event; + var target = event.target || event.srcElement; + + removeChangeTZForm(tzSelectFragment, target, tzClassName); + }; } /* end of adjust-timezone.js */ diff --git a/gitweb/static/js/lib/common-lib.js b/gitweb/static/js/lib/common-lib.js index b37139152d..018bbb7d4c 100644 --- a/gitweb/static/js/lib/common-lib.js +++ b/gitweb/static/js/lib/common-lib.js @@ -64,7 +64,7 @@ function padLeft(input, width, ch) { /* ............................................................ */ -/* Ajax */ +/* Handling browser incompatibilities */ /** * Create XMLHttpRequest object in cross-browser way @@ -88,6 +88,31 @@ function createRequestObject() { } +/** + * Insert rule giving specified STYLE to given SELECTOR at the end of + * first CSS stylesheet. + * + * @param {String} selector: CSS selector, e.g. '.class' + * @param {String} style: rule contents, e.g. 'background-color: red;' + */ +function addCssRule(selector, style) { + var stylesheet = document.styleSheets[0]; + + var theRules = []; + if (stylesheet.cssRules) { // W3C way + theRules = stylesheet.cssRules; + } else if (stylesheet.rules) { // IE way + theRules = stylesheet.rules; + } + + if (stylesheet.insertRule) { // W3C way + stylesheet.insertRule(selector + ' { ' + style + ' }', theRules.length); + } else if (stylesheet.addRule) { // IE way + stylesheet.addRule(selector, style); + } +} + + /* ............................................................ */ /* Support for legacy browsers */ -- cgit v1.3 From 2e987f92408f5ddd20246d7d9553b57d90767d54 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 28 Apr 2011 21:04:11 +0200 Subject: gitweb: Make JavaScript ability to adjust timezones configurable Configure JavaScript-based ability to select common timezone for git dates via %feature mechanism, namely 'javascript-timezone' feature. The following settings are configurable: * default timezone (defaults to 'local' i.e. browser timezone); this also can function as a way to disable this ability, by setting it to false-ish value (undef or '') * name of cookie to store user's choice of timezone * class name to mark dates NOTE: This is a bit of abuse of %feature system, which can store only sequence of values, rather than dictionary (hash); usually but not always only a single value is used. Based-on-code-by: John 'Warthog9' Hawley Helped-by: Kevin Cernekee Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b1e80ef87b..ac335b6dea 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -480,6 +480,18 @@ our %feature = ( 'override' => 0, 'default' => [0]}, + # Enable and configure ability to change common timezone for dates + # in gitweb output via JavaScript. Enabled by default. + # Project specific override is not supported. + 'javascript-timezone' => { + 'override' => 0, + 'default' => [ + 'local', # default timezone: 'utc', 'local', or '(-|+)HHMM' format, + # or undef to turn off this feature + 'gitweb_tz', # name of cookie where to store selected timezone + 'datetime', # CSS class used to mark up dates for manipulation + ]}, + # 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, @@ -3733,14 +3745,19 @@ sub git_footer_html { qq! "!. href() .qq!");\n!. qq!\n!; } else { + my ($jstimezone, $tz_cookie, $datetime_class) = + gitweb_get_feature('javascript-timezone'); + print qq!\n!; } @@ -3946,7 +3963,13 @@ sub git_print_section { sub format_timestamp_html { my $date = shift; - my $strtime = ''.$date->{'rfc2822'}.''; + my $strtime = $date->{'rfc2822'}; + + my (undef, undef, $datetime_class) = + gitweb_get_feature('javascript-timezone'); + if ($datetime_class) { + $strtime = qq!$strtime!; + } my $localtime_format = '(%02d:%02d %s)'; if ($date->{'hour_local'} < 6) { -- cgit v1.3 From f612a71cc901e34f5cf154e6605b7251744f6b95 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Wed, 25 May 2011 18:35:26 +0200 Subject: gitweb: Refactor reading and parsing config file into read_config_file Beside being obvious reduction of duplicated code, this is enables us to easily call site-wide config file in per-installation config file. The actual update to documentation is left for next commit, because of possible exclusive alternative (possible other next commit) of always reading system-wide config file and relying on per-instalation config file overriding system-wide defaults. Signed-off-by: Jakub Narebski Acked-by: John 'Warthog9' Hawley Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 66eadb4ab2..dfac1bce84 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -623,18 +623,30 @@ sub filter_snapshot_fmts { # if it is true then gitweb config would be run for each request. our $per_request_config = 1; +# read and parse gitweb config file given by its parameter. +# returns true on success, false on recoverable error, allowing +# to chain this subroutine, using first file that exists. +# dies on errors during parsing config file, as it is unrecoverable. +sub read_config_file { + my $filename = shift; + return unless defined $filename; + # die if there are errors parsing config file + if (-e $filename) { + do $filename; + die $@ if $@; + return 1; + } + return; +} + 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 $@; - } + + # use first config file that exists + read_config_file($GITWEB_CONFIG) or + read_config_file($GITWEB_CONFIG_SYSTEM); } # Get loadavg of system, to compare against $maxload. -- cgit v1.3 From cee694d0126c8990a52c6cbfb68bb67491812c42 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 2 Jun 2011 16:55:52 +0200 Subject: gitweb: Move information about installation from README to INSTALL Almost straightformard moving of "How to configure gitweb for your local system" section from gitweb/README to gitweb/INSTALL, as it is about build time configuration. Updated references to it. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/INSTALL | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- gitweb/README | 114 -------------------------------------------------------- 2 files changed, 113 insertions(+), 116 deletions(-) (limited to 'gitweb') diff --git a/gitweb/INSTALL b/gitweb/INSTALL index 4964a679b3..32a52b7659 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -28,8 +28,7 @@ scripts). Build time configuration ------------------------ -See also "How to configure gitweb for your local system" in README -file for gitweb (in gitweb/README). +See also "How to configure gitweb for your local system" section below. - There are many configuration variables which affect building of gitweb.cgi; see "default configuration for gitweb" section in main @@ -73,6 +72,118 @@ file for gitweb (in gitweb/README). substitute gitweb.min.js and gitweb.min.css for all uses of gitweb.js and gitweb.css in the help files. + +How to configure gitweb for your local system +--------------------------------------------- + +You can specify the following configuration variables when building GIT: + + * GIT_BINDIR + Points where to find the git executable. You should set it up to + the place where the git binary was installed (usually /usr/bin) if you + don't install git from sources together with gitweb. [Default: $(bindir)] + * GITWEB_SITENAME + Shown in the title of all generated pages, defaults to the server name + (SERVER_NAME CGI environment variable) if not set. [No default] + * GITWEB_PROJECTROOT + The root directory for all projects shown by gitweb. Must be set + correctly for gitweb to find repositories to display. See also + "Gitweb repositories" in the INSTALL file for gitweb. [Default: /pub/git] + * GITWEB_PROJECT_MAXDEPTH + The filesystem traversing limit for getting the project list; the number + is taken as depth relative to the projectroot. It is used when + GITWEB_LIST is a directory (or is not set; then project root is used). + This is meant to speed up project listing on large work trees by limiting + search depth. [Default: 2007] + * GITWEB_LIST + Points to a directory to scan for projects (defaults to project root + if not set / if empty) or to a file with explicit listing of projects + (together with projects' ownership). See "Generating projects list + using gitweb" in INSTALL file for gitweb to find out how to generate + such file from scan of a directory. [No default, which means use root + directory for projects] + * GITWEB_EXPORT_OK + Show repository only if this file exists (in repository). Only + effective if this variable evaluates to true. [No default / Not set] + * GITWEB_STRICT_EXPORT + Only allow viewing of repositories also shown on the overview page. + This for example makes GITWEB_EXPORT_OK to decide if repository is + available and not only if it is shown. If GITWEB_LIST points to + file with list of project, only those repositories listed would be + available for gitweb. [No default] + * GITWEB_HOMETEXT + Points to an .html file which is included on the gitweb project + overview page ('projects_list' view), if it exists. Relative to + gitweb.cgi script. [Default: indextext.html] + * GITWEB_SITE_HEADER + Filename of html text to include at top of each page. Relative to + gitweb.cgi script. [No default] + * GITWEB_SITE_FOOTER + Filename of html text to include at bottom of each page. Relative to + gitweb.cgi script. [No default] + * GITWEB_HOME_LINK_STR + String of the home link on top of all pages, leading to $home_link + (usually main gitweb page, which means projects list). Used as first + part of gitweb view "breadcrumb trail": / / . + [Default: projects] + * GITWEB_SITENAME + Name of your site or organization to appear in page titles. Set it + to something descriptive for clearer bookmarks etc. If not set + (if empty) gitweb uses "$SERVER_NAME Git", or "Untitled Git" if + SERVER_NAME CGI environment variable is not set (e.g. if running + gitweb as standalone script). [No default] + * GITWEB_BASE_URL + Git base URLs used for URL to where fetch project from, i.e. full + URL is "$git_base_url/$project". Shown on projects summary page. + Repository URL for project can be also configured per repository; this + takes precedence over URLs composed from base URL and a project name. + Note that you can setup multiple base URLs (for example one for + git:// protocol access, another for http:// access) from the gitweb + config file. [No default] + * GITWEB_CSS + Points to the location where you put gitweb.css on your web server + (or to be more generic, the URI of gitweb stylesheet). Relative to the + base URI of gitweb. Note that you can setup multiple stylesheets from + the gitweb config file. [Default: static/gitweb.css (or + static/gitweb.min.css if the CSSMIN variable is defined / CSS minifier + is used)] + * GITWEB_LOGO + Points to the location where you put git-logo.png on your web server + (or to be more generic URI of logo, 72x27 size, displayed in top right + corner of each gitweb page, and used as logo for Atom feed). Relative + to base URI of gitweb. [Default: static/git-logo.png] + * GITWEB_FAVICON + Points to the location where you put git-favicon.png on your web server + (or to be more generic URI of favicon, assumed to be image/png type; + web browsers that support favicons (website icons) may display them + in the browser's URL bar and next to site name in bookmarks). Relative + to base URI of gitweb. [Default: static/git-favicon.png] + * GITWEB_JS + Points to the location where you put gitweb.js on your web server + (or to be more generic URI of JavaScript code used by gitweb). + Relative to base URI of gitweb. [Default: static/gitweb.js (or + static/gitweb.min.js if JSMIN build variable is defined / JavaScript + minifier is used)] + * GITWEB_CONFIG + This Perl file will be loaded using 'do' and can be used to override any + of the options above as well as some other options -- see the "Runtime + gitweb configuration" section below, and top of 'gitweb.cgi' for their + full list and description. If the environment variable GITWEB_CONFIG + is set when gitweb.cgi is executed, then the file specified in the + environment variable will be loaded instead of the file specified + when gitweb.cgi was created. [Default: gitweb_config.perl] + * GITWEB_CONFIG_SYSTEM + This Perl file will be loaded using 'do' as a fallback if GITWEB_CONFIG + does not exist. If the environment variable GITWEB_CONFIG_SYSTEM is set + when gitweb.cgi is executed, then the file specified in the environment + variable will be loaded instead of the file specified when gitweb.cgi was + created. [Default: /etc/gitweb.conf] + * HIGHLIGHT_BIN + Path to the highlight executable to use (must be the one from + http://www.andre-simon.de due to assumptions about parameters and output). + Useful if highlight is not installed on your webserver's PATH. + [Default: highlight] + Build example ~~~~~~~~~~~~~ diff --git a/gitweb/README b/gitweb/README index a3a697bf55..a4cfcb42cd 100644 --- a/gitweb/README +++ b/gitweb/README @@ -7,120 +7,6 @@ The one working on: From the git version 1.4.0 gitweb is bundled with git. -How to configure gitweb for your local system ---------------------------------------------- - -See also the "Build time configuration" section in the INSTALL -file for gitweb (in gitweb/INSTALL). - -You can specify the following configuration variables when building GIT: - * GIT_BINDIR - Points where to find the git executable. You should set it up to - the place where the git binary was installed (usually /usr/bin) if you - don't install git from sources together with gitweb. [Default: $(bindir)] - * GITWEB_SITENAME - Shown in the title of all generated pages, defaults to the server name - (SERVER_NAME CGI environment variable) if not set. [No default] - * GITWEB_PROJECTROOT - The root directory for all projects shown by gitweb. Must be set - correctly for gitweb to find repositories to display. See also - "Gitweb repositories" in the INSTALL file for gitweb. [Default: /pub/git] - * GITWEB_PROJECT_MAXDEPTH - The filesystem traversing limit for getting the project list; the number - is taken as depth relative to the projectroot. It is used when - GITWEB_LIST is a directory (or is not set; then project root is used). - This is meant to speed up project listing on large work trees by limiting - search depth. [Default: 2007] - * GITWEB_LIST - Points to a directory to scan for projects (defaults to project root - if not set / if empty) or to a file with explicit listing of projects - (together with projects' ownership). See "Generating projects list - using gitweb" in INSTALL file for gitweb to find out how to generate - such file from scan of a directory. [No default, which means use root - directory for projects] - * GITWEB_EXPORT_OK - Show repository only if this file exists (in repository). Only - effective if this variable evaluates to true. [No default / Not set] - * GITWEB_STRICT_EXPORT - Only allow viewing of repositories also shown on the overview page. - This for example makes GITWEB_EXPORT_OK to decide if repository is - available and not only if it is shown. If GITWEB_LIST points to - file with list of project, only those repositories listed would be - available for gitweb. [No default] - * GITWEB_HOMETEXT - Points to an .html file which is included on the gitweb project - overview page ('projects_list' view), if it exists. Relative to - gitweb.cgi script. [Default: indextext.html] - * GITWEB_SITE_HEADER - Filename of html text to include at top of each page. Relative to - gitweb.cgi script. [No default] - * GITWEB_SITE_FOOTER - Filename of html text to include at bottom of each page. Relative to - gitweb.cgi script. [No default] - * GITWEB_HOME_LINK_STR - String of the home link on top of all pages, leading to $home_link - (usually main gitweb page, which means projects list). Used as first - part of gitweb view "breadcrumb trail": / / . - [Default: projects] - * GITWEB_SITENAME - Name of your site or organization to appear in page titles. Set it - to something descriptive for clearer bookmarks etc. If not set - (if empty) gitweb uses "$SERVER_NAME Git", or "Untitled Git" if - SERVER_NAME CGI environment variable is not set (e.g. if running - gitweb as standalone script). [No default] - * GITWEB_BASE_URL - Git base URLs used for URL to where fetch project from, i.e. full - URL is "$git_base_url/$project". Shown on projects summary page. - Repository URL for project can be also configured per repository; this - takes precedence over URLs composed from base URL and a project name. - Note that you can setup multiple base URLs (for example one for - git:// protocol access, another for http:// access) from the gitweb - config file. [No default] - * GITWEB_CSS - Points to the location where you put gitweb.css on your web server - (or to be more generic, the URI of gitweb stylesheet). Relative to the - base URI of gitweb. Note that you can setup multiple stylesheets from - the gitweb config file. [Default: static/gitweb.css (or - static/gitweb.min.css if the CSSMIN variable is defined / CSS minifier - is used)] - * GITWEB_LOGO - Points to the location where you put git-logo.png on your web server - (or to be more generic URI of logo, 72x27 size, displayed in top right - corner of each gitweb page, and used as logo for Atom feed). Relative - to base URI of gitweb. [Default: static/git-logo.png] - * GITWEB_FAVICON - Points to the location where you put git-favicon.png on your web server - (or to be more generic URI of favicon, assumed to be image/png type; - web browsers that support favicons (website icons) may display them - in the browser's URL bar and next to site name in bookmarks). Relative - to base URI of gitweb. [Default: static/git-favicon.png] - * GITWEB_JS - Points to the location where you put gitweb.js on your web server - (or to be more generic URI of JavaScript code used by gitweb). - Relative to base URI of gitweb. [Default: static/gitweb.js (or - static/gitweb.min.js if JSMIN build variable is defined / JavaScript - minifier is used)] - * GITWEB_CONFIG - This Perl file will be loaded using 'do' and can be used to override any - of the options above as well as some other options -- see the "Runtime - gitweb configuration" section below, and top of 'gitweb.cgi' for their - full list and description. If the environment variable GITWEB_CONFIG - is set when gitweb.cgi is executed, then the file specified in the - environment variable will be loaded instead of the file specified - when gitweb.cgi was created. [Default: gitweb_config.perl] - * GITWEB_CONFIG_SYSTEM - This Perl file will be loaded using 'do' as a fallback if GITWEB_CONFIG - does not exist. If the environment variable GITWEB_CONFIG_SYSTEM is set - when gitweb.cgi is executed, then the file specified in the environment - variable will be loaded instead of the file specified when gitweb.cgi was - created. [Default: /etc/gitweb.conf] - * HIGHLIGHT_BIN - Path to the highlight executable to use (must be the one from - http://www.andre-simon.de due to assumptions about parameters and output). - Useful if highlight is not installed on your webserver's PATH. - [Default: highlight] - - Runtime gitweb configuration ---------------------------- -- cgit v1.3 From 560869e32103945f680e0f390fc2062e66b3d04a Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 2 Jun 2011 16:55:53 +0200 Subject: gitweb: Describe CSSMIN and JSMIN in gitweb/INSTALL The build-time configuration variables JSMIN and CSSMIN were mentioned only in Makefile; add their description to gitweb/INSTALL. This required moving description of GITWEB_JS up, near GITWEB_CSS and just introduced CSMIN and JSMIN. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/INSTALL | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) (limited to 'gitweb') diff --git a/gitweb/INSTALL b/gitweb/INSTALL index 32a52b7659..2346aad088 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -147,6 +147,19 @@ You can specify the following configuration variables when building GIT: the gitweb config file. [Default: static/gitweb.css (or static/gitweb.min.css if the CSSMIN variable is defined / CSS minifier is used)] + * GITWEB_JS + Points to the location where you put gitweb.js on your web server + (or to be more generic URI of JavaScript code used by gitweb). + Relative to base URI of gitweb. [Default: static/gitweb.js (or + static/gitweb.min.js if JSMIN build variable is defined / JavaScript + minifier is used)] + * CSSMIN, JSMIN + Invocation of a CSS minifier or a JavaScript minifier, respectively, + working as a filter (source on standard input, minified result on + standard output). If set, it is used to generate a minified version of + 'static/gitweb.css' or 'static/gitweb.js', respectively. *Note* that + minified files would have *.min.css and *.min.js extension, which is + important if you also set GITWEB_CSS and/or GITWEB_JS. [No default] * GITWEB_LOGO Points to the location where you put git-logo.png on your web server (or to be more generic URI of logo, 72x27 size, displayed in top right @@ -158,12 +171,6 @@ You can specify the following configuration variables when building GIT: web browsers that support favicons (website icons) may display them in the browser's URL bar and next to site name in bookmarks). Relative to base URI of gitweb. [Default: static/git-favicon.png] - * GITWEB_JS - Points to the location where you put gitweb.js on your web server - (or to be more generic URI of JavaScript code used by gitweb). - Relative to base URI of gitweb. [Default: static/gitweb.js (or - static/gitweb.min.js if JSMIN build variable is defined / JavaScript - minifier is used)] * GITWEB_CONFIG This Perl file will be loaded using 'do' and can be used to override any of the options above as well as some other options -- see the "Runtime -- cgit v1.3 From 80b4dfeeb2512d5b6d44b7f6eb72b77222072ec2 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 3 Jun 2011 18:31:48 +0200 Subject: gitweb: Move "Requirements" up in gitweb/INSTALL This way you can examine prerequisites at first glance, before detailed instructions on installing gitweb. Straightforward text movement. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/INSTALL | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'gitweb') diff --git a/gitweb/INSTALL b/gitweb/INSTALL index 2346aad088..c5236fee9d 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -25,6 +25,21 @@ The above example assumes that your web server is configured to run scripts). +Requirements +------------ + + - Core git tools + - Perl + - Perl modules: CGI, Encode, Fcntl, File::Find, File::Basename. + - web server + +The following optional Perl modules are required for extra features + - Digest::MD5 - for gravatar support + - CGI::Fast and FCGI - for running gitweb as FastCGI script + - HTML::TagCloud - for fancy tag cloud in project list view + - HTTP::Date or Time::ParseDate - to support If-Modified-Since for feeds + + Build time configuration ------------------------ @@ -347,21 +362,6 @@ $projects_list variable in gitweb config): perl -- /var/www/cgi-bin/gitweb.cgi -Requirements ------------- - - - Core git tools - - Perl - - Perl modules: CGI, Encode, Fcntl, File::Find, File::Basename. - - web server - -The following optional Perl modules are required for extra features - - Digest::MD5 - for gravatar support - - CGI::Fast and FCGI - for running gitweb as FastCGI script - - HTML::TagCloud - for fancy tag cloud in project list view - - HTTP::Date or Time::ParseDate - to support If-Modified-Since for feeds - - Example web server configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.3 From bee6ea17a1bab824eba6133eefc3c70b219ec98c Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 4 Jun 2011 10:43:35 +0200 Subject: gitweb: Fix usability of $prevent_xss With XSS prevention on (enabled using $prevent_xss), blobs ('blob_plain') of all types except a few known safe ones are served with "Content-Disposition: attachment". However the check was too strict; it didn't take into account optional parameter attributes, media-type = type "/" subtype *( ";" parameter ) as described in RFC 2616 http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17 http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 This fixes that, and it for example treats following as safe MIME media type: text/plain; charset=utf-8 Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gitweb') diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index bdaa4e9463..c5548875ff 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -4752,7 +4752,7 @@ sub git_blob_plain { # want to be sure not to break that by serving the image as an # attachment (though Firefox 3 doesn't seem to care). my $sandbox = $prevent_xss && - $type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))$!; + $type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))(?:[ ;]|$)!; print $cgi->header( -type => $type, -- cgit v1.3 From 2c162b56f370f5c33e6a945e6922d598006c5ec4 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Thu, 9 Jun 2011 02:08:57 -0500 Subject: gitweb: do not misparse nonnumeric content tag files that contain a digit v1.7.6-rc0~27^2~4 (gitweb: Change the way "content tags" ('ctags') are handled, 2011-04-29) tried to make gitweb's tag cloud feature more intuitive for webmasters by checking whether the ctags/