Merge branch 'jn/gitweb-blame'
authorJunio C Hamano <gitster@pobox.com>
Tue, 1 Dec 2009 19:28:15 +0000 (11:28 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 1 Dec 2009 19:28:15 +0000 (11:28 -0800)
* jn/gitweb-blame:
gitweb: Add link to other blame implementation in blame views
gitweb: Make linking to actions requiring JavaScript a feature
gitweb.js: fix padLeftStr() and its usage
gitweb.js: Harden setting blamed commit info in incremental blame
gitweb.js: fix null object exception in initials calculation
gitweb: Minify gitweb.js if JSMIN is defined
gitweb: Create links leading to 'blame_incremental' using JavaScript
gitweb: Colorize 'blame_incremental' view during processing
gitweb: Incremental blame (using JavaScript)
gitweb: Add optional "time to generate page" info in footer

Conflicts:
Makefile
gitweb/gitweb.css

1  2 
Makefile
git-instaweb.sh
gitweb/README
gitweb/gitweb.css
gitweb/gitweb.perl
diff --combined Makefile
index 4dba10e7f06d816741697356f402e2a2b5da3dbe,9b7dc036b6d6e3d3760e0c900a2aad600093d9d6..4a1e5bcc4def58a5d8f05a368fea69cde8ddb74e
+++ b/Makefile
@@@ -16,7 -16,7 +16,7 @@@ all:
  # when attempting to read from an fopen'ed directory.
  #
  # Define NO_OPENSSL environment variable if you do not have OpenSSL.
 -# This also implies MOZILLA_SHA1.
 +# This also implies BLK_SHA1.
  #
  # Define NO_CURL if you do not have libcurl installed.  git-http-pull and
  # git-http-push are not built, and you cannot use http:// and https://
  # specify your own (or DarwinPort's) include directories and
  # library directories by defining CFLAGS and LDFLAGS appropriately.
  #
 +# Define BLK_SHA1 environment variable if you want the C version
 +# of the SHA1 that assumes you can do unaligned 32-bit loads and
 +# have a fast htonl() function.
 +#
  # Define PPC_SHA1 environment variable when running make to make use of
  # a bundled SHA1 routine optimized for PowerPC.
  #
 -# Define ARM_SHA1 environment variable when running make to make use of
 -# a bundled SHA1 routine optimized for ARM.
 -#
 -# Define MOZILLA_SHA1 environment variable when running make to make use of
 -# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast
 -# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
 -# choice) has very fast version optimized for i586.
 +# Define NEEDS_CRYPTO_WITH_SSL if you need -lcrypto when using -lssl (Darwin).
  #
 -# Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin).
 +# Define NEEDS_SSL_WITH_CRYPTO if you need -lssl when using -lcrypto (Darwin).
  #
  # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin).
  #
  #
  # Define ASCIIDOC8 if you want to format documentation with AsciiDoc 8
  #
 -# Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72.
 +# Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72
 +# (not v1.73 or v1.71).
 +#
 +# Define ASCIIDOC_NO_ROFF if your DocBook XSL escapes raw roff directives
 +# (versions 1.72 and later and 1.68.1 and earlier).
 +#
 +# Define GNU_ROFF if your target system uses GNU groff.  This forces
 +# apostrophes to be ASCII so that cut&pasting examples to the shell
 +# will work.
  #
  # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
  # MakeMaker (e.g. using ActiveState under Cygwin).
  #
  # Define NO_REGEX if you have no or inferior regex support in your C library.
  #
+ # Define JSMIN to point to JavaScript minifier that functions as
+ # a filter to have gitweb.js minified.
++#
 +# 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.
 +#
 +# Define DEFAULT_EDITOR to a sensible editor command (defaults to "vi") if you
 +# want to use something different.  The value will be interpreted by the shell
 +# if necessary when it is used.  Examples:
 +#
 +#   DEFAULT_EDITOR='~/bin/vi',
 +#   DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR',
 +#   DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork'
  
  GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@@ -228,12 -213,6 +231,12 @@@ uname_R := $(shell sh -c 'uname -r 2>/d
  uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not')
  uname_V := $(shell sh -c 'uname -v 2>/dev/null || echo not')
  
 +ifdef MSVC
 +      # avoid the MingW and Cygwin configuration sections
 +      uname_S := Windows
 +      uname_O := Windows
 +endif
 +
  # CFLAGS and LDFLAGS are for the users to override from the command line.
  
  CFLAGS = -g -O2 -Wall
@@@ -274,6 -253,9 +277,9 @@@ lib = li
  # DESTDIR=
  pathsep = :
  
+ # JavaScript minifier invocation that can function as filter
+ JSMIN =
  # default configuration for gitweb
  GITWEB_CONFIG = gitweb_config.perl
  GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf
@@@ -289,6 -271,11 +295,11 @@@ GITWEB_HOMETEXT = indextext.htm
  GITWEB_CSS = gitweb.css
  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 =
  
@@@ -343,7 -330,6 +354,7 @@@ SCRIPT_SH += git-merge-one-file.s
  SCRIPT_SH += git-merge-resolve.sh
  SCRIPT_SH += git-mergetool.sh
  SCRIPT_SH += git-mergetool--lib.sh
 +SCRIPT_SH += git-notes.sh
  SCRIPT_SH += git-parse-remote.sh
  SCRIPT_SH += git-pull.sh
  SCRIPT_SH += git-quiltimport.sh
@@@ -377,7 -363,6 +388,7 @@@ EXTRA_PROGRAMS 
  PROGRAMS += $(EXTRA_PROGRAMS)
  PROGRAMS += git-fast-import$X
  PROGRAMS += git-hash-object$X
 +PROGRAMS += git-imap-send$X
  PROGRAMS += git-index-pack$X
  PROGRAMS += git-merge-index$X
  PROGRAMS += git-merge-tree$X
@@@ -387,9 -372,9 +398,9 @@@ PROGRAMS += git-patch-id$
  PROGRAMS += git-shell$X
  PROGRAMS += git-show-index$X
  PROGRAMS += git-unpack-file$X
 -PROGRAMS += git-update-server-info$X
  PROGRAMS += git-upload-pack$X
  PROGRAMS += git-var$X
 +PROGRAMS += git-http-backend$X
  
  # List built-in command $C whose implementation cmd_$C() is not in
  # builtin-$C.o but is linked in as part of some other command.
@@@ -409,8 -394,7 +420,8 @@@ BUILT_INS += git-stage$
  BUILT_INS += git-status$X
  BUILT_INS += git-whatchanged$X
  
 -# what 'all' will build and 'install' will install, in gitexecdir
 +# what 'all' will build and 'install' will install in gitexecdir,
 +# excluding programs for built-in commands
  ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
  
  # what 'all' will build but not install in gitexecdir
@@@ -429,7 -413,6 +440,7 @@@ export PERL_PAT
  LIB_FILE=libgit.a
  XDIFF_LIB=xdiff/lib.a
  
 +LIB_H += advice.h
  LIB_H += archive.h
  LIB_H += attr.h
  LIB_H += blob.h
@@@ -437,7 -420,6 +448,7 @@@ LIB_H += builtin.
  LIB_H += cache.h
  LIB_H += cache-tree.h
  LIB_H += commit.h
 +LIB_H += compat/bswap.h
  LIB_H += compat/cygwin.h
  LIB_H += compat/mingw.h
  LIB_H += csum-file.h
@@@ -458,7 -440,6 +469,7 @@@ LIB_H += ll-merge.
  LIB_H += log-tree.h
  LIB_H += mailmap.h
  LIB_H += merge-recursive.h
 +LIB_H += notes.h
  LIB_H += object.h
  LIB_H += pack.h
  LIB_H += pack-refs.h
@@@ -479,7 -460,6 +490,7 @@@ LIB_H += sideband.
  LIB_H += sigchain.h
  LIB_H += strbuf.h
  LIB_H += string-list.h
 +LIB_H += submodule.h
  LIB_H += tag.h
  LIB_H += transport.h
  LIB_H += tree.h
@@@ -490,7 -470,6 +501,7 @@@ LIB_H += utf8.
  LIB_H += wt-status.h
  
  LIB_OBJS += abspath.o
 +LIB_OBJS += advice.o
  LIB_OBJS += alias.o
  LIB_OBJS += alloc.o
  LIB_OBJS += archive.o
@@@ -544,7 -523,6 +555,7 @@@ LIB_OBJS += match-trees.
  LIB_OBJS += merge-file.o
  LIB_OBJS += merge-recursive.o
  LIB_OBJS += name-hash.o
 +LIB_OBJS += notes.o
  LIB_OBJS += object.o
  LIB_OBJS += pack-check.o
  LIB_OBJS += pack-refs.o
@@@ -565,7 -543,6 +576,7 @@@ LIB_OBJS += read-cache.
  LIB_OBJS += reflog-walk.o
  LIB_OBJS += refs.o
  LIB_OBJS += remote.o
 +LIB_OBJS += replace_object.o
  LIB_OBJS += rerere.o
  LIB_OBJS += revision.o
  LIB_OBJS += run-command.o
@@@ -579,12 -556,10 +590,12 @@@ LIB_OBJS += sideband.
  LIB_OBJS += sigchain.o
  LIB_OBJS += strbuf.o
  LIB_OBJS += string-list.o
 +LIB_OBJS += submodule.o
  LIB_OBJS += symlinks.o
  LIB_OBJS += tag.o
  LIB_OBJS += trace.o
  LIB_OBJS += transport.o
 +LIB_OBJS += transport-helper.o
  LIB_OBJS += tree-diff.o
  LIB_OBJS += tree.o
  LIB_OBJS += tree-walk.o
@@@ -624,6 -599,7 +635,6 @@@ BUILTIN_OBJS += builtin-diff-index.
  BUILTIN_OBJS += builtin-diff-tree.o
  BUILTIN_OBJS += builtin-diff.o
  BUILTIN_OBJS += builtin-fast-export.o
 -BUILTIN_OBJS += builtin-fetch--tool.o
  BUILTIN_OBJS += builtin-fetch-pack.o
  BUILTIN_OBJS += builtin-fetch.o
  BUILTIN_OBJS += builtin-fmt-merge-msg.o
@@@ -656,7 -632,6 +667,7 @@@ BUILTIN_OBJS += builtin-read-tree.
  BUILTIN_OBJS += builtin-receive-pack.o
  BUILTIN_OBJS += builtin-reflog.o
  BUILTIN_OBJS += builtin-remote.o
 +BUILTIN_OBJS += builtin-replace.o
  BUILTIN_OBJS += builtin-rerere.o
  BUILTIN_OBJS += builtin-reset.o
  BUILTIN_OBJS += builtin-rev-list.o
@@@ -674,7 -649,6 +685,7 @@@ BUILTIN_OBJS += builtin-tar-tree.
  BUILTIN_OBJS += builtin-unpack-objects.o
  BUILTIN_OBJS += builtin-update-index.o
  BUILTIN_OBJS += builtin-update-ref.o
 +BUILTIN_OBJS += builtin-update-server-info.o
  BUILTIN_OBJS += builtin-upload-archive.o
  BUILTIN_OBJS += builtin-verify-pack.o
  BUILTIN_OBJS += builtin-verify-tag.o
@@@ -743,7 -717,6 +754,7 @@@ ifeq ($(uname_S),SCO_SV
        TAR = gtar
  endif
  ifeq ($(uname_S),Darwin)
 +      NEEDS_CRYPTO_WITH_SSL = YesPlease
        NEEDS_SSL_WITH_CRYPTO = YesPlease
        NEEDS_LIBICONV = YesPlease
        ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2)
@@@ -767,7 -740,6 +778,7 @@@ ifeq ($(uname_S),SunOS
        NO_MKSTEMPS = YesPlease
        NO_REGEX = YesPlease
        NO_EXTERNAL_GREP = YesPlease
 +      THREADED_DELTA_SEARCH = YesPlease
        ifeq ($(uname_R),5.7)
                NEEDS_RESOLV = YesPlease
                NO_IPV6 = YesPlease
                NO_C99_FORMAT = YesPlease
                NO_STRTOUMAX = YesPlease
        endif
 -      ifdef NO_IPV6
 -              NEEDS_RESOLV = YesPlease
 -      endif
        INSTALL = /usr/ucb/install
        TAR = gtar
        BASIC_CFLAGS += -D__EXTENSIONS__ -D__sun__ -DHAVE_ALLOCA_H
@@@ -805,15 -780,12 +816,15 @@@ ifeq ($(uname_O),Cygwin
        NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes
        NO_TRUSTABLE_FILEMODE = UnfortunatelyYes
        OLD_ICONV = UnfortunatelyYes
 +      NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
        # There are conflicting reports about this.
        # On some boxes NO_MMAP is needed, and not so elsewhere.
        # Try commenting this out if you suspect MMAP is more efficient
        NO_MMAP = YesPlease
        NO_IPV6 = YesPlease
        X = .exe
 +      COMPAT_OBJS += compat/cygwin.o
 +      UNRELIABLE_FSTAT = UnfortunatelyYes
  endif
  ifeq ($(uname_S),FreeBSD)
        NEEDS_LIBICONV = YesPlease
@@@ -877,18 -849,11 +888,18 @@@ ifeq ($(uname_S),IRIX
        NO_MEMMEM = YesPlease
        NO_MKSTEMPS = YesPlease
        NO_MKDTEMP = YesPlease
 +      # When compiled with the MIPSpro 7.4.4m compiler, and without pthreads
 +      # (i.e. NO_PTHREADS is set), and _with_ MMAP (i.e. NO_MMAP is not set),
 +      # git dies with a segmentation fault when trying to access the first
 +      # entry of a reflog.  The conservative choice is made to always set
 +      # NO_MMAP.  If you suspect that your compiler is not affected by this
 +      # issue, comment out the NO_MMAP statement.
        NO_MMAP = YesPlease
        NO_EXTERNAL_GREP = UnfortunatelyYes
        SNPRINTF_RETURNS_BOGUS = YesPlease
        SHELL_PATH = /usr/gnu/bin/bash
        NEEDS_LIBGEN = YesPlease
 +      THREADED_DELTA_SEARCH = YesPlease
  endif
  ifeq ($(uname_S),IRIX64)
        NO_SETENV=YesPlease
        NO_MEMMEM = YesPlease
        NO_MKSTEMPS = YesPlease
        NO_MKDTEMP = YesPlease
 +      # When compiled with the MIPSpro 7.4.4m compiler, and without pthreads
 +      # (i.e. NO_PTHREADS is set), and _with_ MMAP (i.e. NO_MMAP is not set),
 +      # git dies with a segmentation fault when trying to access the first
 +      # entry of a reflog.  The conservative choice is made to always set
 +      # NO_MMAP.  If you suspect that your compiler is not affected by this
 +      # issue, comment out the NO_MMAP statement.
        NO_MMAP = YesPlease
        NO_EXTERNAL_GREP = UnfortunatelyYes
        SNPRINTF_RETURNS_BOGUS = YesPlease
        SHELL_PATH=/usr/gnu/bin/bash
        NEEDS_LIBGEN = YesPlease
 +      THREADED_DELTA_SEARCH = YesPlease
  endif
  ifeq ($(uname_S),HP-UX)
        NO_IPV6=YesPlease
        NO_SYS_SELECT_H = YesPlease
        SNPRINTF_RETURNS_BOGUS = YesPlease
  endif
 -ifneq (,$(findstring CYGWIN,$(uname_S)))
 -      COMPAT_OBJS += compat/cygwin.o
 +ifeq ($(uname_S),Windows)
 +      GIT_VERSION := $(GIT_VERSION).MSVC
 +      pathsep = ;
 +      NO_PREAD = YesPlease
 +      NEEDS_CRYPTO_WITH_SSL = YesPlease
 +      NO_LIBGEN_H = YesPlease
 +      NO_SYMLINK_HEAD = YesPlease
 +      NO_IPV6 = YesPlease
 +      NO_SETENV = YesPlease
 +      NO_UNSETENV = YesPlease
 +      NO_STRCASESTR = YesPlease
 +      NO_STRLCPY = YesPlease
 +      NO_MEMMEM = YesPlease
 +      # NEEDS_LIBICONV = YesPlease
 +      NO_ICONV = YesPlease
 +      NO_C99_FORMAT = YesPlease
 +      NO_STRTOUMAX = YesPlease
 +      NO_STRTOULL = YesPlease
 +      NO_MKDTEMP = YesPlease
 +      NO_MKSTEMPS = YesPlease
 +      SNPRINTF_RETURNS_BOGUS = YesPlease
 +      NO_SVN_TESTS = YesPlease
 +      NO_PERL_MAKEMAKER = YesPlease
 +      RUNTIME_PREFIX = YesPlease
 +      NO_POSIX_ONLY_PROGRAMS = YesPlease
 +      NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
 +      NO_NSEC = YesPlease
 +      USE_WIN32_MMAP = YesPlease
 +      # USE_NED_ALLOCATOR = YesPlease
        UNRELIABLE_FSTAT = UnfortunatelyYes
 +      OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
 +      NO_REGEX = YesPlease
 +      NO_CURL = YesPlease
 +      NO_PTHREADS = YesPlease
 +      BLK_SHA1 = YesPlease
 +
 +      CC = compat/vcbuild/scripts/clink.pl
 +      AR = compat/vcbuild/scripts/lib.pl
 +      CFLAGS =
 +      BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE
 +      COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o
 +      COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -DSTRIP_EXTENSION=\".exe\"
 +      BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib
 +      EXTLIBS = advapi32.lib shell32.lib wininet.lib ws2_32.lib
 +      lib =
 +ifndef DEBUG
 +      BASIC_CFLAGS += -GL -Os -MT
 +      BASIC_LDFLAGS += -LTCG
 +      AR += -LTCG
 +else
 +      BASIC_CFLAGS += -Zi -MTd
 +endif
 +      X = .exe
  endif
  ifneq (,$(findstring MINGW,$(uname_S)))
        pathsep = ;
        NO_PREAD = YesPlease
 -      NO_OPENSSL = YesPlease
 +      NEEDS_CRYPTO_WITH_SSL = YesPlease
        NO_LIBGEN_H = YesPlease
        NO_SYMLINK_HEAD = YesPlease
 -      NO_IPV6 = YesPlease
        NO_SETENV = YesPlease
        NO_UNSETENV = YesPlease
        NO_STRCASESTR = YesPlease
        UNRELIABLE_FSTAT = UnfortunatelyYes
        OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
        NO_REGEX = YesPlease
 +      BLK_SHA1 = YesPlease
        COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
        COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o
@@@ -1025,6 -933,10 +1036,6 @@@ els
        NO_PTHREADS = YesPlease
  endif
  endif
 -ifneq (,$(findstring arm,$(uname_M)))
 -      ARM_SHA1 = YesPlease
 -      NO_MKSTEMPS = YesPlease
 -endif
  
  -include config.mak.autogen
  -include config.mak
@@@ -1078,7 -990,9 +1089,7 @@@ els
        else
                CURL_LIBCURL = -lcurl
        endif
 -      BUILTIN_OBJS += builtin-http-fetch.o
 -      EXTLIBS += $(CURL_LIBCURL)
 -      LIB_OBJS += http.o http-walker.o
 +      PROGRAMS += git-remote-curl$X git-http-fetch$X
        curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p)
        ifeq "$(curl_check)" "070908"
                ifndef NO_EXPAT
@@@ -1103,6 -1017,7 +1114,6 @@@ EXTLIBS += -l
  
  ifndef NO_POSIX_ONLY_PROGRAMS
        PROGRAMS += git-daemon$X
 -      PROGRAMS += git-imap-send$X
  endif
  ifndef NO_OPENSSL
        OPENSSL_LIBSSL = -lssl
        else
                OPENSSL_LINK =
        endif
 +      ifdef NEEDS_CRYPTO_WITH_SSL
 +              OPENSSL_LINK += -lcrypto
 +      endif
  else
        BASIC_CFLAGS += -DNO_OPENSSL
 -      MOZILLA_SHA1 = 1
 +      BLK_SHA1 = 1
        OPENSSL_LIBSSL =
  endif
  ifdef NEEDS_SSL_WITH_CRYPTO
@@@ -1266,18 -1178,23 +1277,18 @@@ ifdef NO_DEFLATE_BOUN
        BASIC_CFLAGS += -DNO_DEFLATE_BOUND
  endif
  
 +ifdef BLK_SHA1
 +      SHA1_HEADER = "block-sha1/sha1.h"
 +      LIB_OBJS += block-sha1/sha1.o
 +else
  ifdef PPC_SHA1
        SHA1_HEADER = "ppc/sha1.h"
        LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o
 -else
 -ifdef ARM_SHA1
 -      SHA1_HEADER = "arm/sha1.h"
 -      LIB_OBJS += arm/sha1.o arm/sha1_arm.o
 -else
 -ifdef MOZILLA_SHA1
 -      SHA1_HEADER = "mozilla-sha1/sha1.h"
 -      LIB_OBJS += mozilla-sha1/sha1.o
  else
        SHA1_HEADER = <openssl/sha.h>
        EXTLIBS += $(LIB_4_CRYPTO)
  endif
  endif
 -endif
  ifdef NO_PERL_MAKEMAKER
        export NO_PERL_MAKEMAKER
  endif
@@@ -1351,7 -1268,6 +1362,7 @@@ ifndef 
        QUIET_LINK     = @echo '   ' LINK $@;
        QUIET_BUILT_IN = @echo '   ' BUILTIN $@;
        QUIET_GEN      = @echo '   ' GEN $@;
 +      QUIET_LNCP     = @echo '   ' LN/CP $@;
        QUIET_SUBDIR0  = +@subdir=
        QUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
                         $(MAKE) $(PRINT_DIR) -C $$subdir
@@@ -1390,22 -1306,6 +1401,22 @@@ BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_H
        $(COMPAT_CFLAGS)
  LIB_OBJS += $(COMPAT_OBJS)
  
 +# Quote for C
 +
 +ifdef DEFAULT_EDITOR
 +DEFAULT_EDITOR_CQ = "$(subst ",\",$(subst \,\\,$(DEFAULT_EDITOR)))"
 +DEFAULT_EDITOR_CQ_SQ = $(subst ','\'',$(DEFAULT_EDITOR_CQ))
 +
 +BASIC_CFLAGS += -DDEFAULT_EDITOR='$(DEFAULT_EDITOR_CQ_SQ)'
 +endif
 +
 +ifdef DEFAULT_PAGER
 +DEFAULT_PAGER_CQ = "$(subst ",\",$(subst \,\\,$(DEFAULT_PAGER)))"
 +DEFAULT_PAGER_CQ_SQ = $(subst ','\'',$(DEFAULT_PAGER_CQ))
 +
 +BASIC_CFLAGS += -DDEFAULT_PAGER='$(DEFAULT_PAGER_CQ_SQ)'
 +endif
 +
  ALL_CFLAGS += $(BASIC_CFLAGS)
  ALL_LDFLAGS += $(BASIC_LDFLAGS)
  
@@@ -1418,7 -1318,7 +1429,7 @@@ SHELL = $(SHELL_PATH
  
  all:: shell_compatibility_test $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
  ifneq (,$X)
 -      $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test '$p' -ef '$p$X' || $(RM) '$p';)
 +      $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';)
  endif
  
  all::
@@@ -1442,7 -1342,7 +1453,7 @@@ strip: $(PROGRAMS) git$
  git.o: git.c common-cmds.h GIT-CFLAGS
        $(QUIET_CC)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
                '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
 -              $(ALL_CFLAGS) -c $(filter %.c,$^)
 +              $(ALL_CFLAGS) -o $@ -c $(filter %.c,$^)
  
  git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
@@@ -1498,8 -1398,13 +1509,13 @@@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % 
        chmod +x $@+ && \
        mv $@+ $@
  
+ 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
+ endif
        $(QUIET_GEN)$(RM) $@ $@+ && \
        sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
            -e 's|++GIT_VERSION++|$(GIT_VERSION)|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' \
            $< >$@+ && \
        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' \
            -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 $@+ && \
@@@ -1547,6 -1455,11 +1566,11 @@@ $(patsubst %.perl,%,$(SCRIPT_PERL)) git
        mv $@+ $@
  endif # NO_PERL
  
+ ifdef JSMIN
+ gitweb/gitweb.min.js: gitweb/gitweb.js
+       $(QUIET_GEN)$(JSMIN) <$< >$@
+ endif # JSMIN
  configure: configure.ac
        $(QUIET_GEN)$(RM) $@ $<+ && \
        sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
@@@ -1595,21 -1508,12 +1619,21 @@@ git-imap-send$X: imap-send.o $(GITLIBS
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL)
  
 -http.o http-walker.o http-push.o transport.o: http.h
 +http.o http-walker.o http-push.o: http.h
 +
 +http.o http-walker.o: $(LIB_H)
  
 +git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o $(GITLIBS)
 +      $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
 +              $(LIBS) $(CURL_LIBCURL)
  git-http-push$X: revision.o http.o http-push.o $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
  
 +git-remote-curl$X: remote-curl.o http.o http-walker.o $(GITLIBS)
 +      $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
 +              $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 +
  $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)
  $(patsubst git-%$X,%.o,$(PROGRAMS)) git.o: $(LIB_H) $(wildcard */*.h)
  builtin-revert.o wt-status.o: wt-status.h
@@@ -1669,7 -1573,6 +1693,7 @@@ GIT-CFLAGS: .FORCE-GIT-CFLAG
  # and the first level quoting from the shell that runs "echo".
  GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
        @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
 +      @echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@
        @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
        @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
        @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
@@@ -1843,10 -1746,7 +1867,10 @@@ dist: git.spec git-archive$(X) configur
        gzip -f -9 $(GIT_TARNAME).tar
  
  rpm: dist
 -      $(RPMBUILD) -ta $(GIT_TARNAME).tar.gz
 +      $(RPMBUILD) \
 +              --define "_source_filedigest_algorithm md5" \
 +              --define "_binary_filedigest_algorithm md5" \
 +              -ta $(GIT_TARNAME).tar.gz
  
  htmldocs = git-htmldocs-$(GIT_VERSION)
  manpages = git-manpages-$(GIT_VERSION)
@@@ -1874,7 -1774,7 +1898,7 @@@ distclean: clea
        $(RM) configure
  
  clean:
 -      $(RM) *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \
 +      $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \
                $(LIB_FILE) $(XDIFF_LIB)
        $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X
        $(RM) $(TEST_PROGRAMS)
diff --combined git-instaweb.sh
index 341930ca9d74867cf334952fc47bb3afe95552b7,6f381c88da6e9102801c3fe42c5aefcf79c47c6b..b8e6456208d8cccb669eb125cfb2ac677443db82
@@@ -41,7 -41,7 +41,7 @@@ resolve_full_httpd () 
        case "$httpd" in
        *apache2*|*lighttpd*)
                # ensure that the apache2/lighttpd command ends with "-f"
 -              if ! echo "$httpd" | grep -- '-f *$' >/dev/null 2>&1
 +              if ! echo "$httpd" | sane_grep -- '-f *$' >/dev/null 2>&1
                then
                        httpd="$httpd -f"
                fi
  }
  
  start_httpd () {
 +      if test -f "$fqgitdir/pid"; then
 +              say "Instance already running. Restarting..."
 +              stop_httpd
 +      fi
 +
        # here $httpd should have a meaningful value
        resolve_full_httpd
  
        # don't quote $full_httpd, there can be arguments to it (-f)
 -      $full_httpd "$fqgitdir/gitweb/httpd.conf"
 -      if test $? != 0; then
 -              echo "Could not execute http daemon $httpd."
 -              exit 1
 -      fi
 +      case "$httpd" in
 +      *mongoose*)
 +              #The mongoose server doesn't have a daemon mode so we'll have to fork it
 +              $full_httpd "$fqgitdir/gitweb/httpd.conf" &
 +              #Save the pid before doing anything else (we'll print it later)
 +              pid=$!
 +
 +              if test $? != 0; then
 +                      echo "Could not execute http daemon $httpd."
 +                      exit 1
 +              fi
 +
 +              cat > "$fqgitdir/pid" <<EOF
 +$pid
 +EOF
 +              ;;
 +      *)
 +              $full_httpd "$fqgitdir/gitweb/httpd.conf"
 +              if test $? != 0; then
 +                      echo "Could not execute http daemon $httpd."
 +                      exit 1
 +              fi
 +              ;;
 +      esac
  }
  
  stop_httpd () {
@@@ -280,7 -256,7 +280,7 @@@ apache2_conf () 
        mkdir -p "$GIT_DIR/gitweb/logs"
        bind=
        test x"$local" = xtrue && bind='127.0.0.1:'
 -      echo 'text/css css' > $fqgitdir/mime.types
 +      echo 'text/css css' > "$fqgitdir/mime.types"
        cat > "$conf" <<EOF
  ServerName "git-instaweb"
  ServerRoot "$fqgitdir/gitweb"
@@@ -296,14 -272,14 +296,14 @@@ EO
                fi
        done
        cat >> "$conf" <<EOF
 -TypesConfig $fqgitdir/mime.types
 +TypesConfig "$fqgitdir/mime.types"
  DirectoryIndex gitweb.cgi
  EOF
  
        # check to see if Dennis Stosberg's mod_perl compatibility patch
        # (<20060621130708.Gcbc6e5c@leonov.stosberg.net>) has been applied
 -      if test -f "$module_path/mod_perl.so" && grep '^our $gitbin' \
 -                              "$GIT_DIR/gitweb/gitweb.cgi" >/dev/null
 +      if test -f "$module_path/mod_perl.so" &&
 +         sane_grep 'MOD_PERL' "$GIT_DIR/gitweb/gitweb.cgi" >/dev/null
        then
                # favor mod_perl if available
                cat >> "$conf" <<EOF
@@@ -321,22 -297,8 +321,22 @@@ EO
                # plain-old CGI
                resolve_full_httpd
                list_mods=$(echo "$full_httpd" | sed "s/-f$/-l/")
 -              $list_mods | grep 'mod_cgi\.c' >/dev/null 2>&1 || \
 -              echo "LoadModule cgi_module $module_path/mod_cgi.so" >> "$conf"
 +              $list_mods | sane_grep 'mod_cgi\.c' >/dev/null 2>&1 || \
 +              if test -f "$module_path/mod_cgi.so"
 +              then
 +                      echo "LoadModule cgi_module $module_path/mod_cgi.so" >> "$conf"
 +              else
 +                      $list_mods | grep 'mod_cgid\.c' >/dev/null 2>&1 || \
 +                      if test -f "$module_path/mod_cgid.so"
 +                      then
 +                              echo "LoadModule cgid_module $module_path/mod_cgid.so" \
 +                                      >> "$conf"
 +                      else
 +                              echo "You have no CGI support!"
 +                              exit 2
 +                      fi
 +                      echo "ScriptSock logs/gitweb.sock" >> "$conf"
 +              fi
                cat >> "$conf" <<EOF
  AddHandler cgi-script .cgi
  <Location /gitweb.cgi>
@@@ -346,31 -308,6 +346,31 @@@ EO
        fi
  }
  
 +mongoose_conf() {
 +      cat > "$conf" <<EOF
 +# Mongoose web server configuration file.
 +# Lines starting with '#' and empty lines are ignored.
 +# For detailed description of every option, visit
 +# http://code.google.com/p/mongoose/wiki/MongooseManual
 +
 +root          $fqgitdir/gitweb
 +ports         $port
 +index_files   gitweb.cgi
 +#ssl_cert     $fqgitdir/gitweb/ssl_cert.pem
 +error_log     $fqgitdir/gitweb/error.log
 +access_log    $fqgitdir/gitweb/access.log
 +
 +#cgi setup
 +cgi_env               PATH=/usr/local/bin:/usr/bin:/bin,GIT_DIR=$GIT_DIR,GIT_EXEC_PATH=$GIT_EXEC_PATH
 +cgi_interp    $PERL
 +cgi_ext               cgi,pl
 +
 +# mimetype mapping
 +mime_types    .gz=application/x-gzip,.tar.gz=application/x-tgz,.tgz=application/x-tgz,.tar=application/x-tar,.zip=application/zip,.gif=image/gif,.jpg=image/jpeg,.jpeg=image/jpeg,.png=image/png,.css=text/css,.html=text/html,.htm=text/html,.js=text/javascript,.c=text/plain,.cpp=text/plain,.log=text/plain,.conf=text/plain,.text=text/plain,.txt=text/plain,.dtd=text/xml,.bz2=application/x-bzip,.tbz=application/x-bzip-compressed-tar,.tar.bz2=application/x-bzip-compressed-tar
 +EOF
 +}
 +
 +
  script='
  s#^(my|our) \$projectroot =.*#$1 \$projectroot = "'$(dirname "$fqgitdir")'";#;
  s#(my|our) \$gitbin =.*#$1 \$gitbin = "'$GIT_EXEC_PATH'";#;
@@@ -394,8 -331,15 +394,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*)
  webrick)
        webrick_conf
        ;;
 +*mongoose*)
 +      mongoose_conf
 +      ;;
  *)
        echo "Unknown httpd specified: $httpd"
        exit 1
diff --combined gitweb/README
index 66c6a9391d3494ae8928e88501d8fa0d36dffa42,cbcd993ecb81a84a7c5e47a8489ca93744fe3237..b69b0e504283af3ae48b3975e0c0ba505f660ee5
@@@ -92,6 -92,10 +92,10 @@@ You can specify the following configura
     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
@@@ -165,12 -169,6 +169,12 @@@ not include variables usually directly 
     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
 +   <base href="$base_url>.  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
     "breadcrumbs").  By default set to absolute URI of a page ($my_uri).
diff --combined gitweb/gitweb.css
index cb3f0baf05e8a48484132bb8936b4f46f22493ed,c101e4af75a56220cd4b404e9d13b658c1beef0c..50067f2e0dea2c1e17a41e9a5e8b2a8f6240c13d
@@@ -32,10 -32,6 +32,10 @@@ img.avatar 
        vertical-align: middle;
  }
  
 +a.list img.avatar {
 +      border-style: none;
 +}
 +
  div.page_header {
        height: 25px;
        padding: 8px;
@@@ -79,6 -75,13 +79,13 @@@ div.page_footer_text 
        font-style: italic;
  }
  
+ div#generating_info {
+       margin: 4px;
+       font-size: smaller;
+       text-align: center;
+       color: #505050;
+ }
  div.page_body {
        padding: 8px;
        font-family: monospace;
@@@ -254,6 -257,11 +261,11 @@@ 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%;
@@@ -343,14 -351,19 +355,25 @@@ td.pre, div.pre, div.diff 
  
  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 {
diff --combined gitweb/gitweb.perl
index f94536c680c05932595f0766fde1d87b5a8f2162,49402dc2566abe0bf1ffd2ba3744a486eb99ebd0..7e477af9567cff322d18fc9a1fbfe507732418dc
@@@ -18,6 -18,12 +18,12 @@@ use File::Find qw()
  use File::Basename qw(basename);
  binmode STDOUT, ':utf8';
  
+ our $t0;
+ if (eval { require Time::HiRes; 1; }) {
+       $t0 = [Time::HiRes::gettimeofday()];
+ }
+ our $number_of_git_cmds = 0;
  BEGIN {
        CGI->compile() if $ENV{'MOD_PERL'};
  }
@@@ -90,6 -96,8 +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/";
@@@ -160,8 -168,7 +168,8 @@@ our %known_snapshot_formats = 
        #       'suffix' => filename suffix,
        #       'format' => --format for git-archive,
        #       'compressor' => [compressor command and arguments]
 -      #                       (array reference, optional)}
 +      #                       (array reference, optional)
 +      #       'disabled' => boolean (optional)}
        #
        'tgz' => {
                'display' => 'tar.gz',
                'format' => 'tar',
                'compressor' => ['bzip2']},
  
 +      'txz' => {
 +              'display' => 'tar.xz',
 +              'type' => 'application/x-xz',
 +              'suffix' => '.tar.xz',
 +              'format' => 'tar',
 +              'compressor' => ['xz'],
 +              'disabled' => 1},
 +
        'zip' => {
                'display' => 'zip',
                'type' => 'application/x-zip',
  our %known_snapshot_format_aliases = (
        'gzip'  => 'tgz',
        'bzip2' => 'tbz2',
 +      'xz'    => 'txz',
  
        # backward compatibility: legacy gitweb config support
        'x-gzip' => undef, 'gz' => undef,
@@@ -297,19 -295,6 +305,19 @@@ our %feature = 
                'override' => 0,
                'default' => [1]},
  
 +      # Enable showing size of blobs in a 'tree' view, in a separate
 +      # column, similar to what 'ls -l' does.  This cost a bit of IO.
 +
 +      # To disable system wide have in $GITWEB_CONFIG
 +      # $feature{'show-sizes'}{'default'} = [0];
 +      # To have project specific config enable override in $GITWEB_CONFIG
 +      # $feature{'show-sizes'}{'override'} = 1;
 +      # and in project config gitweb.showsizes = 0|1;
 +      'show-sizes' => {
 +              'sub' => sub { feature_bool('showsizes', @_) },
 +              'override' => 0,
 +              'default' => [1]},
 +
        # Make gitweb use an alternative format of the URLs which can be
        # more readable and natural-looking: project name is embedded
        # directly in the path and the query string contains other
                'sub' => \&feature_avatar,
                'override' => 0,
                'default' => ['']},
+       # Enable displaying how much time and how many git commands
+       # it took to generate and display page.  Disabled by default.
+       # Project specific override is not supported.
+       'timed' => {
+               'override' => 0,
+               'default' => [0]},
+       # Enable turning some links into links to actions which require
+       # JavaScript to run (like 'blame_incremental').  Not enabled by
+       # default.  Project specific override is currently not supported.
+       'javascript-actions' => {
+               'override' => 0,
+               'default' => [0]},
  );
  
  sub gitweb_get_feature {
                @{$feature{$name}{'default'}});
        if (!$override) { return @defaults; }
        if (!defined $sub) {
 -              warn "feature $name is not overrideable";
 +              warn "feature $name is not overridable";
                return @defaults;
        }
        return $sub->(@defaults);
@@@ -517,8 -516,7 +539,8 @@@ sub filter_snapshot_fmts 
                exists $known_snapshot_format_aliases{$_} ?
                       $known_snapshot_format_aliases{$_} : $_} @fmts;
        @fmts = grep {
 -              exists $known_snapshot_formats{$_} } @fmts;
 +              exists $known_snapshot_formats{$_} &&
 +              !$known_snapshot_formats{$_}{'disabled'}} @fmts;
  }
  
  our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
@@@ -531,6 -529,7 +553,7 @@@ if (-e $GITWEB_CONFIG) 
  
  # version of the core git binary
  our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
+ $number_of_git_cmds++;
  
  $projects_list ||= $projectroot;
  
@@@ -568,12 -567,16 +591,16 @@@ our @cgi_param_mapping = 
        snapshot_format => "sf",
        extra_options => "opt",
        search_use_regexp => "sr",
+       # this must be last entry (for manipulation from JavaScript)
+       javascript => "js"
  );
  our %cgi_param_mapping = @cgi_param_mapping;
  
  # we will also need to know the possible actions, for validation
  our %actions = (
        "blame" => \&git_blame,
+       "blame_incremental" => \&git_blame_incremental,
+       "blame_data" => \&git_blame_data,
        "blobdiff" => \&git_blobdiff,
        "blobdiff_plain" => \&git_blobdiff_plain,
        "blob" => \&git_blob,
@@@ -964,13 -967,10 +991,13 @@@ sub href 
                        if (defined $params{'hash_parent_base'}) {
                                $href .= esc_url($params{'hash_parent_base'});
                                # skip the file_parent if it's the same as the file_name
 -                              delete $params{'file_parent'} if $params{'file_parent'} eq $params{'file_name'};
 -                              if (defined $params{'file_parent'} && $params{'file_parent'} !~ /\.\./) {
 -                                      $href .= ":/".esc_url($params{'file_parent'});
 -                                      delete $params{'file_parent'};
 +                              if (defined $params{'file_parent'}) {
 +                                      if (defined $params{'file_name'} && $params{'file_parent'} eq $params{'file_name'}) {
 +                                              delete $params{'file_parent'};
 +                                      } elsif ($params{'file_parent'} !~ /\.\./) {
 +                                              $href .= ":/".esc_url($params{'file_parent'});
 +                                              delete $params{'file_parent'};
 +                                      }
                                }
                                $href .= "..";
                                delete $params{'hash_parent'};
@@@ -1096,7 -1096,8 +1123,7 @@@ sub to_utf8 
  # correct, but quoted slashes look too horrible in bookmarks
  sub esc_param {
        my $str = shift;
 -      $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;
  }
@@@ -1536,10 -1537,10 +1563,10 @@@ sub format_subject_html 
                $long =~ s/[[:cntrl:]]/?/g;
                return $cgi->a({-href => $href, -class => "list subject",
                                -title => to_utf8($long)},
 -                     esc_html($short) . $extra);
 +                     esc_html($short)) . $extra;
        } else {
                return $cgi->a({-href => $href, -class => "list subject"},
 -                     esc_html($long)  . $extra);
 +                     esc_html($long)) . $extra;
        }
  }
  
@@@ -1607,29 -1608,6 +1634,29 @@@ sub git_get_avatar 
        }
  }
  
 +sub format_search_author {
 +      my ($author, $searchtype, $displaytext) = @_;
 +      my $have_search = gitweb_check_feature('search');
 +
 +      if ($have_search) {
 +              my $performed = "";
 +              if ($searchtype eq 'author') {
 +                      $performed = "authored";
 +              } elsif ($searchtype eq 'committer') {
 +                      $performed = "committed";
 +              }
 +
 +              return $cgi->a({-href => href(action=>"search", hash=>$hash,
 +                              searchtext=>$author,
 +                              searchtype=>$searchtype), class=>"list",
 +                              title=>"Search for commits $performed by $author"},
 +                              $displaytext);
 +
 +      } else {
 +              return $displaytext;
 +      }
 +}
 +
  # format the author name of the given commit with the given tag
  # the author name is chopped and escaped according to the other
  # optional parameters (see chop_str).
@@@ -1638,10 -1616,8 +1665,10 @@@ sub format_author_html 
        my $co = shift;
        my $author = chop_and_escape_str($co->{'author_name'}, @_);
        return "<$tag class=\"author\">" .
 -             git_get_avatar($co->{'author_email'}, -pad_after => 1) .
 -             $author . "</$tag>";
 +             format_search_author($co->{'author_name'}, "author",
 +                     git_get_avatar($co->{'author_email'}, -pad_after => 1) .
 +                     $author) .
 +             "</$tag>";
  }
  
  # format git diff header line, i.e. "diff --(git|combined|cc) ..."
@@@ -2006,6 -1982,7 +2033,7 @@@ sub get_feed_info 
  
  # returns path to the core git executable and the --git-dir parameter as list
  sub git_cmd {
+       $number_of_git_cmds++;
        return $GIT, '--git-dir='.$git_dir;
  }
  
@@@ -2020,27 -1997,16 +2048,27 @@@ sub quote_command 
  
  # get HEAD ref of given project as hash
  sub git_get_head_hash {
 -      my $project = shift;
 +      return git_get_full_hash(shift, 'HEAD');
 +}
 +
 +sub git_get_full_hash {
 +      return git_get_hash(@_);
 +}
 +
 +sub git_get_short_hash {
 +      return git_get_hash(@_, '--short=7');
 +}
 +
 +sub git_get_hash {
 +      my ($project, $hash, @options) = @_;
        my $o_git_dir = $git_dir;
        my $retval = undef;
        $git_dir = "$projectroot/$project";
 -      if (open my $fd, "-|", git_cmd(), "rev-parse", "--verify", "HEAD") {
 -              my $head = <$fd>;
 +      if (open my $fd, '-|', git_cmd(), 'rev-parse',
 +          '--verify', '-q', @options, $hash) {
 +              $retval = <$fd>;
 +              chomp $retval if defined $retval;
                close $fd;
 -              if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) {
 -                      $retval = $1;
 -              }
        }
        if (defined $o_git_dir) {
                $git_dir = $o_git_dir;
@@@ -2632,7 -2598,7 +2660,7 @@@ sub parse_commit_text 
                } elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) {
                        push @parents, $1;
                } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
 -                      $co{'author'} = $1;
 +                      $co{'author'} = to_utf8($1);
                        $co{'author_epoch'} = $2;
                        $co{'author_tz'} = $3;
                        if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
                                $co{'author_name'} = $co{'author'};
                        }
                } elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
 -                      $co{'committer'} = $1;
 +                      $co{'committer'} = to_utf8($1);
                        $co{'committer_epoch'} = $2;
                        $co{'committer_tz'} = $3;
 -                      $co{'committer_name'} = $co{'committer'};
                        if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
                                $co{'committer_name'}  = $1;
                                $co{'committer_email'} = $2;
@@@ -2812,31 -2779,16 +2840,31 @@@ sub parse_ls_tree_line 
        my %opts = @_;
        my %res;
  
 -      #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa  panic.c'
 -      $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
 +      if ($opts{'-l'}) {
 +              #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa   16717  panic.c'
 +              $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40}) +(-|[0-9]+)\t(.+)$/s;
  
 -      $res{'mode'} = $1;
 -      $res{'type'} = $2;
 -      $res{'hash'} = $3;
 -      if ($opts{'-z'}) {
 -              $res{'name'} = $4;
 +              $res{'mode'} = $1;
 +              $res{'type'} = $2;
 +              $res{'hash'} = $3;
 +              $res{'size'} = $4;
 +              if ($opts{'-z'}) {
 +                      $res{'name'} = $5;
 +              } else {
 +                      $res{'name'} = unquote($5);
 +              }
        } else {
 -              $res{'name'} = unquote($4);
 +              #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa  panic.c'
 +              $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
 +
 +              $res{'mode'} = $1;
 +              $res{'type'} = $2;
 +              $res{'hash'} = $3;
 +              if ($opts{'-z'}) {
 +                      $res{'name'} = $4;
 +              } else {
 +                      $res{'name'} = unquote($4);
 +              }
        }
  
        return wantarray ? %res : \%res;
@@@ -3281,10 -3233,36 +3309,36 @@@ sub git_footer_html 
        }
        print "</div>\n"; # class="page_footer"
  
+       if (defined $t0 && gitweb_check_feature('timed')) {
+               print "<div id=\"generating_info\">\n";
+               print 'This page took '.
+                     '<span id="generating_time" class="time_span">'.
+                     Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
+                     ' seconds </span>'.
+                     ' and '.
+                     '<span id="generating_cmd">'.
+                     $number_of_git_cmds.
+                     '</span> git commands '.
+                     " to generate.\n";
+               print "</div>\n"; # class="page_footer"
+       }
        if (-f $site_footer) {
                insert_file($site_footer);
        }
  
+       print qq!<script type="text/javascript" src="$javascript"></script>\n!;
+       if ($action eq 'blame_incremental') {
+               print qq!<script type="text/javascript">\n!.
+                     qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
+                     qq!           "!. href() .qq!");\n!.
+                     qq!</script>\n!;
+       } elsif (gitweb_check_feature('javascript-actions')) {
+               print qq!<script type="text/javascript">\n!.
+                     qq!window.onload = fixLinks;\n!.
+                     qq!</script>\n!;
+       }
        print "</body>\n" .
              "</html>";
  }
@@@ -3374,18 -3352,22 +3428,18 @@@ sub git_print_page_nav 
  }
  
  sub format_paging_nav {
 -      my ($action, $hash, $head, $page, $has_next_link) = @_;
 +      my ($action, $page, $has_next_link) = @_;
        my $paging_nav;
  
  
 -      if ($hash ne $head || $page) {
 -              $paging_nav .= $cgi->a({-href => href(action=>$action)}, "HEAD");
 -      } else {
 -              $paging_nav .= "HEAD";
 -      }
 -
        if ($page > 0) {
 -              $paging_nav .= " &sdot; " .
 +              $paging_nav .=
 +                      $cgi->a({-href => href(-replay=>1, page=>undef)}, "first") .
 +                      " &sdot; " .
                        $cgi->a({-href => href(-replay=>1, page=>$page-1),
                                 -accesskey => "p", -title => "Alt-p"}, "prev");
        } else {
 -              $paging_nav .= " &sdot; prev";
 +              $paging_nav .= "first &sdot; prev";
        }
  
        if ($has_next_link) {
@@@ -3432,11 -3414,10 +3486,11 @@@ sub git_print_authorship 
        my $co = shift;
        my %opts = @_;
        my $tag = $opts{-tag} || 'div';
 +      my $author = $co->{'author_name'};
  
        my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
        print "<$tag class=\"author_date\">" .
 -            esc_html($co->{'author_name'}) .
 +            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)
@@@ -3455,12 -3436,8 +3509,12 @@@ sub git_print_authorship_rows 
        @people = ('author', 'committer') unless @people;
        foreach my $who (@people) {
                my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"});
 -              print "<tr><td>$who</td><td>" . esc_html($co->{$who}) . "</td>" .
 -                    "<td rowspan=\"2\">" .
 +              print "<tr><td>$who</td><td>" .
 +                    format_search_author($co->{"${who}_name"}, $who,
 +                             esc_html($co->{"${who}_name"})) . " " .
 +                    format_search_author($co->{"${who}_email"}, $who,
 +                             esc_html("<" . $co->{"${who}_email"} . ">")) .
 +                    "</td><td rowspan=\"2\">" .
                      git_get_avatar($co->{"${who}_email"}, -size => 'double') .
                      "</td></tr>\n" .
                      "<tr>" .
@@@ -3628,9 -3605,6 +3682,9 @@@ sub git_print_tree_entry 
        # and link is the action links of the entry.
  
        print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n";
 +      if (exists $t->{'size'}) {
 +              print "<td class=\"size\">$t->{'size'}</td>\n";
 +      }
        if ($t->{'type'} eq "blob") {
                print "<td class=\"list\">" .
                        $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
        } elsif ($t->{'type'} eq "tree") {
                print "<td class=\"list\">";
                print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
 -                                           file_name=>"$basedir$t->{'name'}", %base_key)},
 +                                           file_name=>"$basedir$t->{'name'}",
 +                                           %base_key)},
                              esc_path($t->{'name'}));
                print "</td>\n";
                print "<td class=\"link\">";
                print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
 -                                           file_name=>"$basedir$t->{'name'}", %base_key)},
 +                                           file_name=>"$basedir$t->{'name'}",
 +                                           %base_key)},
                              "tree");
                if (defined $hash_base) {
                        print " | " .
@@@ -4368,46 -4340,6 +4422,46 @@@ sub git_project_list_body 
        print "</table>\n";
  }
  
 +sub git_log_body {
 +      # uses global variable $project
 +      my ($commitlist, $from, $to, $refs, $extra) = @_;
 +
 +      $from = 0 unless defined $from;
 +      $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
 +
 +      for (my $i = 0; $i <= $to; $i++) {
 +              my %co = %{$commitlist->[$i]};
 +              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',
 +                             "<span class=\"age\">$co{'age_string'}</span>" .
 +                             esc_html($co{'title'}) . $ref,
 +                             $commit);
 +              print "<div class=\"title_text\">\n" .
 +                    "<div class=\"log_link\">\n" .
 +                    $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") .
 +                    " | " .
 +                    $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") .
 +                    " | " .
 +                    $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
 +                    "<br/>\n" .
 +                    "</div>\n";
 +                    git_print_authorship(\%co, -tag => 'span');
 +                    print "<br/>\n</div>\n";
 +
 +              print "<div class=\"log_body\">\n";
 +              git_print_log($co{'comment'}, -final_empty_line=> 1);
 +              print "</div>\n";
 +      }
 +      if ($extra) {
 +              print "<div class=\"page_nav\">\n";
 +              print "$extra\n";
 +              print "</div>\n";
 +      }
 +}
 +
  sub git_shortlog_body {
        # uses global variable $project
        my ($commitlist, $from, $to, $refs, $extra) = @_;
  
  sub git_history_body {
        # Warning: assumes constant type (blob or tree) during history
 -      my ($commitlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_;
 +      my ($commitlist, $from, $to, $refs, $extra,
 +          $file_name, $file_hash, $ftype) = @_;
  
        $from = 0 unless defined $from;
        $to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist});
                      $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
  
                if ($ftype eq 'blob') {
 -                      my $blob_current = git_get_hash_by_path($hash_base, $file_name);
 +                      my $blob_current = $file_hash;
                        my $blob_parent  = git_get_hash_by_path($commit, $file_name);
                        if (defined $blob_current && defined $blob_parent &&
                                        $blob_current ne $blob_parent) {
@@@ -4881,7 -4812,13 +4935,13 @@@ sub git_tag 
        git_footer_html();
  }
  
- sub git_blame {
+ sub git_blame_common {
+       my $format = shift || 'porcelain';
+       if ($format eq 'porcelain' && $cgi->param('js')) {
+               $format = 'incremental';
+               $action = 'blame_incremental'; # for page title etc
+       }
        # permissions
        gitweb_check_feature('blame')
                or die_error(403, "Blame view not allowed");
                }
        }
  
-       # 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();
        my $formats_nav =
                $cgi->a({-href => href(action=>"blob", -replay=>1)},
                        "blob") .
+               " | ";
+       if ($format eq 'incremental') {
+               $formats_nav .=
+                       $cgi->a({-href => href(action=>"blame", javascript=>0, -replay=>1)},
+                               "blame") . " (non-incremental)";
+       } else {
+               $formats_nav .=
+                       $cgi->a({-href => href(action=>"blame_incremental", -replay=>1)},
+                               "blame") . " (incremental)";
+       }
+       $formats_nav .=
                " | " .
                $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 "<noscript>\n<div class=\"error\"><center><b>\n".
+                     "This page requires JavaScript to run.\n Use ".
+                     $cgi->a({-href => href(action=>'blame',javascript=>0,-replay=>1)},
+                             'this page').
+                     " instead.\n".
+                     "</b></center></div>\n</noscript>\n";
+               print qq!<div id="progress_bar" style="width: 100%; background-color: yellow"></div>\n!;
+       }
+       print qq!<div class="page_body">\n!;
+       print qq!<div id="progress_info">... / ...</div>\n!
+               if ($format eq 'incremental');
+       print qq!<table id="blame_table" class="blame" width="100%">\n!.
+             #qq!<col width="5.5em" /><col width="2.5em" /><col width="*" />\n!.
+             qq!<thead>\n!.
+             qq!<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n!.
+             qq!</thead>\n!.
+             qq!<tbody>\n!;
        my @rev_color = qw(light dark);
        my $num_colors = scalar(@rev_color);
        my $current_color = 0;
-       my %metainfo = ();
  
-       print <<HTML;
- <div class="page_body">
- <table class="blame">
- <tr><th>Commit</th><th>Line</th><th>Data</th></tr>
- HTML
-  LINE:
-       while (my $line = <$fd>) {
-               chomp $line;
-               # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
-               # no <lines in group> 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 ($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!<tr id="l$linenr" class="$color_class">!.
+                             qq!<td class="sha1"><a href=""> </a></td>!.
+                             qq!<td class="linenr">!.
+                             qq!<a class="linenr" href="">$linenr</a></td>!;
+                       print qq!<td class="pre">! . esc_html($line) . "</td>\n";
+                       print qq!</tr>\n!;
                }
-               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};
+       } else { # porcelain, i.e. ordinary blame
+               my %metainfo = (); # saves information about commits
+               # blame data
+       LINE:
+               while (my $line = <$fd>) {
+                       chomp $line;
+                       # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
+                       # no <lines in group> 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 "<tr id=\"l$lineno\" class=\"$tr_class\">\n";
-               if ($group_size) {
-                       print "<td class=\"sha1\"";
-                       print " title=\"". esc_html($author) . ", $date\"";
-                       print " rowspan=\"$group_size\"" if ($group_size > 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 "<br />" .
-                                             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 "<tr id=\"l$lineno\" class=\"$tr_class\">\n";
+                       if ($group_size) {
+                               print "<td class=\"sha1\"";
+                               print " title=\"". esc_html($author) . ", $date\"";
+                               print " rowspan=\"$group_size\"" if ($group_size > 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 "<br />" .
+                                                     esc_html(join('', @author_initials));
+                                               #           or join('.', ...)
+                                       }
                                }
+                               print "</td>\n";
                        }
-                       print "</td>\n";
-               }
-               # 'previous' <sha1 of parent commit> <filename at commit>
-               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 "<td class=\"linenr\">";
-               print $cgi->a({ -href => "$blamed#l$orig_lineno",
-                               -class => "linenr" },
-                             esc_html($lineno));
-               print "</td>";
-               print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
-               print "</tr>\n";
+                       # 'previous' <sha1 of parent commit> <filename at commit>
+                       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 "<td class=\"linenr\">";
+                       print $cgi->a({ -href => "$blamed#l$orig_lineno",
+                                       -class => "linenr" },
+                                     esc_html($lineno));
+                       print "</td>";
+                       print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
+                       print "</tr>\n";
+               } # end while
        }
-       print "</table>\n";
-       print "</div>";
+       # footer
+       print "</tbody>\n".
+             "</table>\n"; # class="blame"
+       print "</div>\n";   # class="blame_body"
        close $fd
                or print "Reading blob failed\n";
  
-       # page footer
        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();
@@@ -5175,8 -5209,7 +5332,8 @@@ sub git_blob 
                        chomp $line;
                        $nr++;
                        $line = untabify($line);
 -                      printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
 +                      printf "<div class=\"pre\"><a id=\"l%i\" href=\"" . href(-replay => 1)
 +                              . "#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
                               $nr, $nr, $nr, esc_html($line, -nbsp=>1);
                }
        }
@@@ -5199,14 -5232,10 +5356,14 @@@ sub git_tree 
        }
        die_error(404, "No such tree") unless defined($hash);
  
 +      my $show_sizes = gitweb_check_feature('show-sizes');
 +      my $have_blame = gitweb_check_feature('blame');
 +
        my @entries = ();
        {
                local $/ = "\0";
 -              open my $fd, "-|", git_cmd(), "ls-tree", '-z', $hash
 +              open my $fd, "-|", git_cmd(), "ls-tree", '-z',
 +                      ($show_sizes ? '-l' : ()), @extra_options, $hash
                        or die_error(500, "Open git-ls-tree failed");
                @entries = map { chomp; $_ } <$fd>;
                close $fd
        my $ref = format_ref_marker($refs, $hash_base);
        git_header_html();
        my $basedir = '';
 -      my $have_blame = gitweb_check_feature('blame');
        if (defined $hash_base && (my %co = parse_commit($hash_base))) {
                my @views_nav = ();
                if (defined $file_name) {
                        # FIXME: Should be available when we have no hash base as well.
                        push @views_nav, $snapshot_links;
                }
 -              git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav));
 +              git_print_page_nav('tree','', $hash_base, undef, undef,
 +                                 join(' | ', @views_nav));
                git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
        } else {
                undef $hash_base;
                undef $up unless $up;
                # based on git_print_tree_entry
                print '<td class="mode">' . mode_str('040000') . "</td>\n";
 +              print '<td class="size">&nbsp;</td>'."\n" if $show_sizes;
                print '<td class="list">';
 -              print $cgi->a({-href => href(action=>"tree", hash_base=>$hash_base,
 +              print $cgi->a({-href => href(action=>"tree",
 +                                           hash_base=>$hash_base,
                                             file_name=>$up)},
                              "..");
                print "</td>\n";
                print "</tr>\n";
        }
        foreach my $line (@entries) {
 -              my %t = parse_ls_tree_line($line, -z => 1);
 +              my %t = parse_ls_tree_line($line, -z => 1, -l => $show_sizes);
  
                if ($alternate) {
                        print "<tr class=\"dark\">\n";
        git_footer_html();
  }
  
 +sub snapshot_name {
 +      my ($project, $hash) = @_;
 +
 +      # path/to/project.git  -> project
 +      # path/to/project/.git -> project
 +      my $name = to_utf8($project);
 +      $name =~ s,([^/])/*\.git$,$1,;
 +      $name = basename($name);
 +      # sanitize name
 +      $name =~ s/[[:cntrl:]]/?/g;
 +
 +      my $ver = $hash;
 +      if ($hash =~ /^[0-9a-fA-F]+$/) {
 +              # shorten SHA-1 hash
 +              my $full_hash = git_get_full_hash($project, $hash);
 +              if ($full_hash =~ /^$hash/ && length($hash) > 7) {
 +                      $ver = git_get_short_hash($project, $hash);
 +              }
 +      } elsif ($hash =~ m!^refs/tags/(.*)$!) {
 +              # tags don't need shortened SHA-1 hash
 +              $ver = $1;
 +      } else {
 +              # branches and other need shortened SHA-1 hash
 +              if ($hash =~ m!^refs/(?:heads|remotes)/(.*)$!) {
 +                      $ver = $1;
 +              }
 +              $ver .= '-' . git_get_short_hash($project, $hash);
 +      }
 +      # in case of hierarchical branch names
 +      $ver =~ s!/!.!g;
 +
 +      # name = project-version_string
 +      $name = "$name-$ver";
 +
 +      return wantarray ? ($name, $name) : $name;
 +}
 +
  sub git_snapshot {
        my $format = $input_params{'snapshot_format'};
        if (!@snapshot_fmts) {
                die_error(400, "Invalid snapshot format parameter");
        } elsif (!exists($known_snapshot_formats{$format})) {
                die_error(400, "Unknown snapshot format");
 +      } elsif ($known_snapshot_formats{$format}{'disabled'}) {
 +              die_error(403, "Snapshot format not allowed");
        } elsif (!grep($_ eq $format, @snapshot_fmts)) {
                die_error(403, "Unsupported snapshot format");
        }
  
 -      if (!defined $hash) {
 -              $hash = git_get_head_hash($project);
 +      my $type = git_get_type("$hash^{}");
 +      if (!$type) {
 +              die_error(404, 'Object does not exist');
 +      }  elsif ($type eq 'blob') {
 +              die_error(400, 'Object is not a tree-ish');
        }
  
 -      my $name = $project;
 -      $name =~ s,([^/])/*\.git$,$1,;
 -      $name = basename($name);
 -      my $filename = to_utf8($name);
 -      $name =~ s/\047/\047\\\047\047/g;
 -      my $cmd;
 -      $filename .= "-$hash$known_snapshot_formats{$format}{'suffix'}";
 -      $cmd = quote_command(
 +      my ($name, $prefix) = snapshot_name($project, $hash);
 +      my $filename = "$name$known_snapshot_formats{$format}{'suffix'}";
 +      my $cmd = quote_command(
                git_cmd(), 'archive',
                "--format=$known_snapshot_formats{$format}{'format'}",
 -              "--prefix=$name/", $hash);
 +              "--prefix=$prefix/", $hash);
        if (exists $known_snapshot_formats{$format}{'compressor'}) {
                $cmd .= ' | ' . quote_command(@{$known_snapshot_formats{$format}{'compressor'}});
        }
  
 +      $filename =~ s/(["\\])/\\$1/g;
        print $cgi->header(
                -type => $known_snapshot_formats{$format}{'type'},
 -              -content_disposition => 'inline; filename="' . "$filename" . '"',
 +              -content_disposition => 'inline; filename="' . $filename . '"',
                -status => '200 OK');
  
        open my $fd, "-|", $cmd
        close $fd;
  }
  
 -sub git_log {
 +sub git_log_generic {
 +      my ($fmt_name, $body_subr, $base, $parent, $file_name, $file_hash) = @_;
 +
        my $head = git_get_head_hash($project);
 -      if (!defined $hash) {
 -              $hash = $head;
 +      if (!defined $base) {
 +              $base = $head;
        }
        if (!defined $page) {
                $page = 0;
        }
        my $refs = git_get_references();
  
 -      my @commitlist = parse_commits($hash, 101, (100 * $page));
 +      my $commit_hash = $base;
 +      if (defined $parent) {
 +              $commit_hash = "$parent..$base";
 +      }
 +      my @commitlist =
 +              parse_commits($commit_hash, 101, (100 * $page),
 +                            defined $file_name ? ($file_name, "--full-history") : ());
 +
 +      my $ftype;
 +      if (!defined $file_hash && defined $file_name) {
 +              # some commits could have deleted file in question,
 +              # and not have it in tree, but one of them has to have it
 +              for (my $i = 0; $i < @commitlist; $i++) {
 +                      $file_hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name);
 +                      last if defined $file_hash;
 +              }
 +      }
 +      if (defined $file_hash) {
 +              $ftype = git_get_type($file_hash);
 +      }
 +      if (defined $file_name && !defined $ftype) {
 +              die_error(500, "Unknown type of object");
 +      }
 +      my %co;
 +      if (defined $file_name) {
 +              %co = parse_commit($base)
 +                      or die_error(404, "Unknown commit object");
 +      }
  
 -      my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#commitlist >= 100);
  
 -      my ($patch_max) = gitweb_get_feature('patches');
 -      if ($patch_max) {
 +      my $paging_nav = format_paging_nav($fmt_name, $page, $#commitlist >= 100);
 +      my $next_link = '';
 +      if ($#commitlist >= 100) {
 +              $next_link =
 +                      $cgi->a({-href => href(-replay=>1, page=>$page+1),
 +                               -accesskey => "n", -title => "Alt-n"}, "next");
 +      }
 +      my $patch_max = gitweb_get_feature('patches');
 +      if ($patch_max && !defined $file_name) {
                if ($patch_max < 0 || @commitlist <= $patch_max) {
                        $paging_nav .= " &sdot; " .
                                $cgi->a({-href => href(action=>"patches", -replay=>1)},
        }
  
        git_header_html();
 -      git_print_page_nav('log','', $hash,undef,undef, $paging_nav);
 -
 -      if (!@commitlist) {
 -              my %co = parse_commit($hash);
 -
 -              git_print_header_div('summary', $project);
 -              print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n";
 +      git_print_page_nav($fmt_name,'', $hash,$hash,$hash, $paging_nav);
 +      if (defined $file_name) {
 +              git_print_header_div('commit', esc_html($co{'title'}), $base);
 +      } else {
 +              git_print_header_div('summary', $project)
        }
 -      my $to = ($#commitlist >= 99) ? (99) : ($#commitlist);
 -      for (my $i = 0; $i <= $to; $i++) {
 -              my %co = %{$commitlist[$i]};
 -              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',
 -                             "<span class=\"age\">$co{'age_string'}</span>" .
 -                             esc_html($co{'title'}) . $ref,
 -                             $commit);
 -              print "<div class=\"title_text\">\n" .
 -                    "<div class=\"log_link\">\n" .
 -                    $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") .
 -                    " | " .
 -                    $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") .
 -                    " | " .
 -                    $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
 -                    "<br/>\n" .
 -                    "</div>\n";
 -                    git_print_authorship(\%co, -tag => 'span');
 -                    print "<br/>\n</div>\n";
 +      git_print_page_path($file_name, $ftype, $hash_base)
 +              if (defined $file_name);
 +
 +      $body_subr->(\@commitlist, 0, 99, $refs, $next_link,
 +                   $file_name, $file_hash, $ftype);
  
 -              print "<div class=\"log_body\">\n";
 -              git_print_log($co{'comment'}, -final_empty_line=> 1);
 -              print "</div>\n";
 -      }
 -      if ($#commitlist >= 100) {
 -              print "<div class=\"page_nav\">\n";
 -              print $cgi->a({-href => href(-replay=>1, page=>$page+1),
 -                             -accesskey => "n", -title => "Alt-n"}, "next");
 -              print "</div>\n";
 -      }
        git_footer_html();
  }
  
 +sub git_log {
 +      git_log_generic('log', \&git_log_body,
 +                      $hash, $hash_parent);
 +}
 +
  sub git_commit {
        $hash ||= $hash_base || "HEAD";
        my %co = parse_commit($hash)
                        } @$parents ) .
                        ')';
        }
 -      if (gitweb_check_feature('patches')) {
 +      if (gitweb_check_feature('patches') && @$parents <= 1) {
                $formats_nav .= " | " .
                        $cgi->a({-href => href(action=>"patch", -replay=>1)},
                                "patch");
@@@ -5780,7 -5758,7 +5937,7 @@@ sub git_commitdiff 
                $formats_nav =
                        $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
                                "raw");
 -              if ($patch_max) {
 +              if ($patch_max && @{$co{'parents'}} <= 1) {
                        $formats_nav .= " | " .
                                $cgi->a({-href => href(action=>"patch", -replay=>1)},
                                        "patch");
@@@ -5988,7 -5966,7 +6145,7 @@@ sub git_commitdiff_plain 
  
  # format-patch-style patches
  sub git_patch {
 -      git_commitdiff(-format => 'patch', -single=> 1);
 +      git_commitdiff(-format => 'patch', -single => 1);
  }
  
  sub git_patches {
  }
  
  sub git_history {
 -      if (!defined $hash_base) {
 -              $hash_base = git_get_head_hash($project);
 -      }
 -      if (!defined $page) {
 -              $page = 0;
 -      }
 -      my $ftype;
 -      my %co = parse_commit($hash_base)
 -          or die_error(404, "Unknown commit object");
 -
 -      my $refs = git_get_references();
 -      my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
 -
 -      my @commitlist = parse_commits($hash_base, 101, (100 * $page),
 -                                     $file_name, "--full-history")
 -          or die_error(404, "No such file or directory on given branch");
 -
 -      if (!defined $hash && defined $file_name) {
 -              # some commits could have deleted file in question,
 -              # and not have it in tree, but one of them has to have it
 -              for (my $i = 0; $i <= @commitlist; $i++) {
 -                      $hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name);
 -                      last if defined $hash;
 -              }
 -      }
 -      if (defined $hash) {
 -              $ftype = git_get_type($hash);
 -      }
 -      if (!defined $ftype) {
 -              die_error(500, "Unknown type of object");
 -      }
 -
 -      my $paging_nav = '';
 -      if ($page > 0) {
 -              $paging_nav .=
 -                      $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
 -                                             file_name=>$file_name)},
 -                              "first");
 -              $paging_nav .= " &sdot; " .
 -                      $cgi->a({-href => href(-replay=>1, page=>$page-1),
 -                               -accesskey => "p", -title => "Alt-p"}, "prev");
 -      } else {
 -              $paging_nav .= "first";
 -              $paging_nav .= " &sdot; prev";
 -      }
 -      my $next_link = '';
 -      if ($#commitlist >= 100) {
 -              $next_link =
 -                      $cgi->a({-href => href(-replay=>1, page=>$page+1),
 -                               -accesskey => "n", -title => "Alt-n"}, "next");
 -              $paging_nav .= " &sdot; $next_link";
 -      } else {
 -              $paging_nav .= " &sdot; next";
 -      }
 -
 -      git_header_html();
 -      git_print_page_nav('history','', $hash_base,$co{'tree'},$hash_base, $paging_nav);
 -      git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
 -      git_print_page_path($file_name, $ftype, $hash_base);
 -
 -      git_history_body(\@commitlist, 0, 99,
 -                       $refs, $hash_base, $ftype, $next_link);
 -
 -      git_footer_html();
 +      git_log_generic('history', \&git_history_body,
 +                      $hash_base, $hash_parent_base,
 +                      $file_name, $hash);
  }
  
  sub git_search {
  }
  
  sub git_shortlog {
 -      my $head = git_get_head_hash($project);
 -      if (!defined $hash) {
 -              $hash = $head;
 -      }
 -      if (!defined $page) {
 -              $page = 0;
 -      }
 -      my $refs = git_get_references();
 -
 -      my $commit_hash = $hash;
 -      if (defined $hash_parent) {
 -              $commit_hash = "$hash_parent..$hash";
 -      }
 -      my @commitlist = parse_commits($commit_hash, 101, (100 * $page));
 -
 -      my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#commitlist >= 100);
 -      my $next_link = '';
 -      if ($#commitlist >= 100) {
 -              $next_link =
 -                      $cgi->a({-href => href(-replay=>1, page=>$page+1),
 -                               -accesskey => "n", -title => "Alt-n"}, "next");
 -      }
 -      my $patch_max = gitweb_check_feature('patches');
 -      if ($patch_max) {
 -              if ($patch_max < 0 || @commitlist <= $patch_max) {
 -                      $paging_nav .= " &sdot; " .
 -                              $cgi->a({-href => href(action=>"patches", -replay=>1)},
 -                                      "patches");
 -              }
 -      }
 -
 -      git_header_html();
 -      git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
 -      git_print_header_div('summary', $project);
 -
 -      git_shortlog_body(\@commitlist, 0, 99, $refs, $next_link);
 -
 -      git_footer_html();
 +      git_log_generic('shortlog', \&git_shortlog_body,
 +                      $hash, $hash_parent);
  }
  
  ## ......................................................................