From 0dbf027ad2fc946cb63330146560962fa5d43d2d Mon Sep 17 00:00:00 2001 From: Giuseppe Bilotta Date: Sat, 31 Jan 2009 02:31:51 +0100 Subject: gitweb: webserver config for PATH_INFO Document some possible Apache configurations when the path_info feature is enabled in gitweb. Signed-off-by: Giuseppe Bilotta Acked-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/README | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) (limited to 'gitweb/README') diff --git a/gitweb/README b/gitweb/README index 825162a0b6..52ad88b34e 100644 --- a/gitweb/README +++ b/gitweb/README @@ -322,6 +322,82 @@ something like the following in your gitweb.conf (or gitweb_config.perl) file: $home_link = "/"; +PATH_INFO usage +----------------------- +If you enable PATH_INFO usage in gitweb by putting + + $feature{'pathinfo'}{'default'} = [1]; + +in your gitweb.conf, it is possible to set up your server so that it +consumes and produces URLs in the form + +http://git.example.com/project.git/shortlog/sometag + +by using a configuration such as the following, that assumes that +/var/www/gitweb is the DocumentRoot of your webserver, and that it +contains the gitweb.cgi script and complementary static files +(stylesheet, favicon): + + + ServerAlias git.example.com + + DocumentRoot /var/www/gitweb + + + Options ExecCGI + AddHandler cgi-script cgi + + DirectoryIndex gitweb.cgi + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule ^.* /gitweb.cgi/$0 [L,PT] + + + +The rewrite rule guarantees that existing static files will be properly +served, whereas any other URL will be passed to gitweb as PATH_INFO +parameter. + +Notice that in this case you don't need special settings for +@stylesheets, $my_uri and $home_link, but you lose "dumb client" access +to your project .git dirs. A possible workaround for the latter is the +following: in your project root dir (e.g. /pub/git) have the projects +named without a .git extension (e.g. /pub/git/project instead of +/pub/git/project.git) and configure Apache as follows: + + + ServerAlias git.example.com + + DocumentRoot /var/www/gitweb + + AliasMatch ^(/.*?)(\.git)(/.*)? /pub/git$1$3 + + Options ExecCGI + AddHandler cgi-script cgi + + DirectoryIndex gitweb.cgi + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule ^.* /gitweb.cgi/$0 [L,PT] + + + +The additional AliasMatch makes it so that + +http://git.example.com/project.git + +will give raw access to the project's git dir (so that the project can +be cloned), while + +http://git.example.com/project + +will provide human-friendly gitweb access. + + Originally written by: Kay Sievers -- cgit v1.3 From e80f97e20c8122f79450e1f40f569df0b52773e9 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 1 Feb 2009 22:37:45 +0100 Subject: gitweb: Update README that gitweb works better with PATH_INFO One had to configure gitweb for it to find static files (stylesheets, images) when using path_info URLs. Now that it is not necessary thanks to adding BASE element to HTML head if needed, update README to reflect this fact. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/README | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'gitweb/README') diff --git a/gitweb/README b/gitweb/README index 52ad88b34e..a9dc2e57d9 100644 --- a/gitweb/README +++ b/gitweb/README @@ -162,14 +162,12 @@ not include variables usually directly set during build): $GITWEB_LIST during installation. If empty, $projectroot is used to scan for repositories. * $my_url, $my_uri - URL and absolute URL of gitweb script; you might need to set those - variables if you are using 'pathinfo' feature: see also below. + 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. * $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; you might - need to set it up to [base] gitweb URI if you use 'pathinfo' feature - (alternative format of the URLs, with project name embedded directly - in the path part of URL). + "breadcrumbs"). By default set to absolute URI of a page ($my_uri). * @stylesheets List of URIs of stylesheets (relative to base URI of a page). You might specify more than one stylesheet, for example use gitweb.css -- cgit v1.3 From 7e1100e9e939c9178b2aa3969349e9e8d34488bf Mon Sep 17 00:00:00 2001 From: Matt McCutchen Date: Sat, 7 Feb 2009 19:00:09 -0500 Subject: gitweb: add $prevent_xss option to prevent XSS by repository content Add a gitweb configuration variable $prevent_xss that disables features to prevent content in repositories from launching cross-site scripting (XSS) attacks in the gitweb domain. Currently, this option makes gitweb ignore README.html (a better solution may be worked out in the future) and serve a blob_plain file of an untrusted type with "Content-Disposition: attachment", which tells the browser not to show the file at its original URL. The XSS prevention is currently off by default. Signed-off-by: Matt McCutchen Signed-off-by: Junio C Hamano --- gitweb/README | 9 ++++++++- gitweb/gitweb.perl | 21 +++++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) (limited to 'gitweb/README') diff --git a/gitweb/README b/gitweb/README index 825162a0b6..19ae28ef9b 100644 --- a/gitweb/README +++ b/gitweb/README @@ -214,6 +214,11 @@ not include variables usually directly set during build): Rename detection options for git-diff and git-diff-tree. By default ('-M'); set it to ('-C') or ('-C', '-C') to also detect copies, or set it to () if you don't want to have renames detection. + * $prevent_xss + If true, some gitweb features are disabled to prevent content in + repositories from launching cross-site scripting (XSS) attacks. Set this + to true if you don't trust the content of your repositories. The default + is false. Projects list file format @@ -260,7 +265,9 @@ You can use the following files in repository: A .html file (HTML fragment) which is included on the gitweb project summary page inside
block element. You can use it for longer description of a project, to provide links (for example to project's - homepage), etc. + homepage), etc. This is recognized only if XSS prevention is off + ($prevent_xss is false); a way to include a readme safely when XSS + prevention is on may be worked out in the future. * description (or gitweb.description) Short (shortened by default to 25 characters in the projects list page) single line description of a project (of a repository). Plain text file; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 99f71b47c2..bdaa4e9463 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -132,6 +132,10 @@ our $fallback_encoding = 'latin1'; # - one might want to include '-B' option, e.g. '-B', '-M' our @diff_opts = ('-M'); # taken from git_commit +# Disables features that would allow repository owners to inject script into +# the gitweb domain. +our $prevent_xss = 0; + # information about snapshot formats that gitweb is capable of serving our %known_snapshot_formats = ( # name => { @@ -4494,7 +4498,9 @@ sub git_summary { print "\n"; - if (-s "$projectroot/$project/README.html") { + # If XSS prevention is on, we don't include README.html. + # TODO: Allow a readme in some safe format. + if (!$prevent_xss && -s "$projectroot/$project/README.html") { print "
readme
\n" . "
\n"; insert_file("$projectroot/$project/README.html"); @@ -4739,10 +4745,21 @@ sub git_blob_plain { $save_as .= '.txt'; } + # With XSS prevention on, blobs of all types except a few known safe + # ones are served with "Content-Disposition: attachment" to make sure + # they don't run in our security domain. For certain image types, + # blob view writes an tag referring to blob_plain view, and we + # 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))$!; + print $cgi->header( -type => $type, -expires => $expires, - -content_disposition => 'inline; filename="' . $save_as . '"'); + -content_disposition => + ($sandbox ? 'attachment' : 'inline') + . '; filename="' . $save_as . '"'); undef $/; binmode STDOUT, ':raw'; print <$fd>; -- cgit v1.3 From 680ebc01806187b33cab9093c39102468298350f Mon Sep 17 00:00:00 2001 From: Mike Ralphson Date: Fri, 17 Apr 2009 19:13:28 +0100 Subject: Documentation: fix typos / spelling mistakes Signed-off-by: Mike Ralphson Signed-off-by: Junio C Hamano --- Documentation/git-cvsimport.txt | 2 +- Documentation/git-format-patch.txt | 2 +- contrib/thunderbird-patch-inline/README | 4 ++-- gitweb/README | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) (limited to 'gitweb/README') diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt index d7bab13f6c..614e769f4e 100644 --- a/Documentation/git-cvsimport.txt +++ b/Documentation/git-cvsimport.txt @@ -196,7 +196,7 @@ Problems related to tags: If you suspect that any of these issues may apply to the repository you want to import consider using these alternative tools which proved to be -more stable in practise: +more stable in practice: * cvs2git (part of cvs2svn), `http://cvs2svn.tigris.org` * parsecvs, `http://cgit.freedesktop.org/~keithp/parsecvs` diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index eb2fbcff1a..5eddca92c4 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -194,7 +194,7 @@ CONFIGURATION ------------- You can specify extra mail header lines to be added to each message in the repository configuration, new defaults for the subject prefix -and file suffix, control attachements, and number patches when outputting +and file suffix, control attachments, and number patches when outputting more than one. ------------ diff --git a/contrib/thunderbird-patch-inline/README b/contrib/thunderbird-patch-inline/README index 39f96aa115..000147bbe4 100644 --- a/contrib/thunderbird-patch-inline/README +++ b/contrib/thunderbird-patch-inline/README @@ -1,12 +1,12 @@ appp.sh is a script that is supposed to be used together with ExternalEditor -for Mozilla Thundebird. It will let you include patches inline in e-mails +for Mozilla Thunderbird. It will let you include patches inline in e-mails in an easy way. Usage: - Generate the patch with git format-patch. - Start writing a new e-mail in Thunderbird. - Press the external editor button (or Ctrl-E) to run appp.sh -- Select the previosly generated patch file. +- Select the previously generated patch file. - Finish editing the e-mail. Any text that is entered into the message editor before appp.sh is called diff --git a/gitweb/README b/gitweb/README index 8433dd1d45..ccda890c0e 100644 --- a/gitweb/README +++ b/gitweb/README @@ -206,7 +206,7 @@ not include variables usually directly set during build): * $fallback_encoding Gitweb assumes this charset if line contains non-UTF-8 characters. Fallback decoding is used without error checking, so it can be even - 'utf-8'. Value mist be valid encodig; see Encoding::Supported(3pm) man + 'utf-8'. Value must be valid encoding; see Encoding::Supported(3pm) man page for a list. By default 'latin1', aka. 'iso-8859-1'. * @diff_opts Rename detection options for git-diff and git-diff-tree. By default -- cgit v1.3 From 1bed73c64a354248d6b342e58df257e8233bcbd2 Mon Sep 17 00:00:00 2001 From: Giuseppe Bilotta Date: Sat, 27 Jun 2009 18:24:11 +0200 Subject: gitweb/README: fix AliasMatch in example When combining "dumb client" and human-friendly access by using the '.git' extension to switch between the two, make sure the AliasMatch covers the entire request. Without a full match, a request for http://git.example.com/project/shortlog/branch..gitsomething would result in a 404 because the server would try to access the the project 'project/shortlog/branch.' The solution is still not bulletproof, so document the possible failing case. Signed-off-by: Giuseppe Bilotta Signed-off-by: Junio C Hamano --- gitweb/README | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'gitweb/README') diff --git a/gitweb/README b/gitweb/README index ccda890c0e..9056d1e090 100644 --- a/gitweb/README +++ b/gitweb/README @@ -377,7 +377,7 @@ named without a .git extension (e.g. /pub/git/project instead of DocumentRoot /var/www/gitweb - AliasMatch ^(/.*?)(\.git)(/.*)? /pub/git$1$3 + AliasMatch ^(/.*?)(\.git)(/.*)?$ /pub/git$1$3 Options ExecCGI AddHandler cgi-script cgi @@ -402,6 +402,14 @@ http://git.example.com/project will provide human-friendly gitweb access. +This solution is not 100% bulletproof, in the sense that if some project +has a named ref (branch, tag) starting with 'git/', then paths such as + +http://git.example.com/project/command/abranch..git/abranch + +will fail with a 404 error. + + Originally written by: Kay Sievers -- cgit v1.3 From 46068383aa825dfe9026f9255cea07da07e06253 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 4 Aug 2009 17:54:32 +0200 Subject: gitweb/README: Document $base_url Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/README | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'gitweb/README') diff --git a/gitweb/README b/gitweb/README index 9056d1e090..66c6a9391d 100644 --- a/gitweb/README +++ b/gitweb/README @@ -165,6 +165,12 @@ not include variables usually directly set during build): 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. + * $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 + Date: Tue, 1 Sep 2009 13:39:17 +0200 Subject: gitweb: Incremental blame (using JavaScript) Add 'blame_incremental' view, which uses "git blame --incremental" and JavaScript (Ajax), where 'blame' use "git blame --porcelain". * gitweb generates initial info by putting file contents (from "git cat-file") together with line numbers in blame table * then gitweb makes web browser JavaScript engine call startBlame() function from gitweb.js * startBlame() opens XMLHttpRequest connection to 'blame_data' view, which in turn calls "git blame --incremental" for a file, and streams output of git-blame to JavaScript (gitweb.js) * XMLHttpRequest event handler updates line info in blame view as soon as it gets data from 'blame_data' (from server), and it also updates progress info * when 'blame_data' ends, and gitweb.js finishes updating line info, it fixes colors to match (as far as possible) ordinary 'blame' view, and updates information about how long it took to generate page. Gitweb deals with streamed 'blame_data' server errors by displaying them in the progress info area (just in case). The 'blame_incremental' view tries to be equivalent to 'blame' action; there are however a few differences in output between 'blame' and 'blame_incremental' view: * 'blame_incremental' always used query form for this part of link(s) which is generated by JavaScript code. The difference is visible if we use path_info link (pass some or all arguments in path_info). Changing this would require implementing something akin to href() subroutine from gitweb.perl in JavaScript (in gitweb.js). * 'blame_incremental' always uses "rowspan" attribute, even if rowspan="1". This simplifies code, and is not visible to user. * The progress bar and progress info are still there even after JavaScript part of 'blame_incremental' finishes work. Note that currently no link generated by gitweb leads to this new view. This code is based on patch by Petr Baudis patch, which in turn was tweaked up version of Fredrik Kuivinen 's proof of concept patch. This patch adds GITWEB_JS compile configuration option, and modifies git-instaweb.sh to take gitweb.js into account. The code for git-instaweb.sh was taken from Pasky's patch. Signed-off-by: Fredrik Kuivinen Signed-off-by: Petr Baudis Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- Makefile | 6 +- git-instaweb.sh | 7 + gitweb/README | 4 + gitweb/gitweb.css | 11 + gitweb/gitweb.js | 726 +++++++++++++++++++++++++++++++++++++++++++++++++++++ gitweb/gitweb.perl | 274 +++++++++++++------- 6 files changed, 940 insertions(+), 88 deletions(-) create mode 100644 gitweb/gitweb.js (limited to 'gitweb/README') diff --git a/Makefile b/Makefile index daf4296706..2ad3b1e40c 100644 --- a/Makefile +++ b/Makefile @@ -265,6 +265,7 @@ GITWEB_HOMETEXT = indextext.html GITWEB_CSS = gitweb.css GITWEB_LOGO = git-logo.png GITWEB_FAVICON = git-favicon.png +GITWEB_JS = gitweb.js GITWEB_SITE_HEADER = GITWEB_SITE_FOOTER = @@ -1407,13 +1408,14 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl -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' \ $< >$@+ && \ chmod +x $@+ && \ mv $@+ $@ -git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css +git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css gitweb/gitweb.js $(QUIET_GEN)$(RM) $@ $@+ && \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ @@ -1422,6 +1424,8 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css -e '/@@GITWEB_CGI@@/d' \ -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \ -e '/@@GITWEB_CSS@@/d' \ + -e '/@@GITWEB_JS@@/r gitweb/gitweb.js' \ + -e '/@@GITWEB_JS@@/d' \ -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \ $@.sh > $@+ && \ chmod +x $@+ && \ diff --git a/git-instaweb.sh b/git-instaweb.sh index 5f4419b69b..6f381c88da 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -331,8 +331,15 @@ gitweb_css () { EOFGITWEB } +gitweb_js () { + cat > "$1" <<\EOFGITWEB +@@GITWEB_JS@@ +EOFGITWEB +} + gitweb_cgi "$GIT_DIR/gitweb/gitweb.cgi" gitweb_css "$GIT_DIR/gitweb/gitweb.css" +gitweb_js "$GIT_DIR/gitweb/gitweb.js" case "$httpd" in *lighttpd*) diff --git a/gitweb/README b/gitweb/README index 9056d1e090..cbcd993ecb 100644 --- a/gitweb/README +++ b/gitweb/README @@ -92,6 +92,10 @@ 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: 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] * 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/gitweb.css b/gitweb/gitweb.css index 9893443edc..4a2e496568 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -348,6 +348,17 @@ 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; +} + /* styling of diffs (patchsets): commitdiff and blobdiff views */ div.diff.header, div.diff.extended_header { diff --git a/gitweb/gitweb.js b/gitweb/gitweb.js new file mode 100644 index 0000000000..b15e2a7944 --- /dev/null +++ b/gitweb/gitweb.js @@ -0,0 +1,726 @@ +// Copyright (C) 2007, Fredrik Kuivinen +// 2007, Petr Baudis +// 2008-2009, Jakub Narebski + +/** + * @fileOverview JavaScript code for gitweb (git web interface). + * @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. + */ + + +/* ============================================================ */ +/* generic utility functions */ + + +/** + * pad number N with nonbreakable spaces on the left, to WIDTH characters + * example: padLeftStr(12, 3, ' ') == ' 12' + * (' ' 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. ' ' + * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR + */ +function padLeftStr(input, width, str) { + var prefix = ''; + + width -= input.toString().length; + while (width > 1) { + 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, ' ') + '%)'; + } + + 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 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'; +} + +var colorRe = /(?:light|dark)/; + +/** + * 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); + } + + // 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 = 'light'; // or tr.className + 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; + a_sha1.firstChild.data = commit.sha1.substr(0, 8); + if (group.numlines >= 2) { + var fragment = document.createDocumentFragment(); + var br = document.createElement("br"); + var text = document.createTextNode( + commit.author.match(/\b([A-Z])\B/g).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 + if (xhr.readyState === 3 && xhr.status !== 200) { + 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/gitweb.perl b/gitweb/gitweb.perl index 18cbf35391..6cdd8c39b4 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -96,6 +96,8 @@ our $stylesheet = undef; our $logo = "++GITWEB_LOGO++"; # URI of GIT favicon, assumed to be image/png type our $favicon = "++GITWEB_FAVICON++"; +# URI of gitweb.js (JavaScript code for gitweb) +our $javascript = "++GITWEB_JS++"; # URI and label (title) of GIT logo link #our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/"; @@ -564,6 +566,8 @@ our %cgi_param_mapping = @cgi_param_mapping; # we will also need to know the possible actions, for validation our %actions = ( "blame" => \&git_blame, + "blame_incremental" => \&git_blame_incremental, + "blame_data" => \&git_blame_data, "blobdiff" => \&git_blobdiff, "blobdiff_plain" => \&git_blobdiff_plain, "blob" => \&git_blob, @@ -4787,7 +4791,9 @@ sub git_tag { git_footer_html(); } -sub git_blame { +sub git_blame_common { + my $format = shift || 'porcelain'; + # permissions gitweb_check_feature('blame') or die_error(403, "Blame view not allowed"); @@ -4809,10 +4815,43 @@ sub git_blame { } } - # run git-blame --porcelain - open my $fd, "-|", git_cmd(), "blame", '-p', - $hash_base, '--', $file_name - or die_error(500, "Open git-blame failed"); + my $fd; + if ($format eq 'incremental') { + # get file contents (as base) + open $fd, "-|", git_cmd(), 'cat-file', 'blob', $hash + or die_error(500, "Open git-cat-file failed"); + } elsif ($format eq 'data') { + # run git-blame --incremental + open $fd, "-|", git_cmd(), "blame", "--incremental", + $hash_base, "--", $file_name + or die_error(500, "Open git-blame --incremental failed"); + } else { + # run git-blame --porcelain + open $fd, "-|", git_cmd(), "blame", '-p', + $hash_base, '--', $file_name + or die_error(500, "Open git-blame --porcelain failed"); + } + + # incremental blame data returns early + if ($format eq 'data') { + print $cgi->header( + -type=>"text/plain", -charset => "utf-8", + -status=> "200 OK"); + local $| = 1; # output autoflush + print while <$fd>; + close $fd + or print "ERROR $!\n"; + + print 'END'; + if (defined $t0 && gitweb_check_feature('timed')) { + print ' '. + Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]). + ' '.$number_of_git_cmds; + } + print "\n"; + + return; + } # page header git_header_html(); @@ -4823,109 +4862,170 @@ sub git_blame { $cgi->a({-href => href(action=>"history", -replay=>1)}, "history") . " | " . - $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, + $cgi->a({-href => href(action=>$action, file_name=>$file_name)}, "HEAD"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, $ftype, $hash_base); # page body + if ($format eq 'incremental') { + print "\n"; + + print qq!
\n!; + } + + print qq!
\n!; + print qq!
... / ...
\n! + if ($format eq 'incremental'); + print qq!\n!. + #qq!\n!. + qq!\n!. + qq!\n!. + qq!\n!. + qq!\n!; + my @rev_color = qw(light dark); my $num_colors = scalar(@rev_color); my $current_color = 0; - my %metainfo = (); - print < -
CommitLineData
- -HTML - LINE: - while (my $line = <$fd>) { - chomp $line; - # the header: [] - # no for subsequent lines in group of lines - my ($full_rev, $orig_lineno, $lineno, $group_size) = - ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/); - if (!exists $metainfo{$full_rev}) { - $metainfo{$full_rev} = { 'nprevious' => 0 }; - } - my $meta = $metainfo{$full_rev}; - my $data; - while ($data = <$fd>) { - chomp $data; - last if ($data =~ s/^\t//); # contents of line - if ($data =~ /^(\S+)(?: (.*))?$/) { - $meta->{$1} = $2 unless exists $meta->{$1}; + if ($format eq 'incremental') { + my $color_class = $rev_color[$current_color]; + + #contents of a file + my $linenr = 0; + LINE: + while (my $line = <$fd>) { + chomp $line; + $linenr++; + + print qq!!. + qq!!. + qq!!; + print qq!\n"; + print qq!\n!; + } + + } else { # porcelain, i.e. ordinary blame + my %metainfo = (); # saves information about commits + + # blame data + LINE: + while (my $line = <$fd>) { + chomp $line; + # the header: [] + # no for subsequent lines in group of lines + my ($full_rev, $orig_lineno, $lineno, $group_size) = + ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/); + if (!exists $metainfo{$full_rev}) { + $metainfo{$full_rev} = { 'nprevious' => 0 }; } - if ($data =~ /^previous /) { - $meta->{'nprevious'}++; + my $meta = $metainfo{$full_rev}; + my $data; + while ($data = <$fd>) { + chomp $data; + last if ($data =~ s/^\t//); # contents of line + if ($data =~ /^(\S+)(?: (.*))?$/) { + $meta->{$1} = $2 unless exists $meta->{$1}; + } + if ($data =~ /^previous /) { + $meta->{'nprevious'}++; + } } - } - my $short_rev = substr($full_rev, 0, 8); - my $author = $meta->{'author'}; - my %date = - parse_date($meta->{'author-time'}, $meta->{'author-tz'}); - my $date = $date{'iso-tz'}; - if ($group_size) { - $current_color = ($current_color + 1) % $num_colors; - } - my $tr_class = $rev_color[$current_color]; - $tr_class .= ' boundary' if (exists $meta->{'boundary'}); - $tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0); - $tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1); - print "\n"; - if ($group_size) { - print "\n"; + if ($group_size) { + print "\n"; } - print "\n"; - } - # 'previous' - if (exists $meta->{'previous'} && - $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) { - $meta->{'parent'} = $1; - $meta->{'file_parent'} = unquote($2); - } - my $linenr_commit = - exists($meta->{'parent'}) ? - $meta->{'parent'} : $full_rev; - my $linenr_filename = - exists($meta->{'file_parent'}) ? - $meta->{'file_parent'} : unquote($meta->{'filename'}); - my $blamed = href(action => 'blame', - file_name => $linenr_filename, - hash_base => $linenr_commit); - print ""; - print "\n"; - print "\n"; + # 'previous' + if (exists $meta->{'previous'} && + $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) { + $meta->{'parent'} = $1; + $meta->{'file_parent'} = unquote($2); + } + my $linenr_commit = + exists($meta->{'parent'}) ? + $meta->{'parent'} : $full_rev; + my $linenr_filename = + exists($meta->{'file_parent'}) ? + $meta->{'file_parent'} : unquote($meta->{'filename'}); + my $blamed = href(action => 'blame', + file_name => $linenr_filename, + hash_base => $linenr_commit); + print ""; + print "\n"; + print "\n"; + } # end while + } - print "
CommitLineData
!. + qq!$linenr! . esc_html($line) . "
1); - print ">"; - print $cgi->a({-href => href(action=>"commit", - hash=>$full_rev, - file_name=>$file_name)}, - esc_html($short_rev)); - if ($group_size >= 2) { - my @author_initials = ($author =~ /\b([[:upper:]])\B/g); - if (@author_initials) { - print "
" . - esc_html(join('', @author_initials)); - # or join('.', ...) + my $short_rev = substr($full_rev, 0, 8); + my $author = $meta->{'author'}; + my %date = + parse_date($meta->{'author-time'}, $meta->{'author-tz'}); + my $date = $date{'iso-tz'}; + if ($group_size) { + $current_color = ($current_color + 1) % $num_colors; + } + my $tr_class = $rev_color[$current_color]; + $tr_class .= ' boundary' if (exists $meta->{'boundary'}); + $tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0); + $tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1); + print "
1); + print ">"; + print $cgi->a({-href => href(action=>"commit", + hash=>$full_rev, + file_name=>$file_name)}, + esc_html($short_rev)); + if ($group_size >= 2) { + my @author_initials = ($author =~ /\b([[:upper:]])\B/g); + if (@author_initials) { + print "
" . + esc_html(join('', @author_initials)); + # or join('.', ...) + } } + print "
"; - print $cgi->a({ -href => "$blamed#l$orig_lineno", - -class => "linenr" }, - esc_html($lineno)); - print "" . esc_html($data) . "
"; + print $cgi->a({ -href => "$blamed#l$orig_lineno", + -class => "linenr" }, + esc_html($lineno)); + print "" . esc_html($data) . "
\n"; - print "
"; + + # footer + print "\n". + "\n"; # class="blame" + print "
\n"; # class="blame_body" close $fd or print "Reading blob failed\n"; - # page footer + if ($format eq 'incremental') { + print qq!\n!. + qq!\n!; + } + git_footer_html(); } +sub git_blame { + git_blame_common(); +} + +sub git_blame_incremental { + git_blame_common('incremental'); +} + +sub git_blame_data { + git_blame_common('data'); +} + sub git_tags { my $head = git_get_head_hash($project); git_header_html(); -- cgit v1.3 From eff726f0c28097e7964eb876d005e50e0dcdb9a0 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Wed, 2 Dec 2009 22:14:36 +0100 Subject: gitweb: Describe (possible) gitweb.js minification in gitweb/README Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/README | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'gitweb/README') diff --git a/gitweb/README b/gitweb/README index b69b0e5042..e34ee793ef 100644 --- a/gitweb/README +++ b/gitweb/README @@ -95,7 +95,8 @@ You can specify the following configuration variables when building GIT: * 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] + Relative to base URI of gitweb. [Default: gitweb.js (or 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 b62a1a98bc65f52d972a3f403c5b29d6a84642fd Mon Sep 17 00:00:00 2001 From: John 'Warthog9' Hawley Date: Sat, 30 Jan 2010 23:30:39 +0100 Subject: gitweb: Load checking This changes slightly the behavior of gitweb, so that it verifies that the box isn't inundated with before attempting to serve gitweb. If the box is overloaded, it basically returns a 503 Server Unavailable until the load falls below the defined threshold. This helps dramatically if you have a box that's I/O bound, reaches a certain load and you don't want gitweb, the I/O hog that it is, increasing the pain the server is already undergoing. This behavior is controlled by $maxload configuration variable. Default is a load of 300, which for most cases should never be hit. Unset it (set it to undefined value, i.e. undef) to turn off checking. Currently it requires that '/proc/loadavg' file exists, otherwise the load check is bypassed (load is taken to be 0). So platforms that do not implement '/proc/loadavg' currently cannot use this feature (provisions are included for additional checks to be added by others). There is simple test in t/t9501-gitweb-standalone-http-status.sh to check that it correctly returns "503 Service Unavailable" if load is too high, and also if there are any Perl warnings or errors. Signed-off-by: John 'Warthog9' Hawley Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/README | 7 ++++- gitweb/gitweb.perl | 48 ++++++++++++++++++++++++++++---- t/gitweb-lib.sh | 1 + t/t9501-gitweb-standalone-http-status.sh | 22 +++++++++++++++ 4 files changed, 72 insertions(+), 6 deletions(-) (limited to 'gitweb/README') diff --git a/gitweb/README b/gitweb/README index e34ee793ef..6c2c8e1259 100644 --- a/gitweb/README +++ b/gitweb/README @@ -174,7 +174,7 @@ not include variables usually directly set during build): 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 "/". * $home_link Target of the home link on top of all pages (the first part of view @@ -228,6 +228,11 @@ not include variables usually directly set during build): repositories from launching cross-site scripting (XSS) attacks. Set this to true if you don't trust the content of your repositories. The default 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. + 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. Projects list file format diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 7e477af956..e2522cc64f 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -221,6 +221,12 @@ our %avatar_size = ( 'double' => 32 ); +# Used to set the maximum load that we will still respond to gitweb queries. +# If server load exceed this value then return "503 server busy" error. +# If gitweb cannot determined server load, it is taken to be 0. +# Leave it undefined (or set to 'undef') to turn off load checking. +our $maxload = 300; + # You define site-wide feature defaults here; override them with # $GITWEB_CONFIG as necessary. our %feature = ( @@ -551,12 +557,38 @@ if (-e $GITWEB_CONFIG) { do $GITWEB_CONFIG_SYSTEM if -e $GITWEB_CONFIG_SYSTEM; } +# Get loadavg of system, to compare against $maxload. +# Currently it requires '/proc/loadavg' present to get loadavg; +# if it is not present it returns 0, which means no load checking. +sub get_loadavg { + if( -e '/proc/loadavg' ){ + open my $fd, '<', '/proc/loadavg' + or return 0; + my @load = split(/\s+/, scalar <$fd>); + close $fd; + + # The first three columns measure CPU and IO utilization of the last one, + # five, and 10 minute periods. The fourth column shows the number of + # currently running processes and the total number of processes in the m/n + # format. The last column displays the last process ID used. + return $load[0] || 0; + } + # additional checks for load average should go here for things that don't export + # /proc/loadavg + + return 0; +} + # version of the core git binary our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown"; $number_of_git_cmds++; $projects_list ||= $projectroot; +if (defined $maxload && get_loadavg() > $maxload) { + die_error(503, "The load average on the server is too high"); +} + # ====================================================================== # input validation and dispatch @@ -3328,7 +3360,8 @@ sub git_footer_html { } print qq!\n!; - if ($action eq 'blame_incremental') { + if (defined $action && + $action eq 'blame_incremental') { print qq!