Merge branch 'jl/read-tree-m-dry-run'
authorJunio C Hamano <gitster@pobox.com>
Tue, 31 May 2011 19:08:48 +0000 (12:08 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 31 May 2011 19:08:48 +0000 (12:08 -0700)
* jl/read-tree-m-dry-run:
Teach read-tree the -n|--dry-run option
unpack-trees: add the dry_run flag to unpack_trees_options

79 files changed:
.gitignore
Documentation/RelNotes/1.7.5.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.5.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.6.txt
Documentation/config.txt
Documentation/git-clone.txt
Documentation/git-grep.txt
Documentation/git-init-db.txt
Documentation/git-init.txt
Documentation/git-notes.txt
Documentation/git-rev-list.txt
Documentation/git-sh-setup.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/pretty-options.txt
Documentation/rev-list-options.txt
Documentation/technical/api-diff.txt
Makefile
bisect.c
builtin/clone.c
builtin/fetch-pack.c
builtin/grep.c
builtin/hash-object.c
builtin/init-db.c
builtin/log.c
builtin/notes.c
builtin/receive-pack.c
builtin/rev-parse.c
builtin/update-index.c
cache.h
compat/fnmatch/fnmatch.c
config.c
config.mak.in
configure.ac
contrib/completion/git-completion.bash
fsck.c
git-sh-setup.sh
git-submodule.sh
git.c
gitweb/Makefile
gitweb/README
gitweb/gitweb.perl
gitweb/static/gitweb.css
gitweb/static/gitweb.js [deleted file]
gitweb/static/js/README [new file with mode: 0644]
gitweb/static/js/adjust-timezone.js [new file with mode: 0644]
gitweb/static/js/blame_incremental.js [new file with mode: 0644]
gitweb/static/js/javascript-detection.js [new file with mode: 0644]
gitweb/static/js/lib/common-lib.js [new file with mode: 0644]
gitweb/static/js/lib/cookies.js [new file with mode: 0644]
gitweb/static/js/lib/datetime.js [new file with mode: 0644]
grep.c
grep.h
ident.c
notes-merge.c
pretty.c
read-cache.c
rerere.c
revision.c
revision.h
setup.c
sh-i18n--envsubst.c
sha1-array.c [new file with mode: 0644]
sha1-array.h [new file with mode: 0644]
sha1_file.c
t/README
t/t0001-init.sh
t/t0081-line-buffer.sh
t/t1050-large.sh [new file with mode: 0755]
t/t1300-repo-config.sh
t/t3301-notes.sh
t/t4018-diff-funcname.sh
t/t4202-log.sh
t/t7810-grep.sh
t/t9500-gitweb-standalone-no-errors.sh
t/test-lib.sh
transport.c
transport.h
userdiff.c
index 4bdb83975c080dc035697c3f8d1d8b5aa097c667..acffdfaae684dc49d7205d580fd8921ca4696f7f 100644 (file)
 /gitk-git/gitk-wish
 /gitweb/GITWEB-BUILD-OPTIONS
 /gitweb/gitweb.cgi
+/gitweb/static/gitweb.js
 /gitweb/static/gitweb.min.*
 /test-chmtime
 /test-ctype
diff --git a/Documentation/RelNotes/1.7.5.3.txt b/Documentation/RelNotes/1.7.5.3.txt
new file mode 100644 (file)
index 0000000..9c03353
--- /dev/null
@@ -0,0 +1,32 @@
+Git v1.7.5.3 Release Notes
+==========================
+
+Fixes since v1.7.5.2
+--------------------
+
+ * The bash completion scripts should correctly work using zsh's bash
+   completion emulation layer now.
+
+ * Setting $(prefix) in config.mak did not affect where etc/gitconfig
+   file is read from, even though passing it from the command line of
+   $(MAKE) did.
+
+ * The logic to handle "&" (expand to UNIX username) in GECOS field
+   miscounted the length of the name it formatted.
+
+ * "git cherry-pick -s resolve" failed to cherry-pick a root commit.
+
+ * "git diff --word-diff" misbehaved when diff.suppress-blank-empty was
+   in effect.
+
+ * "git log --stdin path" with an input that has additional pathspec
+   used to corrupt memory.
+
+ * "git send-pack" (hence "git push") over smalt-HTTP protocol could
+   deadlock when the client side pack-object died early.
+
+ * Compressed tarball gitweb generates used to be made with the timestamp
+   of the tarball generation; this was bad because snapshot from the same
+   tree should result in a same tarball.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.5.4.txt b/Documentation/RelNotes/1.7.5.4.txt
new file mode 100644 (file)
index 0000000..f3412ce
--- /dev/null
@@ -0,0 +1,18 @@
+Git v1.7.5.4 Release Notes
+==========================
+
+Fixes since v1.7.5.3
+--------------------
+
+ * The single-key mode of "git add -p" was easily fooled into thinking
+   that it was told to add everthing ('a') when up-arrow was pressed by
+   mistake.
+
+ * "git diff -C -C" used to disable the rename detection entirely when
+   there are too many copy candidate paths in the tree; now it falls
+   back to "-C" when doing so would keep the copy candidate paths
+   under the rename detection limit.
+
+ * "git rerere" did not diagnose a corrupt MERGE_RR file in some cases.
+
+And other minor fixes and documentation updates.
index 3d5ff4d9b2fc775b1d9bc3c52fe97a9cf9fd08cc..7e1c7f177327a253d5adb1a9aae31da9caf62b20 100644 (file)
@@ -6,7 +6,8 @@ Updates since v1.7.5
 
  * Various git-svn updates.
 
- * Updates the way content tags are handled in gitweb.
+ * Updates the way content tags are handled in gitweb.  Also adds
+   a UI to choose common timezone for displaying the dates.
 
  * Similar to branch names, tagnames that begin with "-" are now
    disallowed.
@@ -16,6 +17,15 @@ Updates since v1.7.5
 
  * The scripting part of the codebase is getting prepared for i18n/l10n.
 
+ * Pushing and pulling from a repository with large number of refs that
+   point to identical commits are optimized by not listing the same commit
+   during the common ancestor negotiation exchange with the other side.
+
+ * Adding a file larger than core.bigfilethreshold (defaults to 1/2 Gig)
+   using "git add" will send the contents straight to a packfile without
+   having to hold it and its compressed representation both at the same
+   time in memory.
+
  * Processes spawned by "[alias] <name> = !process" in the configuration
    can inspect GIT_PREFIX environment variable to learn where in the
    working tree the original command was invoked.
@@ -66,6 +76,9 @@ Updates since v1.7.5
    "--show-notes" option.  Unlike "--show-notes", "--notes=<ref>" does
    not imply showing the default notes.
 
+ * They also learned a log.abbrevCommit configuration variable to augment
+   the --abbrev-commit command line option.
+
  * "git ls-remote" learned "--exit-code" option to consider it a
    different kind of error when no remote ref to be shown.
 
@@ -93,10 +106,6 @@ Updates since v1.7.5
    still in a conflicted state during a merge, to avoid using information
    that is not final and possibly corrupt with conflict markers.
 
- * Compressed tarball gitweb generates is made without the timestamp of
-   the tarball generation; snapshot from the same tree should result in
-   a same tarball.
-
 Also contains various documentation updates and minor miscellaneous
 changes.
 
@@ -107,45 +116,16 @@ Fixes since v1.7.5
 Unless otherwise noted, all the fixes in 1.7.5.X maintenance track are
 included in this release.
 
- * Setting $(prefix) in config.mak did not affect where etc/gitconfig
-   file is read from, even though passing it from the command line of
-   $(MAKE) did.
-   (merge kk/maint-prefix-in-config-mak later)
-
- * The bash completion scripts should correctly work using zsh's bash
-   completion emulation layer now.
-   (merge fc/completion-zsh later)
-
- * The logic to handle "&" (expand to UNIX username) in GECOS field
-   miscounted the length of the name it formatted.
-   (merge rg/copy-gecos-username later)
-
  * The single-key mode of "git add -p" was easily fooled into thinking
    that it was told to add everthing ('a') when up-arrow was pressed by
    mistake.
    (merge tr/add-i-no-escape later)
 
- * "git cherry-pick -s resolve" failed to cherry-pick a root commit.
-   (merge jk/cherry-pick-root-with-resolve later)
-
  * "git config" used to choke with an insanely long line.
    (merge ef/maint-strbuf-init later)
 
- * "git diff --word-diff" misbehaved when diff.suppress-blank-empty was
-   in effect.
-   (merge jm/maint-diff-words-with-sbe later)
-
- * "git log --stdin path" with an input that has additional pathspec
-   used to corrupt memory.
-   (merge jc/maint-pathspec-stdin-and-cmdline later)
-
- * "git send-pack" (hence "git push") over smalt-HTTP protocol could
-   deadlock when the client side pack-object died early.
-   (merge js/maint-send-pack-stateless-rpc-deadlock-fix later)
-   (merge jk/git-connection-deadlock-fix later)
-
 ---
 exec >/var/tmp/1
 echo O=$(git describe master)
-O=v1.7.5.2-352-g4961210
+O=v1.7.5.3-365-g7eacc2b
 git shortlog --no-merges ^maint ^$O master
index 1a060ecbc8b137bff3df2c689d7e803937c66d18..6b937771994f5b0a532b6f2cc522a9d3f35c9c09 100644 (file)
@@ -1315,6 +1315,11 @@ interactive.singlekey::
        setting is silently ignored if portable keystroke input
        is not available.
 
+log.abbrevCommit::
+       If true, makes linkgit:git-log[1], linkgit:git-show[1], and
+       linkgit:git-whatchanged[1] assume `\--abbrev-commit`. You may
+       override this option with `\--no-abbrev-commit`.
+
 log.date::
        Set the default date-time mode for the 'log' command.
        Setting a value for log.date is similar to using 'git log''s
index 86eb4c93682aa3d92fe001ae871913fc0dec8607..b093e45497248076c335391f3e95b6384520ef6b 100644 (file)
@@ -12,7 +12,7 @@ SYNOPSIS
 'git clone' [--template=<template_directory>]
          [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
          [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
-         [--separate-git-dir|-L <git dir>]
+         [--separate-git-dir <git dir>]
          [--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>
          [<directory>]
 
@@ -177,7 +177,6 @@ objects from the source repository into a pack in the cloned repository.
        repository does not have a worktree/checkout (i.e. if any of
        `--no-checkout`/`-n`, `--bare`, or `--mirror` is given)
 
--L=<git dir>::
 --separate-git-dir=<git dir>::
        Instead of placing the cloned repository where it is supposed
        to be, place the cloned repository at the specified directory,
index d7523b3e45ff06bd7a8abf4ff466fe619c8268e7..e150c77cffb5d082822bd65c7bfbeb1f431fc42f 100644 (file)
@@ -12,7 +12,8 @@ SYNOPSIS
 'git grep' [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp]
           [-v | --invert-match] [-h|-H] [--full-name]
           [-E | --extended-regexp] [-G | --basic-regexp]
-          [-F | --fixed-strings] [-n]
+          [-P | --perl-regexp]
+          [-F | --fixed-strings] [-n | --line-number]
           [-l | --files-with-matches] [-L | --files-without-match]
           [(-O | --open-files-in-pager) [<pager>]]
           [-z | --null]
@@ -97,6 +98,11 @@ OPTIONS
        Use POSIX extended/basic regexp for patterns.  Default
        is to use basic regexp.
 
+-P::
+--perl-regexp::
+       Use Perl-compatible regexp for patterns. Requires libpcre to be
+       compiled in.
+
 -F::
 --fixed-strings::
        Use fixed strings for patterns (don't interpret pattern
index 2c4c716f33f9dd3f0b876a013e22ac115ac3c936..9f97f5a91584a7469a3b0d2f8705febbe5c5ab58 100644 (file)
@@ -8,7 +8,7 @@ git-init-db - Creates an empty git repository
 
 SYNOPSIS
 --------
-'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--shared[=<permissions>]]
+'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--separate-git-dir <git dir>] [--shared[=<permissions>]]
 
 
 DESCRIPTION
index 58cd01145ab8605d30a9c670ee70391b9d974a32..f2777a7786e6bf9c0cb907c77134b06321523654 100644 (file)
@@ -9,7 +9,7 @@ git-init - Create an empty git repository or reinitialize an existing one
 SYNOPSIS
 --------
 'git init' [-q | --quiet] [--bare] [--template=<template_directory>]
-         [--separate-git-dir|-L <git dir>]
+         [--separate-git-dir <git dir>]
          [--shared[=<permissions>]] [directory]
 
 
@@ -54,7 +54,6 @@ current working directory.
 Specify the directory from which templates will be used.  (See the "TEMPLATE
 DIRECTORY" section below.)
 
--L=<git dir>::
 --separate-git-dir=<git dir>::
 
 Instead of initializing the repository where it is supposed to be,
index 913ecd8c433b7ebaaf932dd5cf9b789dc06b7b95..6a187f2e2336575fb4974b8c492359913a8c7a9e 100644 (file)
@@ -17,7 +17,7 @@ SYNOPSIS
 'git notes' merge [-v | -q] [-s <strategy> ] <notes_ref>
 'git notes' merge --commit [-v | -q]
 'git notes' merge --abort [-v | -q]
-'git notes' remove [<object>]
+'git notes' remove [--ignore-missing] [--stdin] [<object>...]
 'git notes' prune [-n | -v]
 'git notes' get-ref
 
@@ -106,8 +106,9 @@ When done, the user can either finalize the merge with
 'git notes merge --abort'.
 
 remove::
-       Remove the notes for a given object (defaults to HEAD).
-       This is equivalent to specifying an empty note message to
+       Remove the notes for given objects (defaults to HEAD). When
+       giving zero or one object from the command line, this is
+       equivalent to specifying an empty note message to
        the `edit` subcommand.
 
 prune::
@@ -154,6 +155,15 @@ OPTIONS
        'GIT_NOTES_REF' and the "core.notesRef" configuration.  The ref
        is taken to be in `refs/notes/` if it is not qualified.
 
+--ignore-missing::
+       Do not consider it an error to request removing notes from an
+       object that does not have notes attached to it.
+
+--stdin::
+       Also read the object names to remove notes from from the standard
+       input (there is no reason you cannot combine this with object
+       names from the command line).
+
 -n::
 --dry-run::
        Do not remove anything; just report the object names whose notes
index 415f4f0b303da22e9eec19c1b9e24732a74ecf67..38fafcaa6b4173ad7f900c71efaa8ea25470072f 100644 (file)
@@ -29,6 +29,7 @@ SYNOPSIS
             [ \--tags[=<pattern>] ]
             [ \--remotes[=<pattern>] ]
             [ \--glob=<glob-pattern> ]
+            [ \--ignore-missing ]
             [ \--stdin ]
             [ \--quiet ]
             [ \--topo-order ]
index 053df505bc3aaa5e74c3a9266858eef0de1c2bd2..27fd8ba854698dcd31f5348ddc00a607abb64965 100644 (file)
@@ -58,9 +58,14 @@ cd_to_toplevel::
        runs chdir to the toplevel of the working tree.
 
 require_work_tree::
-       checks if the repository is a bare repository, and dies
-       if so.  Used by scripts that require working tree
-       (e.g. `checkout`).
+       checks if the current directory is within the working tree
+       of the repository, and otherwise dies.
+
+require_work_tree_exists::
+       checks if the working tree associated with the repository
+       exists, and otherwise dies.  Often done before calling
+       cd_to_toplevel, which is impossible to do if there is no
+       working tree.
 
 get_author_ident_from_commit::
        outputs code for use with eval to set the GIT_AUTHOR_NAME,
index 19d4deff7031b433eb1f6b80c873b86183faefbb..65317cce18818a90f8176bd9518b4d73c2a25cf5 100644 (file)
@@ -44,9 +44,10 @@ unreleased) version of git, that is available from 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v1.7.5.2/git.html[documentation for release 1.7.5.2]
+* link:v1.7.5.3/git.html[documentation for release 1.7.5.3]
 
 * release notes for
+  link:RelNotes/1.7.5.3.txt[1.7.5.3],
   link:RelNotes/1.7.5.2.txt[1.7.5.2],
   link:RelNotes/1.7.5.1.txt[1.7.5.1],
   link:RelNotes/1.7.5.txt[1.7.5].
index 15aebc60623d97053aa14f3e6463d12eb8f60f26..412c55b549354471a56a4e56ca5533277c83bc54 100644 (file)
@@ -593,6 +593,37 @@ and now produces better output), you can remove the cache
 manually with `git update-ref -d refs/notes/textconv/jpg` (where
 "jpg" is the name of the diff driver, as in the example above).
 
+Choosing textconv versus external diff
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you want to show differences between binary or specially-formatted
+blobs in your repository, you can choose to use either an external diff
+command, or to use textconv to convert them to a diff-able text format.
+Which method you choose depends on your exact situation.
+
+The advantage of using an external diff command is flexibility. You are
+not bound to find line-oriented changes, nor is it necessary for the
+output to resemble unified diff. You are free to locate and report
+changes in the most appropriate way for your data format.
+
+A textconv, by comparison, is much more limiting. You provide a
+transformation of the data into a line-oriented text format, and git
+uses its regular diff tools to generate the output. There are several
+advantages to choosing this method:
+
+1. Ease of use. It is often much simpler to write a binary to text
+   transformation than it is to perform your own diff. In many cases,
+   existing programs can be used as textconv filters (e.g., exif,
+   odt2txt).
+
+2. Git diff features. By performing only the transformation step
+   yourself, you can still utilize many of git's diff features,
+   including colorization, word-diff, and combined diffs for merges.
+
+3. Caching. Textconv caching can speed up repeated diffs, such as those
+   you might trigger by running `git log -p`.
+
+
 Marking files as binary
 ^^^^^^^^^^^^^^^^^^^^^^^
 
index d5c977262a818683ebd47d77d37a26c66737119f..2a3dc8664f16957a05bc4d81824d7995517ac89c 100644 (file)
@@ -19,6 +19,11 @@ configuration (see linkgit:git-config[1]).
 This should make "--pretty=oneline" a whole lot more readable for
 people using 80-column terminals.
 
+--no-abbrev-commit::
+       Show the full 40-byte hexadecimal commit object name. This negates
+       `--abbrev-commit` and those options which imply it such as
+       "--oneline". It also overrides the 'log.abbrevCommit' variable.
+
 --oneline::
        This is a shorthand for "--pretty=oneline --abbrev-commit"
        used together.
index 52bae31fcb7f741c34911d94a9253a2a546ee2c2..7e7ba68781f98b55f4189fe0fb4dd6e96c47c0af 100644 (file)
@@ -139,6 +139,10 @@ parents) and `--max-parents=-1` (negative numbers denote no upper limit).
        is automatically prepended if missing. If pattern lacks '?', '*',
        or '[', '/*' at the end is implied.
 
+--ignore-missing::
+
+       Upon seeing an invalid object name in the input, pretend as if
+       the bad input was not given.
 
 ifndef::git-rev-list[]
 --bisect::
index 20b0241d30026747391fa4b6b38de5cf959cee70..2d2ebc04b74ef2b1bf39df892d303d37076325d1 100644 (file)
@@ -32,7 +32,7 @@ Calling sequence
 
 * As you find different pairs of files, call `diff_change()` to feed
   modified files, `diff_addremove()` to feed created or deleted files,
-  or `diff_unmerged()` to feed a file whose state is 'unmerged' to the
+  or `diff_unmerge()` to feed a file whose state is 'unmerged' to the
   API.  These are thin wrappers to a lower-level `diff_queue()` function
   that is flexible enough to record any of these kinds of changes.
 
@@ -50,7 +50,7 @@ Data structures
 This is the internal representation for a single file (blob).  It
 records the blob object name (if known -- for a work tree file it
 typically is a NUL SHA-1), filemode and pathname.  This is what the
-`diff_addremove()`, `diff_change()` and `diff_unmerged()` synthesize and
+`diff_addremove()`, `diff_change()` and `diff_unmerge()` synthesize and
 feed `diff_queue()` function with.
 
 * `struct diff_filepair`
index db72c45f31c7166a46702e973404bd8a73b0652b..e40ac0c7f5ec2f304b88d92f47ff94272f5ce2a4 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -24,6 +24,12 @@ all::
 # Define NO_OPENSSL environment variable if you do not have OpenSSL.
 # This also implies BLK_SHA1.
 #
+# Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be
+# able to use Perl-compatible regular expressions.
+#
+# Define LIBPCREDIR=/foo/bar if your libpcre header and library files are in
+# /foo/bar/include and /foo/bar/lib directories.
+#
 # 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://
 # transports.
@@ -70,6 +76,9 @@ all::
 # Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the
 # FNM_CASEFOLD GNU extension.
 #
+# Define NO_GECOS_IN_PWENT if you don't have pw_gecos in struct passwd
+# in the C library.
+#
 # Define NO_LIBGEN_H if you don't have libgen.h.
 #
 # Define NEEDS_LIBGEN if your libgen needs -lgen when linking
@@ -542,6 +551,7 @@ LIB_H += rerere.h
 LIB_H += resolve-undo.h
 LIB_H += revision.h
 LIB_H += run-command.h
+LIB_H += sha1-array.h
 LIB_H += sha1-lookup.h
 LIB_H += sideband.h
 LIB_H += sigchain.h
@@ -644,6 +654,7 @@ LIB_OBJS += revision.o
 LIB_OBJS += run-command.o
 LIB_OBJS += server-info.o
 LIB_OBJS += setup.o
+LIB_OBJS += sha1-array.o
 LIB_OBJS += sha1-lookup.o
 LIB_OBJS += sha1_file.o
 LIB_OBJS += sha1_name.o
@@ -1253,6 +1264,15 @@ ifdef NO_LIBGEN_H
        COMPAT_OBJS += compat/basename.o
 endif
 
+ifdef USE_LIBPCRE
+       BASIC_CFLAGS += -DUSE_LIBPCRE
+       ifdef LIBPCREDIR
+               BASIC_CFLAGS += -I$(LIBPCREDIR)/include
+               EXTLIBS += -L$(LIBPCREDIR)/$(lib) $(CC_LD_DYNPATH)$(LIBPCREDIR)/$(lib)
+       endif
+       EXTLIBS += -lpcre
+endif
+
 ifdef NO_CURL
        BASIC_CFLAGS += -DNO_CURL
        REMOTE_CURL_PRIMARY =
@@ -2084,6 +2104,7 @@ GIT-BUILD-OPTIONS: FORCE
        @echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@
        @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
        @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
+       @echo USE_LIBPCRE=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@
        @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
        @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
 ifdef GIT_TEST_CMP
index 060c042f8bcc2d402cb9908be3bdbd3bb180a862..dd7e8ed69bc0c1268dc3b0a6590501ba9c10d7db 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -9,13 +9,7 @@
 #include "run-command.h"
 #include "log-tree.h"
 #include "bisect.h"
-
-struct sha1_array {
-       unsigned char (*sha1)[20];
-       int sha1_nr;
-       int sha1_alloc;
-       int sorted;
-};
+#include "sha1-array.h"
 
 static struct sha1_array good_revs;
 static struct sha1_array skipped_revs;
@@ -425,22 +419,15 @@ static void argv_array_push_sha1(struct argv_array *array,
        argv_array_push(array, strbuf_detach(&buf, NULL));
 }
 
-static void sha1_array_push(struct sha1_array *array,
-                           const unsigned char *sha1)
-{
-       ALLOC_GROW(array->sha1, array->sha1_nr + 1, array->sha1_alloc);
-       hashcpy(array->sha1[array->sha1_nr++], sha1);
-}
-
 static int register_ref(const char *refname, const unsigned char *sha1,
                        int flags, void *cb_data)
 {
        if (!strcmp(refname, "bad")) {
                current_bad_sha1 = sha1;
        } else if (!prefixcmp(refname, "good-")) {
-               sha1_array_push(&good_revs, sha1);
+               sha1_array_append(&good_revs, sha1);
        } else if (!prefixcmp(refname, "skip-")) {
-               sha1_array_push(&skipped_revs, sha1);
+               sha1_array_append(&skipped_revs, sha1);
        }
 
        return 0;
@@ -477,41 +464,14 @@ static void read_bisect_paths(struct argv_array *array)
        fclose(fp);
 }
 
-static int array_cmp(const void *a, const void *b)
-{
-       return hashcmp(a, b);
-}
-
-static void sort_sha1_array(struct sha1_array *array)
-{
-       qsort(array->sha1, array->sha1_nr, sizeof(*array->sha1), array_cmp);
-
-       array->sorted = 1;
-}
-
-static const unsigned char *sha1_access(size_t index, void *table)
-{
-       unsigned char (*array)[20] = table;
-       return array[index];
-}
-
-static int lookup_sha1_array(struct sha1_array *array,
-                            const unsigned char *sha1)
-{
-       if (!array->sorted)
-               sort_sha1_array(array);
-
-       return sha1_pos(sha1, array->sha1, array->sha1_nr, sha1_access);
-}
-
 static char *join_sha1_array_hex(struct sha1_array *array, char delim)
 {
        struct strbuf joined_hexs = STRBUF_INIT;
        int i;
 
-       for (i = 0; i < array->sha1_nr; i++) {
+       for (i = 0; i < array->nr; i++) {
                strbuf_addstr(&joined_hexs, sha1_to_hex(array->sha1[i]));
-               if (i + 1 < array->sha1_nr)
+               if (i + 1 < array->nr)
                        strbuf_addch(&joined_hexs, delim);
        }
 
@@ -546,13 +506,13 @@ struct commit_list *filter_skipped(struct commit_list *list,
        if (count)
                *count = 0;
 
-       if (!skipped_revs.sha1_nr)
+       if (!skipped_revs.nr)
                return list;
 
        while (list) {
                struct commit_list *next = list->next;
                list->next = NULL;
-               if (0 <= lookup_sha1_array(&skipped_revs,
+               if (0 <= sha1_array_lookup(&skipped_revs,
                                           list->item->object.sha1)) {
                        if (skipped_first && !*skipped_first)
                                *skipped_first = 1;
@@ -647,7 +607,7 @@ static struct commit_list *managed_skipped(struct commit_list *list,
 
        *tried = NULL;
 
-       if (!skipped_revs.sha1_nr)
+       if (!skipped_revs.nr)
                return list;
 
        list = filter_skipped(list, tried, 0, &count, &skipped_first);
@@ -672,7 +632,7 @@ static void bisect_rev_setup(struct rev_info *revs, const char *prefix,
        /* rev_argv.argv[0] will be ignored by setup_revisions */
        argv_array_push(&rev_argv, xstrdup("bisect_rev_setup"));
        argv_array_push_sha1(&rev_argv, current_bad_sha1, bad_format);
-       for (i = 0; i < good_revs.sha1_nr; i++)
+       for (i = 0; i < good_revs.nr; i++)
                argv_array_push_sha1(&rev_argv, good_revs.sha1[i],
                                     good_format);
        argv_array_push(&rev_argv, xstrdup("--"));
@@ -772,12 +732,12 @@ static struct commit *get_commit_reference(const unsigned char *sha1)
 
 static struct commit **get_bad_and_good_commits(int *rev_nr)
 {
-       int len = 1 + good_revs.sha1_nr;
+       int len = 1 + good_revs.nr;
        struct commit **rev = xmalloc(len * sizeof(*rev));
        int i, n = 0;
 
        rev[n++] = get_commit_reference(current_bad_sha1);
-       for (i = 0; i < good_revs.sha1_nr; i++)
+       for (i = 0; i < good_revs.nr; i++)
                rev[n++] = get_commit_reference(good_revs.sha1[i]);
        *rev_nr = n;
 
@@ -840,9 +800,9 @@ static void check_merge_bases(void)
                const unsigned char *mb = result->item->object.sha1;
                if (!hashcmp(mb, current_bad_sha1)) {
                        handle_bad_merge_base();
-               } else if (0 <= lookup_sha1_array(&good_revs, mb)) {
+               } else if (0 <= sha1_array_lookup(&good_revs, mb)) {
                        continue;
-               } else if (0 <= lookup_sha1_array(&skipped_revs, mb)) {
+               } else if (0 <= sha1_array_lookup(&skipped_revs, mb)) {
                        handle_skipped_merge_base(mb);
                } else {
                        printf("Bisecting: a merge base must be tested\n");
@@ -903,7 +863,7 @@ static void check_good_are_ancestors_of_bad(const char *prefix)
                return;
 
        /* Bisecting with no good rev is ok. */
-       if (good_revs.sha1_nr == 0)
+       if (good_revs.nr == 0)
                return;
 
        /* Check if all good revs are ancestor of the bad rev. */
@@ -968,7 +928,7 @@ int bisect_next_all(const char *prefix)
        bisect_common(&revs);
 
        revs.commits = find_bisection(revs.commits, &reaches, &all,
-                                      !!skipped_revs.sha1_nr);
+                                      !!skipped_revs.nr);
        revs.commits = managed_skipped(revs.commits, &tried);
 
        if (!revs.commits) {
index 49c838fd3ff24ecc7821fbcf8f312d76967c168d..f579794d9a93a0e55289921f20b8f68b85211ca1 100644 (file)
@@ -81,7 +81,7 @@ static struct option builtin_clone_options[] = {
                   "path to git-upload-pack on the remote"),
        OPT_STRING(0, "depth", &option_depth, "depth",
                    "create a shallow clone of that depth"),
-       OPT_STRING('L', "separate-git-dir", &real_git_dir, "gitdir",
+       OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir",
                   "separate git dir from working tree"),
 
        OPT_END()
index 85aff029b225c023f09fda635f16cc00a22ed7ca..436798410210b868cfbe439a1603b0a340a32c50 100644 (file)
@@ -226,7 +226,7 @@ static void insert_one_alternate_ref(const struct ref *ref, void *unused)
 
 static void insert_alternate_refs(void)
 {
-       foreach_alt_odb(refs_from_alternate_cb, insert_one_alternate_ref);
+       for_each_alternate_ref(insert_one_alternate_ref, NULL);
 }
 
 #define INITIAL_FLUSH 16
@@ -472,8 +472,10 @@ static int mark_complete(const char *path, const unsigned char *sha1, int flag,
        }
        if (o && o->type == OBJ_COMMIT) {
                struct commit *commit = (struct commit *)o;
-               commit->object.flags |= COMPLETE;
-               commit_list_insert_by_date(commit, &complete);
+               if (!(commit->object.flags & COMPLETE)) {
+                       commit->object.flags |= COMPLETE;
+                       commit_list_insert_by_date(commit, &complete);
+               }
        }
        return 0;
 }
index 931eee0d750cd8662347e2a7c8e8a89d23228e17..871afaa3c76ea34b67671a6e4a46957a08a946e5 100644 (file)
@@ -753,6 +753,15 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        int i;
        int dummy;
        int use_index = 1;
+       enum {
+               pattern_type_unspecified = 0,
+               pattern_type_bre,
+               pattern_type_ere,
+               pattern_type_fixed,
+               pattern_type_pcre,
+       };
+       int pattern_type = pattern_type_unspecified;
+
        struct option options[] = {
                OPT_BOOLEAN(0, "cached", &cached,
                        "search in index instead of in the work tree"),
@@ -774,13 +783,18 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                        "descend at most <depth> levels", PARSE_OPT_NONEG,
                        NULL, 1 },
                OPT_GROUP(""),
-               OPT_BIT('E', "extended-regexp", &opt.regflags,
-                       "use extended POSIX regular expressions", REG_EXTENDED),
-               OPT_NEGBIT('G', "basic-regexp", &opt.regflags,
-                       "use basic POSIX regular expressions (default)",
-                       REG_EXTENDED),
-               OPT_BOOLEAN('F', "fixed-strings", &opt.fixed,
-                       "interpret patterns as fixed strings"),
+               OPT_SET_INT('E', "extended-regexp", &pattern_type,
+                           "use extended POSIX regular expressions",
+                           pattern_type_ere),
+               OPT_SET_INT('G', "basic-regexp", &pattern_type,
+                           "use basic POSIX regular expressions (default)",
+                           pattern_type_bre),
+               OPT_SET_INT('F', "fixed-strings", &pattern_type,
+                           "interpret patterns as fixed strings",
+                           pattern_type_fixed),
+               OPT_SET_INT('P', "perl-regexp", &pattern_type,
+                           "use Perl-compatible regular expressions",
+                           pattern_type_pcre),
                OPT_GROUP(""),
                OPT_BOOLEAN('n', "line-number", &opt.linenum, "show line numbers"),
                OPT_NEGBIT('h', NULL, &opt.pathname, "don't show filenames", 1),
@@ -886,6 +900,28 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                             PARSE_OPT_KEEP_DASHDASH |
                             PARSE_OPT_STOP_AT_NON_OPTION |
                             PARSE_OPT_NO_INTERNAL_HELP);
+       switch (pattern_type) {
+       case pattern_type_fixed:
+               opt.fixed = 1;
+               opt.pcre = 0;
+               break;
+       case pattern_type_bre:
+               opt.fixed = 0;
+               opt.pcre = 0;
+               opt.regflags &= ~REG_EXTENDED;
+               break;
+       case pattern_type_ere:
+               opt.fixed = 0;
+               opt.pcre = 0;
+               opt.regflags |= REG_EXTENDED;
+               break;
+       case pattern_type_pcre:
+               opt.fixed = 0;
+               opt.pcre = 1;
+               break;
+       default:
+               break; /* nothing */
+       }
 
        if (use_index && !startup_info->have_repository)
                /* die the same way as if we did it at the beginning */
@@ -925,8 +961,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                die(_("no pattern given."));
        if (!opt.fixed && opt.ignore_case)
                opt.regflags |= REG_ICASE;
-       if ((opt.regflags != REG_NEWLINE) && opt.fixed)
-               die(_("cannot mix --fixed-strings and regexp"));
 
 #ifndef NO_PTHREADS
        if (online_cpus() == 1 || !grep_threads_ok(&opt))
index b96f46acf52d23f68c1ec164be8264396dea22db..33911fd5e9325210dae63b3252fa3b56d42b545c 100644 (file)
@@ -14,8 +14,11 @@ static void hash_fd(int fd, const char *type, int write_object, const char *path
 {
        struct stat st;
        unsigned char sha1[20];
+       unsigned flags = (HASH_FORMAT_CHECK |
+                         (write_object ? HASH_WRITE_OBJECT : 0));
+
        if (fstat(fd, &st) < 0 ||
-           index_fd(sha1, fd, &st, write_object, type_from_string(type), path, 1))
+           index_fd(sha1, fd, &st, type_from_string(type), path, flags))
                die(write_object
                    ? "Unable to add %s to database"
                    : "Unable to hash %s", path);
index ba13a54793d46de2410db14e6f387674e3b27c82..025aa47c804e4400cfa16ae8acd8dc2a5175e7c0 100644 (file)
@@ -490,7 +490,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                        "specify that the git repository is to be shared amongst several users",
                        PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0},
                OPT_BIT('q', "quiet", &flags, "be quiet", INIT_DB_QUIET),
-               OPT_STRING('L', "separate-git-dir", &real_git_dir, "gitdir",
+               OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir",
                           "separate git dir from working tree"),
                OPT_END()
        };
index f6219909a787ae355a43e39974140a36848ab1d6..27849dc91de03a46d4c9357ca0d7b3f79188cc98 100644 (file)
@@ -23,6 +23,7 @@
 /* Set a default date-time format for git log ("log.date" config variable) */
 static const char *default_date_mode = NULL;
 
+static int default_abbrev_commit;
 static int default_show_root = 1;
 static int decoration_style;
 static int decoration_given;
@@ -77,6 +78,7 @@ static void cmd_log_init_defaults(struct rev_info *rev)
                get_commit_format(fmt_pretty, rev);
        rev->verbose_header = 1;
        DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
+       rev->abbrev_commit = default_abbrev_commit;
        rev->show_root_diff = default_show_root;
        rev->subject_prefix = fmt_patch_subject_prefix;
        DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV);
@@ -92,7 +94,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
        int quiet = 0, source = 0;
 
        const struct option builtin_log_options[] = {
-               OPT_BOOLEAN(0, "quiet", &quiet, "supress diff output"),
+               OPT_BOOLEAN(0, "quiet", &quiet, "suppress diff output"),
                OPT_BOOLEAN(0, "source", &source, "show source"),
                { OPTION_CALLBACK, 0, "decorate", NULL, NULL, "decorate options",
                  PARSE_OPT_OPTARG, decorate_callback},
@@ -129,13 +131,16 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
        if (source)
                rev->show_source = 1;
 
-       /*
-        * defeat log.decorate configuration interacting with --pretty=raw
-        * from the command line.
-        */
-       if (!decoration_given && rev->pretty_given
-           && rev->commit_format == CMIT_FMT_RAW)
-               decoration_style = 0;
+       if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) {
+               /*
+                * "log --pretty=raw" is special; ignore UI oriented
+                * configuration variables such as decoration.
+                */
+               if (!decoration_given)
+                       decoration_style = 0;
+               if (!rev->abbrev_commit_given)
+                       rev->abbrev_commit = 0;
+       }
 
        if (decoration_style) {
                rev->show_decorations = 1;
@@ -323,6 +328,10 @@ static int git_log_config(const char *var, const char *value, void *cb)
                return git_config_string(&fmt_pretty, var, value);
        if (!strcmp(var, "format.subjectprefix"))
                return git_config_string(&fmt_patch_subject_prefix, var, value);
+       if (!strcmp(var, "log.abbrevcommit")) {
+               default_abbrev_commit = git_config_bool(var, value);
+               return 0;
+       }
        if (!strcmp(var, "log.date"))
                return git_config_string(&default_date_mode, var, value);
        if (!strcmp(var, "log.decorate")) {
@@ -516,11 +525,11 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix)
 
        init_revisions(&rev, prefix);
        init_reflog_walk(&rev.reflog_info);
-       rev.abbrev_commit = 1;
        rev.verbose_header = 1;
        memset(&opt, 0, sizeof(opt));
        opt.def = "HEAD";
        cmd_log_init_defaults(&rev);
+       rev.abbrev_commit = 1;
        rev.commit_format = CMIT_FMT_ONELINE;
        rev.use_terminator = 1;
        rev.always_show_header = 1;
index 1fb1f734397668280b444e63dc8d3c129d41e3b9..f8e437db0156043f1586e66adf343e34ef6cf4dc 100644 (file)
@@ -29,7 +29,7 @@ static const char * const git_notes_usage[] = {
        "git notes [--ref <notes_ref>] merge [-v | -q] [-s <strategy> ] <notes_ref>",
        "git notes merge --commit [-v | -q]",
        "git notes merge --abort [-v | -q]",
-       "git notes [--ref <notes_ref>] remove [<object>]",
+       "git notes [--ref <notes_ref>] remove [<object>...]",
        "git notes [--ref <notes_ref>] prune [-n | -v]",
        "git notes [--ref <notes_ref>] get-ref",
        NULL
@@ -953,40 +953,60 @@ static int merge(int argc, const char **argv, const char *prefix)
        return result < 0; /* return non-zero on conflicts */
 }
 
+#define IGNORE_MISSING 1
+
+static int remove_one_note(struct notes_tree *t, const char *name, unsigned flag)
+{
+       int status;
+       unsigned char sha1[20];
+       if (get_sha1(name, sha1))
+               return error(_("Failed to resolve '%s' as a valid ref."), name);
+       status = remove_note(t, sha1);
+       if (status)
+               fprintf(stderr, _("Object %s has no note\n"), name);
+       else
+               fprintf(stderr, _("Removing note for object %s\n"), name);
+       return (flag & IGNORE_MISSING) ? 0 : status;
+}
+
 static int remove_cmd(int argc, const char **argv, const char *prefix)
 {
+       unsigned flag = 0;
+       int from_stdin = 0;
        struct option options[] = {
+               OPT_BIT(0, "ignore-missing", &flag,
+                       "attempt to remove non-existent note is not an error",
+                       IGNORE_MISSING),
+               OPT_BOOLEAN(0, "stdin", &from_stdin,
+                           "read object names from the standard input"),
                OPT_END()
        };
-       const char *object_ref;
        struct notes_tree *t;
-       unsigned char object[20];
-       int retval;
+       int retval = 0;
 
        argc = parse_options(argc, argv, prefix, options,
                             git_notes_remove_usage, 0);
 
-       if (1 < argc) {
-               error(_("too many parameters"));
-               usage_with_options(git_notes_remove_usage, options);
-       }
-
-       object_ref = argc ? argv[0] : "HEAD";
-
-       if (get_sha1(object_ref, object))
-               die(_("Failed to resolve '%s' as a valid ref."), object_ref);
-
        t = init_notes_check("remove");
 
-       retval = remove_note(t, object);
-       if (retval)
-               fprintf(stderr, _("Object %s has no note\n"), sha1_to_hex(object));
-       else {
-               fprintf(stderr, _("Removing note for object %s\n"),
-                       sha1_to_hex(object));
-
-               commit_notes(t, "Notes removed by 'git notes remove'");
+       if (!argc && !from_stdin) {
+               retval = remove_one_note(t, "HEAD", flag);
+       } else {
+               while (*argv) {
+                       retval |= remove_one_note(t, *argv, flag);
+                       argv++;
+               }
        }
+       if (from_stdin) {
+               struct strbuf sb = STRBUF_INIT;
+               while (strbuf_getwholeline(&sb, stdin, '\n') != EOF) {
+                       strbuf_rtrim(&sb);
+                       retval |= remove_one_note(t, sb.buf, flag);
+               }
+               strbuf_release(&sb);
+       }
+       if (!retval)
+               commit_notes(t, "Notes removed by 'git notes remove'");
        free_notes(t);
        return retval;
 }
index e1ba4dc697abd1b2582713744c5a1892ef4a4882..e1a687ad0761e46f6ec95659bb585b9014d4abf4 100644 (file)
@@ -10,6 +10,7 @@
 #include "remote.h"
 #include "transport.h"
 #include "string-list.h"
+#include "sha1-array.h"
 
 static const char receive_pack_usage[] = "git receive-pack <git-dir>";
 
@@ -731,14 +732,23 @@ static int delete_only(struct command *commands)
        return 1;
 }
 
-static void add_one_alternate_ref(const struct ref *ref, void *unused)
+static void add_one_alternate_sha1(const unsigned char sha1[20], void *unused)
 {
-       add_extra_ref(".have", ref->old_sha1, 0);
+       add_extra_ref(".have", sha1, 0);
+}
+
+static void collect_one_alternate_ref(const struct ref *ref, void *data)
+{
+       struct sha1_array *sa = data;
+       sha1_array_append(sa, ref->old_sha1);
 }
 
 static void add_alternate_refs(void)
 {
-       foreach_alt_odb(refs_from_alternate_cb, add_one_alternate_ref);
+       struct sha1_array sa = SHA1_ARRAY_INIT;
+       for_each_alternate_ref(collect_one_alternate_ref, &sa);
+       sha1_array_for_each_unique(&sa, add_one_alternate_sha1, NULL);
+       sha1_array_clear(&sa);
 }
 
 int cmd_receive_pack(int argc, const char **argv, const char *prefix)
index adb1cae4f27ad3993aa2418f7b0fd355d19cdab9..4c19f844a977b6413afcd5df6b4f8bc0875775a8 100644 (file)
@@ -44,6 +44,7 @@ static int is_rev_argument(const char *arg)
                "--branches=",
                "--branches",
                "--header",
+               "--ignore-missing",
                "--max-age=",
                "--max-count=",
                "--min-age=",
index d7850c6309aec0c1a4f640999fb757d7b32ba1b8..f14bc908309c0bb01ec251d71f26b490b294622c 100644 (file)
@@ -99,7 +99,8 @@ static int add_one_path(struct cache_entry *old, const char *path, int len, stru
        fill_stat_cache_info(ce, st);
        ce->ce_mode = ce_mode_from_stat(old, st->st_mode);
 
-       if (index_path(ce->sha1, path, st, !info_only))
+       if (index_path(ce->sha1, path, st,
+                      info_only ? 0 : HASH_WRITE_OBJECT))
                return -1;
        option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
        option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
diff --git a/cache.h b/cache.h
index 28a921dffc144bab15366bbfb0c52ed34a320bed..e11cf6ab1c73ac97c94e76e8c8699d55af95b978 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -518,8 +518,11 @@ struct pathspec {
 extern int init_pathspec(struct pathspec *, const char **);
 extern void free_pathspec(struct pathspec *);
 extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
-extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path, int format_check);
-extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
+
+#define HASH_WRITE_OBJECT 1
+#define HASH_FORMAT_CHECK 2
+extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
+extern int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags);
 extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
 
 #define REFRESH_REALLY         0x0001  /* ignore_valid */
@@ -1037,8 +1040,6 @@ typedef int (*config_fn_t)(const char *, const char *, void *);
 extern int git_default_config(const char *, const char *, void *);
 extern int git_config_from_file(config_fn_t fn, const char *, void *);
 extern void git_config_push_parameter(const char *text);
-extern int git_config_parse_parameter(const char *text);
-extern int git_config_parse_environment(void);
 extern int git_config_from_parameters(config_fn_t fn, void *data);
 extern int git_config(config_fn_t fn, void *);
 extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
index 14feac7fe179069908df6dbc084b2adcc78c9242..9473aed2bbcb91994f166cf66d24459c66ac2fcb 100644 (file)
@@ -127,6 +127,10 @@ extern char *getenv ();
 extern int errno;
 # endif
 
+# ifndef NULL
+#  define NULL 0
+# endif
+
 /* This function doesn't exist on most systems.  */
 
 # if !defined HAVE___STRCHRNUL && !defined _LIBC
index 9d368488893687a7b6986b5ab8eb1154f3ad7df9..e0b3b80d92ece9f37e0e76292bd61ce97271fcce 100644 (file)
--- a/config.c
+++ b/config.c
@@ -20,14 +20,6 @@ static int zlib_compression_seen;
 
 const char *config_exclusive_filename = NULL;
 
-struct config_item {
-       struct config_item *next;
-       char *name;
-       char *value;
-};
-static struct config_item *config_parameters;
-static struct config_item **config_parameters_tail = &config_parameters;
-
 static void lowercase(char *p)
 {
        for (; *p; p++)
@@ -47,9 +39,9 @@ void git_config_push_parameter(const char *text)
        strbuf_release(&env);
 }
 
-int git_config_parse_parameter(const char *text)
+static int git_config_parse_parameter(const char *text,
+                                     config_fn_t fn, void *data)
 {
-       struct config_item *ct;
        struct strbuf tmp = STRBUF_INIT;
        struct strbuf **pair;
        strbuf_addstr(&tmp, text);
@@ -59,22 +51,19 @@ int git_config_parse_parameter(const char *text)
        strbuf_trim(pair[0]);
        if (!pair[0]->len) {
                strbuf_list_free(pair);
-               return -1;
+               return error("bogus config parameter: %s", text);
        }
-       ct = xcalloc(1, sizeof(struct config_item));
-       ct->name = strbuf_detach(pair[0], NULL);
-       if (pair[1]) {
-               strbuf_trim(pair[1]);
-               ct->value = strbuf_detach(pair[1], NULL);
+       lowercase(pair[0]->buf);
+       if (fn(pair[0]->buf, pair[1] ? pair[1]->buf : NULL, data) < 0) {
+               strbuf_list_free(pair);
+               return -1;
        }
        strbuf_list_free(pair);
-       lowercase(ct->name);
-       *config_parameters_tail = ct;
-       config_parameters_tail = &ct->next;
        return 0;
 }
 
-int git_config_parse_environment(void) {
+int git_config_from_parameters(config_fn_t fn, void *data)
+{
        const char *env = getenv(CONFIG_DATA_ENVIRONMENT);
        char *envw;
        const char **argv = NULL;
@@ -92,8 +81,7 @@ int git_config_parse_environment(void) {
        }
 
        for (i = 0; i < nr; i++) {
-               if (git_config_parse_parameter(argv[i]) < 0) {
-                       error("bogus config parameter: %s", argv[i]);
+               if (git_config_parse_parameter(argv[i], fn, data) < 0) {
                        free(argv);
                        free(envw);
                        return -1;
@@ -102,7 +90,7 @@ int git_config_parse_environment(void) {
 
        free(argv);
        free(envw);
-       return 0;
+       return nr > 0;
 }
 
 static int get_next_char(void)
@@ -837,22 +825,6 @@ int git_config_system(void)
        return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
 }
 
-int git_config_from_parameters(config_fn_t fn, void *data)
-{
-       static int loaded_environment;
-       const struct config_item *ct;
-
-       if (!loaded_environment) {
-               if (git_config_parse_environment() < 0)
-                       return -1;
-               loaded_environment = 1;
-       }
-       for (ct = config_parameters; ct; ct = ct->next)
-               if (fn(ct->name, ct->value, data) < 0)
-                       return -1;
-       return 0;
-}
-
 int git_config_early(config_fn_t fn, void *data, const char *repo_config)
 {
        int ret = 0, found = 0;
@@ -882,9 +854,16 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
                found += 1;
        }
 
-       ret += git_config_from_parameters(fn, data);
-       if (config_parameters)
-               found += 1;
+       switch (git_config_from_parameters(fn, data)) {
+       case -1: /* error */
+               ret--;
+               break;
+       case 0: /* found nothing */
+               break;
+       default: /* found at least one item */
+               found++;
+               break;
+       }
 
        return ret == 0 ? found : ret;
 }
index e378534cbd57add4a6c75b8eb45353e8a9322914..f30130b17a22a6227496b135812cd2ac660ed19b 100644 (file)
@@ -61,6 +61,7 @@ NO_INET_PTON=@NO_INET_PTON@
 NO_ICONV=@NO_ICONV@
 OLD_ICONV=@OLD_ICONV@
 NO_REGEX=@NO_REGEX@
+USE_LIBPCRE=@USE_LIBPCRE@
 NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@
 INLINE=@INLINE@
 SOCKLEN_T=@SOCKLEN_T@
index fafd81557c2d3dd17cb1c15c545f3602a3a0c6bf..048a1d4972769184ff857fb7681bc67b2ebdfb99 100644 (file)
@@ -220,6 +220,27 @@ AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)])
 AS_HELP_STRING([],              [ARG can be prefix for openssl library and headers]),\
 GIT_PARSE_WITH(openssl))
 #
+# Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be
+# able to use Perl-compatible regular expressions.
+#
+# Define LIBPCREDIR=/foo/bar if your libpcre header and library files are in
+# /foo/bar/include and /foo/bar/lib directories.
+#
+AC_ARG_WITH(libpcre,
+AS_HELP_STRING([--with-libpcre],[support Perl-compatible regexes (default is NO)])
+AS_HELP_STRING([],           [ARG can be also prefix for libpcre library and headers]),
+if test "$withval" = "no"; then \
+       USE_LIBPCRE=; \
+elif test "$withval" = "yes"; then \
+       USE_LIBPCRE=YesPlease; \
+else
+       USE_LIBPCRE=YesPlease; \
+       LIBPCREDIR=$withval; \
+       AC_MSG_NOTICE([Setting LIBPCREDIR to $withval]); \
+       GIT_CONF_APPEND_LINE(LIBPCREDIR=$withval); \
+fi \
+)
+#
 # Define NO_CURL if you do not have curl installed.  git-http-pull and
 # git-http-push are not built, and you cannot use http:// and https://
 # transports.
@@ -434,6 +455,25 @@ GIT_UNSTASH_FLAGS($OPENSSLDIR)
 AC_SUBST(NEEDS_SSL_WITH_CRYPTO)
 AC_SUBST(NO_OPENSSL)
 
+#
+# Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be
+# able to use Perl-compatible regular expressions.
+#
+
+if test -n "$USE_LIBPCRE"; then
+
+GIT_STASH_FLAGS($LIBPCREDIR)
+
+AC_CHECK_LIB([pcre], [pcre_version],
+[USE_LIBPCRE=YesPlease],
+[USE_LIBPCRE=])
+
+GIT_UNSTASH_FLAGS($LIBPCREDIR)
+
+AC_SUBST(USE_LIBPCRE)
+
+fi
+
 #
 # 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://
index bb8d7d0878994eb3d53163c330de44d8c0d48b3f..b36290fa6015d9b66dc6a5891ff12f85f279a1c3 100755 (executable)
@@ -1441,8 +1441,9 @@ _git_grep ()
                __gitcomp "
                        --cached
                        --text --ignore-case --word-regexp --invert-match
-                       --full-name
+                       --full-name --line-number
                        --extended-regexp --basic-regexp --fixed-strings
+                       --perl-regexp
                        --files-with-matches --name-only
                        --files-without-match
                        --max-depth
diff --git a/fsck.c b/fsck.c
index 6f266c1ea4b8df503c211170741fa5b490a6a02a..60bd4bbf6a2206e2b1991093ea5d84be7f338f73 100644 (file)
--- a/fsck.c
+++ b/fsck.c
@@ -349,7 +349,7 @@ int fsck_error_function(struct object *obj, int type, const char *fmt, ...)
        va_list ap;
        struct strbuf sb = STRBUF_INIT;
 
-       strbuf_addf(&sb, "object %s:", obj->sha1?sha1_to_hex(obj->sha1):"(null)");
+       strbuf_addf(&sb, "object %s:", sha1_to_hex(obj->sha1));
 
        va_start(ap, fmt);
        strbuf_vaddf(&sb, fmt, ap);
index aa16b8356507e4e669ee5c4cb4b5a667942d559e..94e26ed5e8dcf84c4f238c76b6c508dc84d0b7ea 100644 (file)
@@ -140,6 +140,13 @@ cd_to_toplevel () {
        }
 }
 
+require_work_tree_exists () {
+       if test "z$(git rev-parse --is-bare-repository)" != zfalse
+       then
+               die "fatal: $0 cannot be used without a working tree."
+       fi
+}
+
 require_work_tree () {
        test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true ||
        die "fatal: $0 cannot be used without a working tree."
index bf110e9cb77a0e9930c427408e18d323db2c8916..d189a24c71c44806a9c1381e2a8e5993269e568a 100755 (executable)
@@ -495,7 +495,7 @@ cmd_update()
                                # Run fetch only if $sha1 isn't present or it
                                # is not reachable from a ref.
                                (clear_local_git_env; cd "$path" &&
-                                       ((rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) &&
+                                       ( (rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) &&
                                         test -z "$rev") || git-fetch)) ||
                                die "Unable to fetch in submodule path '$path'"
                        fi
diff --git a/git.c b/git.c
index a5ef3c69ff58434e521d2c42f1b2fa275b872828..89721d420a09bfc8eb6a2d4010f86f5685cfd255 100644 (file)
--- a/git.c
+++ b/git.c
@@ -66,7 +66,7 @@ static void commit_pager_choice(void) {
 
 static int handle_options(const char ***argv, int *argc, int *envchanged)
 {
-       int handled = 0;
+       const char **orig_argv = *argv;
 
        while (*argc > 0) {
                const char *cmd = (*argv)[0];
@@ -122,7 +122,6 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
                                *envchanged = 1;
                        (*argv)++;
                        (*argc)--;
-                       handled++;
                } else if (!prefixcmp(cmd, "--git-dir=")) {
                        setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
                        if (envchanged)
@@ -162,9 +161,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
 
                (*argv)++;
                (*argc)--;
-               handled++;
        }
-       return handled;
+       return (*argv) - orig_argv;
 }
 
 static int handle_alias(int *argcp, const char ***argv)
index 0a6ac00631ed8eac5c20248a345074e4b8ff3707..5d20515fba9251123e29be623178047d4131fc01 100644 (file)
@@ -86,7 +86,7 @@ ifndef V
 endif
 endif
 
-all:: gitweb.cgi
+all:: gitweb.cgi static/gitweb.js
 
 GITWEB_PROGRAMS = gitweb.cgi
 
@@ -112,6 +112,18 @@ endif
 
 GITWEB_FILES += static/git-logo.png static/git-favicon.png
 
+# JavaScript files that are composed (concatenated) to form gitweb.js
+#
+# js/lib/common-lib.js should be always first, then js/lib/*.js,
+# then the rest of files; js/gitweb.js should be last (if it exists)
+GITWEB_JSLIB_FILES += static/js/lib/common-lib.js
+GITWEB_JSLIB_FILES += static/js/lib/datetime.js
+GITWEB_JSLIB_FILES += static/js/lib/cookies.js
+GITWEB_JSLIB_FILES += static/js/javascript-detection.js
+GITWEB_JSLIB_FILES += static/js/adjust-timezone.js
+GITWEB_JSLIB_FILES += static/js/blame_incremental.js
+
+
 GITWEB_REPLACE = \
        -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
        -e 's|++GIT_BINDIR++|$(bindir)|g' \
@@ -146,6 +158,11 @@ gitweb.cgi: gitweb.perl GITWEB-BUILD-OPTIONS
        chmod +x $@+ && \
        mv $@+ $@
 
+static/gitweb.js: $(GITWEB_JSLIB_FILES)
+       $(QUIET_GEN)$(RM) $@ $@+ && \
+       cat $^ >$@+ && \
+       mv $@+ $@
+
 ### Testing rules
 
 test:
index a92bde7f1430043e5955c61eba287a22d4810d22..a3a697bf55a1fc0764a954bf2a1cd4914d6550d1 100644 (file)
@@ -207,6 +207,15 @@ not include variables usually directly set during build):
    full description is available as 'title' attribute (usually shown on
    mouseover).  By default set to 25, which might be too small if you
    use long project descriptions.
+ * $projects_list_group_categories
+   Enables the grouping of projects by category on the project list page.
+   The category of a project is determined by the $GIT_DIR/category
+   file or the 'gitweb.category' variable in its repository configuration.
+   Disabled by default.
+ * $project_list_default_category
+   Default category for projects for which none is specified.  If set
+   to the empty string, such projects will remain uncategorized and
+   listed at the top, above categorized projects.
  * @git_base_url_list
    List of git base URLs used for URL to where fetch project from, shown
    in project summary page.  Full URL is "$git_base_url/$project".
@@ -314,6 +323,13 @@ You can use the following files in repository:
    from the template during repository creation. You can use the
    gitweb.description repo configuration variable, but the file takes
    precedence.
+ * category (or gitweb.category)
+   Singe line category of a project, used to group projects if
+   $projects_list_group_categories is enabled. By default (file and
+   configuration variable absent), uncategorized projects are put in
+   the $project_list_default_category category. You can use the
+   gitweb.category repo configuration variable, but the file takes
+   precedence.
  * cloneurl (or multiple-valued gitweb.url)
    File with repository URL (used for clone and fetch), one per line.
    Displayed in the project summary page. You can use multiple-valued
index 66eadb4ab2939b193fcf84d8d805e0b29ad5e9d2..240dd4701cc7f313b51754b0ffc7ca4a11ab0b7a 100755 (executable)
@@ -115,6 +115,14 @@ sub evaluate_uri {
 # the width (in characters) of the projects list "Description" column
 our $projects_list_description_width = 25;
 
+# group projects by category on the projects list
+# (enabled if this variable evaluates to true)
+our $projects_list_group_categories = 0;
+
+# default category if none specified
+# (leave the empty string for no category)
+our $project_list_default_category = "";
+
 # default order of projects list
 # valid values are none, project, descr, owner, and age
 our $default_projects_order = "project";
@@ -483,6 +491,18 @@ sub evaluate_uri {
                'override' => 0,
                'default' => [0]},
 
+       # Enable and configure ability to change common timezone for dates
+       # in gitweb output via JavaScript.  Enabled by default.
+       # Project specific override is not supported.
+       'javascript-timezone' => {
+               'override' => 0,
+               'default' => [
+                       'local',     # default timezone: 'utc', 'local', or '(-|+)HHMM' format,
+                                    # or undef to turn off this feature
+                       'gitweb_tz', # name of cookie where to store selected timezone
+                       'datetime',  # CSS class used to mark up dates for manipulation
+               ]},
+
        # Syntax highlighting support. This is based on Daniel Svensson's
        # and Sham Chukoury's work in gitweb-xmms2.git.
        # It requires the 'highlight' program present in $PATH,
@@ -623,18 +643,30 @@ sub filter_snapshot_fmts {
 # if it is true then gitweb config would be run for each request.
 our $per_request_config = 1;
 
+# read and parse gitweb config file given by its parameter.
+# returns true on success, false on recoverable error, allowing
+# to chain this subroutine, using first file that exists.
+# dies on errors during parsing config file, as it is unrecoverable.
+sub read_config_file {
+       my $filename = shift;
+       return unless defined $filename;
+       # die if there are errors parsing config file
+       if (-e $filename) {
+               do $filename;
+               die $@ if $@;
+               return 1;
+       }
+       return;
+}
+
 our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM);
 sub evaluate_gitweb_config {
        our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
        our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
-       # die if there are errors parsing config file
-       if (-e $GITWEB_CONFIG) {
-               do $GITWEB_CONFIG;
-               die $@ if $@;
-       } elsif (-e $GITWEB_CONFIG_SYSTEM) {
-               do $GITWEB_CONFIG_SYSTEM;
-               die $@ if $@;
-       }
+
+       # use first config file that exists
+       read_config_file($GITWEB_CONFIG) or
+       read_config_file($GITWEB_CONFIG_SYSTEM);
 }
 
 # Get loadavg of system, to compare against $maxload.
@@ -2562,20 +2594,34 @@ sub git_get_path_by_hash {
 ## ......................................................................
 ## git utility functions, directly accessing git repository
 
-sub git_get_project_description {
-       my $path = shift;
+# get the value of config variable either from file named as the variable
+# itself in the repository ($GIT_DIR/$name file), or from gitweb.$name
+# configuration variable in the repository config file.
+sub git_get_file_or_project_config {
+       my ($path, $name) = @_;
 
        $git_dir = "$projectroot/$path";
-       open my $fd, '<', "$git_dir/description"
-               or return git_get_project_config('description');
-       my $descr = <$fd>;
+       open my $fd, '<', "$git_dir/$name"
+               or return git_get_project_config($name);
+       my $conf = <$fd>;
        close $fd;
-       if (defined $descr) {
-               chomp $descr;
+       if (defined $conf) {
+               chomp $conf;
        }
-       return $descr;
+       return $conf;
+}
+
+sub git_get_project_description {
+       my $path = shift;
+       return git_get_file_or_project_config($path, 'description');
+}
+
+sub git_get_project_category {
+       my $path = shift;
+       return git_get_file_or_project_config($path, 'category');
 }
 
+
 # supported formats:
 # * $GIT_DIR/ctags/<tagname> file (in 'ctags' subdirectory)
 #   - if its contents is a number, use it as tag weight,
@@ -3863,9 +3909,20 @@ sub git_footer_html {
                      qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
                      qq!           "!. href() .qq!");\n!.
                      qq!</script>\n!;
-       } elsif (gitweb_check_feature('javascript-actions')) {
+       } else {
+               my ($jstimezone, $tz_cookie, $datetime_class) =
+                       gitweb_get_feature('javascript-timezone');
+
                print qq!<script type="text/javascript">\n!.
-                     qq!window.onload = fixLinks;\n!.
+                     qq!window.onload = function () {\n!;
+               if (gitweb_check_feature('javascript-actions')) {
+                       print qq!       fixLinks();\n!;
+               }
+               if ($jstimezone && $tz_cookie && $datetime_class) {
+                       print qq!       var tz_cookie = { name: '$tz_cookie', expires: 14, path: '/' };\n!. # in days
+                             qq!       onloadTZSetup('$jstimezone', tz_cookie, '$datetime_class');\n!;
+               }
+               print qq!};\n!.
                      qq!</script>\n!;
        }
 
@@ -4069,22 +4126,25 @@ sub git_print_section {
        print $cgi->end_div;
 }
 
-sub print_local_time {
-       print format_local_time(@_);
-}
+sub format_timestamp_html {
+       my $date = shift;
+       my $strtime = $date->{'rfc2822'};
 
-sub format_local_time {
-       my $localtime = '';
-       my %date = @_;
-       if ($date{'hour_local'} < 6) {
-               $localtime .= sprintf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
-                       $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
-       } else {
-               $localtime .= sprintf(" (%02d:%02d %s)",
-                       $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
+       my (undef, undef, $datetime_class) =
+               gitweb_get_feature('javascript-timezone');
+       if ($datetime_class) {
+               $strtime = qq!<span class="$datetime_class">$strtime</span>!;
+       }
+
+       my $localtime_format = '(%02d:%02d %s)';
+       if ($date->{'hour_local'} < 6) {
+               $localtime_format = '(<span class="atnight">%02d:%02d</span> %s)';
        }
+       $strtime .= ' ' .
+                   sprintf($localtime_format,
+                           $date->{'hour_local'}, $date->{'minute_local'}, $date->{'tz_local'});
 
-       return $localtime;
+       return $strtime;
 }
 
 # Outputs the author name and date in long form
@@ -4097,10 +4157,9 @@ sub git_print_authorship {
        my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
        print "<$tag class=\"author_date\">" .
              format_search_author($author, "author", esc_html($author)) .
-             " [$ad{'rfc2822'}";
-       print_local_time(%ad) if ($opts{-localtime});
-       print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1)
-                 . "</$tag>\n";
+             " [".format_timestamp_html(\%ad)."]".
+             git_get_avatar($co->{'author_email'}, -pad_before => 1) .
+             "</$tag>\n";
 }
 
 # Outputs table rows containing the full author or committer information,
@@ -4117,16 +4176,16 @@ sub git_print_authorship_rows {
                my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"});
                print "<tr><td>$who</td><td>" .
                      format_search_author($co->{"${who}_name"}, $who,
-                              esc_html($co->{"${who}_name"})) . " " .
+                                          esc_html($co->{"${who}_name"})) . " " .
                      format_search_author($co->{"${who}_email"}, $who,
-                              esc_html("<" . $co->{"${who}_email"} . ">")) .
+                                          esc_html("<" . $co->{"${who}_email"} . ">")) .
                      "</td><td rowspan=\"2\">" .
                      git_get_avatar($co->{"${who}_email"}, -size => 'double') .
                      "</td></tr>\n" .
                      "<tr>" .
-                     "<td></td><td> $wd{'rfc2822'}";
-               print_local_time(%wd);
-               print "</td>" .
+                     "<td></td><td>" .
+                     format_timestamp_html(\%wd) .
+                     "</td>" .
                      "</tr>\n";
        }
 }
@@ -4869,8 +4928,9 @@ sub git_patchset_body {
 
 # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
-# fills project list info (age, description, owner, forks) for each
-# project in the list, removing invalid projects from returned list
+# fills project list info (age, description, owner, category, forks)
+# for each project in the list, removing invalid projects from
+# returned list
 # NOTE: modifies $projlist, but does not remove entries from it
 sub fill_project_list_info {
        my $projlist = shift;
@@ -4896,6 +4956,12 @@ sub fill_project_list_info {
                if ($show_ctags) {
                        $pr->{'ctags'} = git_get_project_ctags($pr->{'path'});
                }
+               if ($projects_list_group_categories && !defined $pr->{'category'}) {
+                       my $cat = git_get_project_category($pr->{'path'}) ||
+                                                          $project_list_default_category;
+                       $pr->{'category'} = to_utf8($cat);
+               }
+
                push @projects, $pr;
        }
 
@@ -4923,6 +4989,23 @@ sub sort_projects_list {
        return @projects;
 }
 
+# returns a hash of categories, containing the list of project
+# belonging to each category
+sub build_projlist_by_category {
+       my ($projlist, $from, $to) = @_;
+       my %categories;
+
+       $from = 0 unless defined $from;
+       $to = $#$projlist if (!defined $to || $#$projlist < $to);
+
+       for (my $i = $from; $i <= $to; $i++) {
+               my $pr = $projlist->[$i];
+               push @{$categories{ $pr->{'category'} }}, $pr;
+       }
+
+       return wantarray ? %categories : \%categories;
+}
+
 # print 'sort by' <th> element, generating 'sort by $name' replay link
 # if that order is not selected
 sub print_sort_th {
@@ -4946,6 +5029,55 @@ sub format_sort_th {
        return $sort_th;
 }
 
+sub git_project_list_rows {
+       my ($projlist, $from, $to, $check_forks) = @_;
+
+       $from = 0 unless defined $from;
+       $to = $#$projlist if (!defined $to || $#$projlist < $to);
+
+       my $alternate = 1;
+       for (my $i = $from; $i <= $to; $i++) {
+               my $pr = $projlist->[$i];
+
+               if ($alternate) {
+                       print "<tr class=\"dark\">\n";
+               } else {
+                       print "<tr class=\"light\">\n";
+               }
+               $alternate ^= 1;
+
+               if ($check_forks) {
+                       print "<td>";
+                       if ($pr->{'forks'}) {
+                               my $nforks = scalar @{$pr->{'forks'}};
+                               if ($nforks > 0) {
+                                       print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks"),
+                                                      -title => "$nforks forks"}, "+");
+                               } else {
+                                       print $cgi->span({-title => "$nforks forks"}, "+");
+                               }
+                       }
+                       print "</td>\n";
+               }
+               print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
+                                       -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
+                     "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
+                                       -class => "list", -title => $pr->{'descr_long'}},
+                                       esc_html($pr->{'descr'})) . "</td>\n" .
+                     "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
+               print "<td class=\"". age_class($pr->{'age'}) . "\">" .
+                     (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" .
+                     "<td class=\"link\">" .
+                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary")   . " | " .
+                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
+                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
+                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
+                     ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') .
+                     "</td>\n" .
+                     "</tr>\n";
+       }
+}
+
 sub git_project_list_body {
        # actually uses global variable $project
        my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
@@ -5001,47 +5133,27 @@ sub git_project_list_body {
                print "<th></th>\n" . # for links
                      "</tr>\n";
        }
-       my $alternate = 1;
-       for (my $i = $from; $i <= $to; $i++) {
-               my $pr = $projects[$i];
 
-               if ($alternate) {
-                       print "<tr class=\"dark\">\n";
-               } else {
-                       print "<tr class=\"light\">\n";
-               }
-               $alternate ^= 1;
-
-               if ($check_forks) {
-                       print "<td>";
-                       if ($pr->{'forks'}) {
-                               my $nforks = scalar @{$pr->{'forks'}};
-                               if ($nforks > 0) {
-                                       print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks"),
-                                                      -title => "$nforks forks"}, "+");
-                               } else {
-                                       print $cgi->span({-title => "$nforks forks"}, "+");
+       if ($projects_list_group_categories) {
+               # only display categories with projects in the $from-$to window
+               @projects = sort {$a->{'category'} cmp $b->{'category'}} @projects[$from..$to];
+               my %categories = build_projlist_by_category(\@projects, $from, $to);
+               foreach my $cat (sort keys %categories) {
+                       unless ($cat eq "") {
+                               print "<tr>\n";
+                               if ($check_forks) {
+                                       print "<td></td>\n";
                                }
+                               print "<td class=\"category\" colspan=\"5\">".esc_html($cat)."</td>\n";
+                               print "</tr>\n";
                        }
-                       print "</td>\n";
+
+                       git_project_list_rows($categories{$cat}, undef, undef, $check_forks);
                }
-               print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
-                                       -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
-                     "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
-                                       -class => "list", -title => $pr->{'descr_long'}},
-                                       esc_html($pr->{'descr'})) . "</td>\n" .
-                     "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
-               print "<td class=\"". age_class($pr->{'age'}) . "\">" .
-                     (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" .
-                     "<td class=\"link\">" .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary")   . " | " .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
-                     ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') .
-                     "</td>\n" .
-                     "</tr>\n";
+       } else {
+               git_project_list_rows(\@projects, $from, $to, $check_forks);
        }
+
        if (defined $extra) {
                print "<tr>\n";
                if ($check_forks) {
@@ -5561,7 +5673,8 @@ sub git_summary {
              "<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
              "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
        if (defined $cd{'rfc2822'}) {
-               print "<tr id=\"metadata_lchange\"><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n";
+               print "<tr id=\"metadata_lchange\"><td>last change</td>" .
+                     "<td>".format_timestamp_html(\%cd)."</td></tr>\n";
        }
 
        # use per project git URL list in $projectroot/$project/cloneurl
index 79d7eebba797f3bd80a639f6ca0835e15016762d..7d88509208417e4b1222629002ea339ecc32526e 100644 (file)
@@ -295,6 +295,13 @@ td.current_head {
        text-decoration: underline;
 }
 
+td.category {
+       background-color: #d9d8d1;
+       border-top: 1px solid #000000;
+       border-left: 1px solid #000000;
+       font-weight: bold;
+}
+
 table.diff_tree span.file_status.new {
        color: #008000;
 }
@@ -579,6 +586,39 @@ div.remote {
        display: inline-block;
 }
 
+/* JavaScript-based timezone manipulation */
+
+.popup { /* timezone selection UI */
+       position: absolute;
+       /* "top: 0; right: 0;" would be better, if not for bugs in browsers */
+       top: 0; left: 0;
+       border: 1px solid;
+       padding: 2px;
+       background-color: #f0f0f0;
+       font-style: normal;
+       color: #000000;
+       cursor: auto;
+}
+
+.close-button { /* close timezone selection UI without selecting */
+       /* float doesn't work within absolutely positioned container,
+        * if width of container is not set explicitly */
+       /* float: right; */
+       position: absolute;
+       top: 0px; right: 0px;
+       border:  1px solid green;
+       margin:  1px 1px 1px 1px;
+       padding-bottom: 2px;
+       width:     12px;
+       height:    10px;
+       font-size:  9px;
+       font-weight: bold;
+       text-align: center;
+       background-color: #fff0f0;
+       cursor: pointer;
+}
+
+
 /* Style definition generated by highlight 2.4.5, http://www.andre-simon.de/ */
 
 /* Highlighting theme definition: */
diff --git a/gitweb/static/gitweb.js b/gitweb/static/gitweb.js
deleted file mode 100644 (file)
index 40ec084..0000000
+++ /dev/null
@@ -1,889 +0,0 @@
-// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>
-//               2007, Petr Baudis <pasky@suse.cz>
-//          2008-2009, Jakub Narebski <jnareb@gmail.com>
-
-/**
- * @fileOverview JavaScript code for gitweb (git web interface).
- * @license GPLv2 or later
- */
-
-/* ============================================================ */
-/* functions for generic gitweb actions and views */
-
-/**
- * used to check if link has 'js' query parameter already (at end),
- * and other reasons to not add 'js=1' param at the end of link
- * @constant
- */
-var jsExceptionsRe = /[;?]js=[01]$/;
-
-/**
- * Add '?js=1' or ';js=1' to the end of every link in the document
- * that doesn't have 'js' query parameter set already.
- *
- * Links with 'js=1' lead to JavaScript version of given action, if it
- * exists (currently there is only 'blame_incremental' for 'blame')
- *
- * @globals jsExceptionsRe
- */
-function fixLinks() {
-       var allLinks = document.getElementsByTagName("a") || document.links;
-       for (var i = 0, len = allLinks.length; i < len; i++) {
-               var link = allLinks[i];
-               if (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/;
-                       link.href +=
-                               (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1';
-               }
-       }
-}
-
-
-/* ============================================================ */
-
-/*
- * This code uses DOM methods instead of (nonstandard) innerHTML
- * to modify page.
- *
- * innerHTML is non-standard IE extension, though supported by most
- * browsers; however Firefox up to version 1.5 didn't implement it in
- * a strict mode (application/xml+xhtml mimetype).
- *
- * Also my simple benchmarks show that using elem.firstChild.data =
- * 'content' is slightly faster than elem.innerHTML = 'content'.  It
- * is however more fragile (text element fragment must exists), and
- * less feature-rich (we cannot add HTML).
- *
- * Note that DOM 2 HTML is preferred over generic DOM 2 Core; the
- * equivalent using DOM 2 Core is usually shown in comments.
- */
-
-
-/* ============================================================ */
-/* generic utility functions */
-
-
-/**
- * pad number N with nonbreakable spaces on the left, to WIDTH characters
- * example: padLeftStr(12, 3, '\u00A0') == '\u00A012'
- *          ('\u00A0' is nonbreakable space)
- *
- * @param {Number|String} input: number to pad
- * @param {Number} width: visible width of output
- * @param {String} str: string to prefix to string, e.g. '\u00A0'
- * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR
- */
-function padLeftStr(input, width, str) {
-       var prefix = '';
-
-       width -= input.toString().length;
-       while (width > 0) {
-               prefix += str;
-               width--;
-       }
-       return prefix + input;
-}
-
-/**
- * Pad INPUT on the left to SIZE width, using given padding character CH,
- * for example padLeft('a', 3, '_') is '__a'.
- *
- * @param {String} input: input value converted to string.
- * @param {Number} width: desired length of output.
- * @param {String} ch: single character to prefix to string.
- *
- * @returns {String} Modified string, at least SIZE length.
- */
-function padLeft(input, width, ch) {
-       var s = input + "";
-       while (s.length < width) {
-               s = ch + s;
-       }
-       return s;
-}
-
-/**
- * Create XMLHttpRequest object in cross-browser way
- * @returns XMLHttpRequest object, or null
- */
-function createRequestObject() {
-       try {
-               return new XMLHttpRequest();
-       } catch (e) {}
-       try {
-               return window.createRequest();
-       } catch (e) {}
-       try {
-               return new ActiveXObject("Msxml2.XMLHTTP");
-       } catch (e) {}
-       try {
-               return new ActiveXObject("Microsoft.XMLHTTP");
-       } catch (e) {}
-
-       return null;
-}
-
-
-/* ============================================================ */
-/* utility/helper functions (and variables) */
-
-var xhr;        // XMLHttpRequest object
-var projectUrl; // partial query + separator ('?' or ';')
-
-// 'commits' is an associative map. It maps SHA1s to Commit objects.
-var commits = {};
-
-/**
- * constructor for Commit objects, used in 'blame'
- * @class Represents a blamed commit
- * @param {String} sha1: SHA-1 identifier of a commit
- */
-function Commit(sha1) {
-       if (this instanceof Commit) {
-               this.sha1 = sha1;
-               this.nprevious = 0; /* number of 'previous', effective parents */
-       } else {
-               return new Commit(sha1);
-       }
-}
-
-/* ............................................................ */
-/* progress info, timing, error reporting */
-
-var blamedLines = 0;
-var totalLines  = '???';
-var div_progress_bar;
-var div_progress_info;
-
-/**
- * Detects how many lines does a blamed file have,
- * This information is used in progress info
- *
- * @returns {Number|String} Number of lines in file, or string '...'
- */
-function countLines() {
-       var table =
-               document.getElementById('blame_table') ||
-               document.getElementsByTagName('table')[0];
-
-       if (table) {
-               return table.getElementsByTagName('tr').length - 1; // for header
-       } else {
-               return '...';
-       }
-}
-
-/**
- * update progress info and length (width) of progress bar
- *
- * @globals div_progress_info, div_progress_bar, blamedLines, totalLines
- */
-function updateProgressInfo() {
-       if (!div_progress_info) {
-               div_progress_info = document.getElementById('progress_info');
-       }
-       if (!div_progress_bar) {
-               div_progress_bar = document.getElementById('progress_bar');
-       }
-       if (!div_progress_info && !div_progress_bar) {
-               return;
-       }
-
-       var percentage = Math.floor(100.0*blamedLines/totalLines);
-
-       if (div_progress_info) {
-               div_progress_info.firstChild.data  = blamedLines + ' / ' + totalLines +
-                       ' (' + padLeftStr(percentage, 3, '\u00A0') + '%)';
-       }
-
-       if (div_progress_bar) {
-               //div_progress_bar.setAttribute('style', 'width: '+percentage+'%;');
-               div_progress_bar.style.width = percentage + '%';
-       }
-}
-
-
-var t_interval_server = '';
-var cmds_server = '';
-var t0 = new Date();
-
-/**
- * write how much it took to generate data, and to run script
- *
- * @globals t0, t_interval_server, cmds_server
- */
-function writeTimeInterval() {
-       var info_time = document.getElementById('generating_time');
-       if (!info_time || !t_interval_server) {
-               return;
-       }
-       var t1 = new Date();
-       info_time.firstChild.data += ' + (' +
-               t_interval_server + ' sec server blame_data / ' +
-               (t1.getTime() - t0.getTime())/1000 + ' sec client JavaScript)';
-
-       var info_cmds = document.getElementById('generating_cmd');
-       if (!info_time || !cmds_server) {
-               return;
-       }
-       info_cmds.firstChild.data += ' + ' + cmds_server;
-}
-
-/**
- * show an error message alert to user within page (in prohress info area)
- * @param {String} str: plain text error message (no HTML)
- *
- * @globals div_progress_info
- */
-function errorInfo(str) {
-       if (!div_progress_info) {
-               div_progress_info = document.getElementById('progress_info');
-       }
-       if (div_progress_info) {
-               div_progress_info.className = 'error';
-               div_progress_info.firstChild.data = str;
-       }
-}
-
-/* ............................................................ */
-/* coloring rows during blame_data (git blame --incremental) run */
-
-/**
- * used to extract N from 'colorN', where N is a number,
- * @constant
- */
-var colorRe = /\bcolor([0-9]*)\b/;
-
-/**
- * return N if <tr class="colorN">, otherwise return null
- * (some browsers require CSS class names to begin with letter)
- *
- * @param {HTMLElement} tr: table row element to check
- * @param {String} tr.className: 'class' attribute of tr element
- * @returns {Number|null} N if tr.className == 'colorN', otherwise null
- *
- * @globals colorRe
- */
-function getColorNo(tr) {
-       if (!tr) {
-               return null;
-       }
-       var className = tr.className;
-       if (className) {
-               var match = colorRe.exec(className);
-               if (match) {
-                       return parseInt(match[1], 10);
-               }
-       }
-       return null;
-}
-
-var colorsFreq = [0, 0, 0];
-/**
- * return one of given possible colors (curently least used one)
- * example: chooseColorNoFrom(2, 3) returns 2 or 3
- *
- * @param {Number[]} arguments: one or more numbers
- *        assumes that  1 <= arguments[i] <= colorsFreq.length
- * @returns {Number} Least used color number from arguments
- * @globals colorsFreq
- */
-function chooseColorNoFrom() {
-       // choose the color which is least used
-       var colorNo = arguments[0];
-       for (var i = 1; i < arguments.length; i++) {
-               if (colorsFreq[arguments[i]-1] < colorsFreq[colorNo-1]) {
-                       colorNo = arguments[i];
-               }
-       }
-       colorsFreq[colorNo-1]++;
-       return colorNo;
-}
-
-/**
- * given two neigbour <tr> elements, find color which would be different
- * from color of both of neighbours; used to 3-color blame table
- *
- * @param {HTMLElement} tr_prev
- * @param {HTMLElement} tr_next
- * @returns {Number} color number N such that
- * colorN != tr_prev.className && colorN != tr_next.className
- */
-function findColorNo(tr_prev, tr_next) {
-       var color_prev = getColorNo(tr_prev);
-       var color_next = getColorNo(tr_next);
-
-
-       // neither of neighbours has color set
-       // THEN we can use any of 3 possible colors
-       if (!color_prev && !color_next) {
-               return chooseColorNoFrom(1,2,3);
-       }
-
-       // either both neighbours have the same color,
-       // or only one of neighbours have color set
-       // THEN we can use any color except given
-       var color;
-       if (color_prev === color_next) {
-               color = color_prev; // = color_next;
-       } else if (!color_prev) {
-               color = color_next;
-       } else if (!color_next) {
-               color = color_prev;
-       }
-       if (color) {
-               return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1);
-       }
-
-       // neighbours have different colors
-       // THEN there is only one color left
-       return (3 - ((color_prev + color_next) % 3));
-}
-
-/* ............................................................ */
-/* coloring rows like 'blame' after 'blame_data' finishes */
-
-/**
- * returns true if given row element (tr) is first in commit group
- * to be used only after 'blame_data' finishes (after processing)
- *
- * @param {HTMLElement} tr: table row
- * @returns {Boolean} true if TR is first in commit group
- */
-function isStartOfGroup(tr) {
-       return tr.firstChild.className === 'sha1';
-}
-
-/**
- * change colors to use zebra coloring (2 colors) instead of 3 colors
- * concatenate neighbour commit groups belonging to the same commit
- *
- * @globals colorRe
- */
-function fixColorsAndGroups() {
-       var colorClasses = ['light', 'dark'];
-       var linenum = 1;
-       var tr, prev_group;
-       var colorClass = 0;
-       var table =
-               document.getElementById('blame_table') ||
-               document.getElementsByTagName('table')[0];
-
-       while ((tr = document.getElementById('l'+linenum))) {
-       // index origin is 0, which is table header; start from 1
-       //while ((tr = table.rows[linenum])) { // <- it is slower
-               if (isStartOfGroup(tr, linenum, document)) {
-                       if (prev_group &&
-                           prev_group.firstChild.firstChild.href ===
-                                   tr.firstChild.firstChild.href) {
-                               // we have to concatenate groups
-                               var prev_rows = prev_group.firstChild.rowSpan || 1;
-                               var curr_rows =         tr.firstChild.rowSpan || 1;
-                               prev_group.firstChild.rowSpan = prev_rows + curr_rows;
-                               //tr.removeChild(tr.firstChild);
-                               tr.deleteCell(0); // DOM2 HTML way
-                       } else {
-                               colorClass = (colorClass + 1) % 2;
-                               prev_group = tr;
-                       }
-               }
-               var tr_class = tr.className;
-               tr.className = tr_class.replace(colorRe, colorClasses[colorClass]);
-               linenum++;
-       }
-}
-
-/* ............................................................ */
-/* time and data */
-
-/**
- * used to extract hours and minutes from timezone info, e.g '-0900'
- * @constant
- */
-var tzRe = /^([+-])([0-9][0-9])([0-9][0-9])$/;
-
-/**
- * convert numeric timezone +/-ZZZZ to offset from UTC in seconds
- *
- * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM'
- * @returns {Number} offset from UTC in seconds for timezone
- *
- * @globals tzRe
- */
-function timezoneOffset(timezoneInfo) {
-       var match = tzRe.exec(timezoneInfo);
-       var tz_sign = (match[1] === '-' ? -1 : +1);
-       var tz_hour = parseInt(match[2],10);
-       var tz_min  = parseInt(match[3],10);
-
-       return tz_sign*(((tz_hour*60) + tz_min)*60);
-}
-
-/**
- * return date in local time formatted in iso-8601 like format
- * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200'
- *
- * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC'
- * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM'
- * @returns {String} date in local time in iso-8601 like format
- */
-function formatDateISOLocal(epoch, timezoneInfo) {
-       // date corrected by timezone
-       var localDate = new Date(1000 * (epoch +
-               timezoneOffset(timezoneInfo)));
-       var localDateStr = // e.g. '2005-08-07'
-               localDate.getUTCFullYear()                 + '-' +
-               padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' +
-               padLeft(localDate.getUTCDate(),    2, '0');
-       var localTimeStr = // e.g. '21:49:46'
-               padLeft(localDate.getUTCHours(),   2, '0') + ':' +
-               padLeft(localDate.getUTCMinutes(), 2, '0') + ':' +
-               padLeft(localDate.getUTCSeconds(), 2, '0');
-
-       return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo;
-}
-
-/* ............................................................ */
-/* unquoting/unescaping filenames */
-
-/**#@+
- * @constant
- */
-var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g;
-var octEscRe = /^[0-7]{1,3}$/;
-var maybeQuotedRe = /^\"(.*)\"$/;
-/**#@-*/
-
-/**
- * unquote maybe git-quoted filename
- * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a   a'
- *
- * @param {String} str: git-quoted string
- * @returns {String} Unquoted and unescaped string
- *
- * @globals escCodeRe, octEscRe, maybeQuotedRe
- */
-function unquote(str) {
-       function unq(seq) {
-               var es = {
-                       // character escape codes, aka escape sequences (from C)
-                       // replacements are to some extent JavaScript specific
-                       t: "\t",   // tab            (HT, TAB)
-                       n: "\n",   // newline        (NL)
-                       r: "\r",   // return         (CR)
-                       f: "\f",   // form feed      (FF)
-                       b: "\b",   // backspace      (BS)
-                       a: "\x07", // alarm (bell)   (BEL)
-                       e: "\x1B", // escape         (ESC)
-                       v: "\v"    // vertical tab   (VT)
-               };
-
-               if (seq.search(octEscRe) !== -1) {
-                       // octal char sequence
-                       return String.fromCharCode(parseInt(seq, 8));
-               } else if (seq in es) {
-                       // C escape sequence, aka character escape code
-                       return es[seq];
-               }
-               // quoted ordinary character
-               return seq;
-       }
-
-       var match = str.match(maybeQuotedRe);
-       if (match) {
-               str = match[1];
-               // perhaps str = eval('"'+str+'"'); would be enough?
-               str = str.replace(escCodeRe,
-                       function (substr, p1, offset, s) { return unq(p1); });
-       }
-       return str;
-}
-
-/* ============================================================ */
-/* main part: parsing response */
-
-/**
- * Function called for each blame entry, as soon as it finishes.
- * It updates page via DOM manipulation, adding sha1 info, etc.
- *
- * @param {Commit} commit: blamed commit
- * @param {Object} group: object representing group of lines,
- *                        which blame the same commit (blame entry)
- *
- * @globals blamedLines
- */
-function handleLine(commit, group) {
-       /*
-          This is the structure of the HTML fragment we are working
-          with:
-
-          <tr id="l123" class="">
-            <td class="sha1" title=""><a href=""> </a></td>
-            <td class="linenr"><a class="linenr" href="">123</a></td>
-            <td class="pre"># times (my ext3 doesn&#39;t).</td>
-          </tr>
-       */
-
-       var resline = group.resline;
-
-       // format date and time string only once per commit
-       if (!commit.info) {
-               /* e.g. 'Kay Sievers, 2005-08-07 21:49:46 +0200' */
-               commit.info = commit.author + ', ' +
-                       formatDateISOLocal(commit.authorTime, commit.authorTimezone);
-       }
-
-       // color depends on group of lines, not only on blamed commit
-       var colorNo = findColorNo(
-               document.getElementById('l'+(resline-1)),
-               document.getElementById('l'+(resline+group.numlines))
-       );
-
-       // loop over lines in commit group
-       for (var i = 0; i < group.numlines; i++, resline++) {
-               var tr = document.getElementById('l'+resline);
-               if (!tr) {
-                       break;
-               }
-               /*
-                       <tr id="l123" class="">
-                         <td class="sha1" title=""><a href=""> </a></td>
-                         <td class="linenr"><a class="linenr" href="">123</a></td>
-                         <td class="pre"># times (my ext3 doesn&#39;t).</td>
-                       </tr>
-               */
-               var td_sha1  = tr.firstChild;
-               var a_sha1   = td_sha1.firstChild;
-               var a_linenr = td_sha1.nextSibling.firstChild;
-
-               /* <tr id="l123" class=""> */
-               var tr_class = '';
-               if (colorNo !== null) {
-                       tr_class = 'color'+colorNo;
-               }
-               if (commit.boundary) {
-                       tr_class += ' boundary';
-               }
-               if (commit.nprevious === 0) {
-                       tr_class += ' no-previous';
-               } else if (commit.nprevious > 1) {
-                       tr_class += ' multiple-previous';
-               }
-               tr.className = tr_class;
-
-               /* <td class="sha1" title="?" rowspan="?"><a href="?">?</a></td> */
-               if (i === 0) {
-                       td_sha1.title = commit.info;
-                       td_sha1.rowSpan = group.numlines;
-
-                       a_sha1.href = projectUrl + 'a=commit;h=' + commit.sha1;
-                       if (a_sha1.firstChild) {
-                               a_sha1.firstChild.data = commit.sha1.substr(0, 8);
-                       } else {
-                               a_sha1.appendChild(
-                                       document.createTextNode(commit.sha1.substr(0, 8)));
-                       }
-                       if (group.numlines >= 2) {
-                               var fragment = document.createDocumentFragment();
-                               var br   = document.createElement("br");
-                               var match = commit.author.match(/\b([A-Z])\B/g);
-                               if (match) {
-                                       var text = document.createTextNode(
-                                                       match.join(''));
-                               }
-                               if (br && text) {
-                                       var elem = fragment || td_sha1;
-                                       elem.appendChild(br);
-                                       elem.appendChild(text);
-                                       if (fragment) {
-                                               td_sha1.appendChild(fragment);
-                                       }
-                               }
-                       }
-               } else {
-                       //tr.removeChild(td_sha1); // DOM2 Core way
-                       tr.deleteCell(0); // DOM2 HTML way
-               }
-
-               /* <td class="linenr"><a class="linenr" href="?">123</a></td> */
-               var linenr_commit =
-                       ('previous' in commit ? commit.previous : commit.sha1);
-               var linenr_filename =
-                       ('file_parent' in commit ? commit.file_parent : commit.filename);
-               a_linenr.href = projectUrl + 'a=blame_incremental' +
-                       ';hb=' + linenr_commit +
-                       ';f='  + encodeURIComponent(linenr_filename) +
-                       '#l' + (group.srcline + i);
-
-               blamedLines++;
-
-               //updateProgressInfo();
-       }
-}
-
-// ----------------------------------------------------------------------
-
-var inProgress = false;   // are we processing response
-
-/**#@+
- * @constant
- */
-var sha1Re = /^([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/;
-var infoRe = /^([a-z-]+) ?(.*)/;
-var endRe  = /^END ?([^ ]*) ?(.*)/;
-/**@-*/
-
-var curCommit = new Commit();
-var curGroup  = {};
-
-var pollTimer = null;
-
-/**
- * Parse output from 'git blame --incremental [...]', received via
- * XMLHttpRequest from server (blamedataUrl), and call handleLine
- * (which updates page) as soon as blame entry is completed.
- *
- * @param {String[]} lines: new complete lines from blamedata server
- *
- * @globals commits, curCommit, curGroup, t_interval_server, cmds_server
- * @globals sha1Re, infoRe, endRe
- */
-function processBlameLines(lines) {
-       var match;
-
-       for (var i = 0, len = lines.length; i < len; i++) {
-
-               if ((match = sha1Re.exec(lines[i]))) {
-                       var sha1 = match[1];
-                       var srcline  = parseInt(match[2], 10);
-                       var resline  = parseInt(match[3], 10);
-                       var numlines = parseInt(match[4], 10);
-
-                       var c = commits[sha1];
-                       if (!c) {
-                               c = new Commit(sha1);
-                               commits[sha1] = c;
-                       }
-                       curCommit = c;
-
-                       curGroup.srcline = srcline;
-                       curGroup.resline = resline;
-                       curGroup.numlines = numlines;
-
-               } else if ((match = infoRe.exec(lines[i]))) {
-                       var info = match[1];
-                       var data = match[2];
-                       switch (info) {
-                       case 'filename':
-                               curCommit.filename = unquote(data);
-                               // 'filename' information terminates the entry
-                               handleLine(curCommit, curGroup);
-                               updateProgressInfo();
-                               break;
-                       case 'author':
-                               curCommit.author = data;
-                               break;
-                       case 'author-time':
-                               curCommit.authorTime = parseInt(data, 10);
-                               break;
-                       case 'author-tz':
-                               curCommit.authorTimezone = data;
-                               break;
-                       case 'previous':
-                               curCommit.nprevious++;
-                               // store only first 'previous' header
-                               if (!'previous' in curCommit) {
-                                       var parts = data.split(' ', 2);
-                                       curCommit.previous    = parts[0];
-                                       curCommit.file_parent = unquote(parts[1]);
-                               }
-                               break;
-                       case 'boundary':
-                               curCommit.boundary = true;
-                               break;
-                       } // end switch
-
-               } else if ((match = endRe.exec(lines[i]))) {
-                       t_interval_server = match[1];
-                       cmds_server = match[2];
-
-               } else if (lines[i] !== '') {
-                       // malformed line
-
-               } // end if (match)
-
-       } // end for (lines)
-}
-
-/**
- * Process new data and return pointer to end of processed part
- *
- * @param {String} unprocessed: new data (from nextReadPos)
- * @param {Number} nextReadPos: end of last processed data
- * @return {Number} end of processed data (new value for nextReadPos)
- */
-function processData(unprocessed, nextReadPos) {
-       var lastLineEnd = unprocessed.lastIndexOf('\n');
-       if (lastLineEnd !== -1) {
-               var lines = unprocessed.substring(0, lastLineEnd).split('\n');
-               nextReadPos += lastLineEnd + 1 /* 1 == '\n'.length */;
-
-               processBlameLines(lines);
-       } // end if
-
-       return nextReadPos;
-}
-
-/**
- * Handle XMLHttpRequest errors
- *
- * @param {XMLHttpRequest} xhr: XMLHttpRequest object
- *
- * @globals pollTimer, commits, inProgress
- */
-function handleError(xhr) {
-       errorInfo('Server error: ' +
-               xhr.status + ' - ' + (xhr.statusText || 'Error contacting server'));
-
-       clearInterval(pollTimer);
-       commits = {}; // free memory
-
-       inProgress = false;
-}
-
-/**
- * Called after XMLHttpRequest finishes (loads)
- *
- * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused)
- *
- * @globals pollTimer, commits, inProgress
- */
-function responseLoaded(xhr) {
-       clearInterval(pollTimer);
-
-       fixColorsAndGroups();
-       writeTimeInterval();
-       commits = {}; // free memory
-
-       inProgress = false;
-}
-
-/**
- * handler for XMLHttpRequest onreadystatechange event
- * @see startBlame
- *
- * @globals xhr, inProgress
- */
-function handleResponse() {
-
-       /*
-        * xhr.readyState
-        *
-        *  Value  Constant (W3C)    Description
-        *  -------------------------------------------------------------------
-        *  0      UNSENT            open() has not been called yet.
-        *  1      OPENED            send() has not been called yet.
-        *  2      HEADERS_RECEIVED  send() has been called, and headers
-        *                           and status are available.
-        *  3      LOADING           Downloading; responseText holds partial data.
-        *  4      DONE              The operation is complete.
-        */
-
-       if (xhr.readyState !== 4 && xhr.readyState !== 3) {
-               return;
-       }
-
-       // the server returned error
-       // try ... catch block is to work around bug in IE8
-       try {
-               if (xhr.readyState === 3 && xhr.status !== 200) {
-                       return;
-               }
-       } catch (e) {
-               return;
-       }
-       if (xhr.readyState === 4 && xhr.status !== 200) {
-               handleError(xhr);
-               return;
-       }
-
-       // In konqueror xhr.responseText is sometimes null here...
-       if (xhr.responseText === null) {
-               return;
-       }
-
-       // in case we were called before finished processing
-       if (inProgress) {
-               return;
-       } else {
-               inProgress = true;
-       }
-
-       // extract new whole (complete) lines, and process them
-       while (xhr.prevDataLength !== xhr.responseText.length) {
-               if (xhr.readyState === 4 &&
-                   xhr.prevDataLength === xhr.responseText.length) {
-                       break;
-               }
-
-               xhr.prevDataLength = xhr.responseText.length;
-               var unprocessed = xhr.responseText.substring(xhr.nextReadPos);
-               xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos);
-       } // end while
-
-       // did we finish work?
-       if (xhr.readyState === 4 &&
-           xhr.prevDataLength === xhr.responseText.length) {
-               responseLoaded(xhr);
-       }
-
-       inProgress = false;
-}
-
-// ============================================================
-// ------------------------------------------------------------
-
-/**
- * Incrementally update line data in blame_incremental view in gitweb.
- *
- * @param {String} blamedataUrl: URL to server script generating blame data.
- * @param {String} bUrl: partial URL to project, used to generate links.
- *
- * Called from 'blame_incremental' view after loading table with
- * file contents, a base for blame view.
- *
- * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer
-*/
-function startBlame(blamedataUrl, bUrl) {
-
-       xhr = createRequestObject();
-       if (!xhr) {
-               errorInfo('ERROR: XMLHttpRequest not supported');
-               return;
-       }
-
-       t0 = new Date();
-       projectUrl = bUrl + (bUrl.indexOf('?') === -1 ? '?' : ';');
-       if ((div_progress_bar = document.getElementById('progress_bar'))) {
-               //div_progress_bar.setAttribute('style', 'width: 100%;');
-               div_progress_bar.style.cssText = 'width: 100%;';
-       }
-       totalLines = countLines();
-       updateProgressInfo();
-
-       /* add extra properties to xhr object to help processing response */
-       xhr.prevDataLength = -1;  // used to detect if we have new data
-       xhr.nextReadPos = 0;      // where unread part of response starts
-
-       xhr.onreadystatechange = handleResponse;
-       //xhr.onreadystatechange = function () { handleResponse(xhr); };
-
-       xhr.open('GET', blamedataUrl);
-       xhr.setRequestHeader('Accept', 'text/plain');
-       xhr.send(null);
-
-       // not all browsers call onreadystatechange event on each server flush
-       // poll response using timer every second to handle this issue
-       pollTimer = setInterval(xhr.onreadystatechange, 1000);
-}
-
-// end of gitweb.js
diff --git a/gitweb/static/js/README b/gitweb/static/js/README
new file mode 100644 (file)
index 0000000..f8460ed
--- /dev/null
@@ -0,0 +1,20 @@
+GIT web interface (gitweb) - JavaScript
+=======================================
+
+This directory holds JavaScript code used by gitweb (GIT web interface).
+Scripts from there would be concatenated together in the order specified
+by gitweb/Makefile into gitweb/static/gitweb.js, during building of
+gitweb/gitweb.cgi (during gitweb building).  The resulting file (or its
+minification) would then be installed / deployed together with gitweb.
+
+Scripts in 'lib/' subdirectory compose generic JavaScript library,
+providing features required by gitweb but in no way limited to gitweb
+only.  In the future those scripts could be replaced by some JavaScript
+library / framework, like e.g. jQuery, YUI, Prototype, MooTools, Dojo,
+ExtJS, Script.aculo.us or SproutCore.
+
+All scripts that manipulate gitweb output should be put outside 'lib/',
+directly in this directory ('gitweb/static/js/').  Those scripts would
+have to be rewritten if gitweb moves to using some JavaScript library.
+
+See also comments in gitweb/Makefile.
diff --git a/gitweb/static/js/adjust-timezone.js b/gitweb/static/js/adjust-timezone.js
new file mode 100644 (file)
index 0000000..0c67779
--- /dev/null
@@ -0,0 +1,330 @@
+// Copyright (C) 2011, John 'Warthog9' Hawley <warthog9@eaglescrag.net>
+//               2011, Jakub Narebski <jnareb@gmail.com>
+
+/**
+ * @fileOverview Manipulate dates in gitweb output, adjusting timezone
+ * @license GPLv2 or later
+ */
+
+/**
+ * Get common timezone, add UI for changing timezones, and adjust
+ * dates to use requested common timezone.
+ *
+ * This function is called during onload event (added to window.onload).
+ *
+ * @param {String} tzDefault: default timezone, if there is no cookie
+ * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone
+ * @param {String} tzCookieInfo.name: name of cookie to store timezone
+ * @param {String} tzClassName: denotes elements with date to be adjusted
+ */
+function onloadTZSetup(tzDefault, tzCookieInfo, tzClassName) {
+       var tzCookieTZ = getCookie(tzCookieInfo.name, tzCookieInfo);
+       var tz = tzDefault;
+
+       if (tzCookieTZ) {
+               // set timezone to value saved in a cookie
+               tz = tzCookieTZ;
+               // refresh cookie, so its expiration counts from last use of gitweb
+               setCookie(tzCookieInfo.name, tzCookieTZ, tzCookieInfo);
+       }
+
+       // add UI for changing timezone
+       addChangeTZ(tz, tzCookieInfo, tzClassName);
+
+       // server-side of gitweb produces datetime in UTC,
+       // so if tz is 'utc' there is no need for changes
+       var nochange = tz === 'utc';
+
+       // adjust dates to use specified common timezone
+       fixDatetimeTZ(tz, tzClassName, nochange);
+}
+
+
+/* ...................................................................... */
+/* Changing dates to use requested timezone */
+
+/**
+ * Replace RFC-2822 dates contained in SPAN elements with tzClassName
+ * CSS class with equivalent dates in given timezone.
+ *
+ * @param {String} tz: numeric timezone in '(-|+)HHMM' format, or 'utc', or 'local'
+ * @param {String} tzClassName: specifies elements to be changed
+ * @param {Boolean} nochange: markup for timezone change, but don't change it
+ */
+function fixDatetimeTZ(tz, tzClassName, nochange) {
+       // sanity check, method should be ensured by common-lib.js
+       if (!document.getElementsByClassName) {
+               return;
+       }
+
+       // translate to timezone in '(-|+)HHMM' format
+       tz = normalizeTimezoneInfo(tz);
+
+       // NOTE: result of getElementsByClassName should probably be cached
+       var classesFound = document.getElementsByClassName(tzClassName, "span");
+       for (var i = 0, len = classesFound.length; i < len; i++) {
+               var curElement = classesFound[i];
+
+               curElement.title = 'Click to change timezone';
+               if (!nochange) {
+                       // we use *.firstChild.data (W3C DOM) instead of *.innerHTML
+                       // as the latter doesn't always work everywhere in every browser
+                       var epoch = parseRFC2822Date(curElement.firstChild.data);
+                       var adjusted = formatDateRFC2882(epoch, tz);
+
+                       curElement.firstChild.data = adjusted;
+               }
+       }
+}
+
+
+/* ...................................................................... */
+/* Adding triggers, generating timezone menu, displaying and hiding */
+
+/**
+ * Adds triggers for UI to change common timezone used for dates in
+ * gitweb output: it marks up and/or creates item to click to invoke
+ * timezone change UI, creates timezone UI fragment to be attached,
+ * and installs appropriate onclick trigger (via event delegation).
+ *
+ * @param {String} tzSelected: pre-selected timezone,
+ *                             'utc' or 'local' or '(-|+)HHMM'
+ * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone
+ * @param {String} tzClassName: specifies elements to install trigger
+ */
+function addChangeTZ(tzSelected, tzCookieInfo, tzClassName) {
+       // make link to timezone UI discoverable
+       addCssRule('.'+tzClassName + ':hover',
+                  'text-decoration: underline; cursor: help;');
+
+       // create form for selecting timezone (to be saved in a cookie)
+       var tzSelectFragment = document.createDocumentFragment();
+       tzSelectFragment = createChangeTZForm(tzSelectFragment,
+                                             tzSelected, tzCookieInfo, tzClassName);
+
+       // event delegation handler for timezone selection UI (clicking on entry)
+       // see http://www.nczonline.net/blog/2009/06/30/event-delegation-in-javascript/
+       // assumes that there is no existing document.onclick handler
+       document.onclick = function onclickHandler(event) {
+               //IE doesn't pass in the event object
+               event = event || window.event;
+
+               //IE uses srcElement as the target
+               var target = event.target || event.srcElement;
+
+               switch (target.className) {
+               case tzClassName:
+                       // don't display timezone menu if it is already displayed
+                       if (tzSelectFragment.childNodes.length > 0) {
+                               displayChangeTZForm(target, tzSelectFragment);
+                       }
+                       break;
+               } // end switch
+       };
+}
+
+/**
+ * Create DocumentFragment with UI for changing common timezone in
+ * which dates are shown in.
+ *
+ * @param {DocumentFragment} documentFragment: where attach UI
+ * @param {String} tzSelected: default (pre-selected) timezone
+ * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone
+ * @returns {DocumentFragment}
+ */
+function createChangeTZForm(documentFragment, tzSelected, tzCookieInfo, tzClassName) {
+       var div = document.createElement("div");
+       div.className = 'popup';
+
+       /* '<div class="close-button" title="(click on this box to close)">X</div>' */
+       var closeButton = document.createElement('div');
+       closeButton.className = 'close-button';
+       closeButton.title = '(click on this box to close)';
+       closeButton.appendChild(document.createTextNode('X'));
+       closeButton.onclick = closeTZFormHandler(documentFragment, tzClassName);
+       div.appendChild(closeButton);
+
+       /* 'Select timezone: <br clear="all">' */
+       div.appendChild(document.createTextNode('Select timezone: '));
+       var br = document.createElement('br');
+       br.clear = 'all';
+       div.appendChild(br);
+
+       /* '<select name="tzoffset">
+        *    ...
+        *    <option value="-0700">UTC-07:00</option>
+        *    <option value="-0600">UTC-06:00</option>
+        *    ...
+        *  </select>' */
+       var select = document.createElement("select");
+       select.name = "tzoffset";
+       //select.style.clear = 'all';
+       select.appendChild(generateTZOptions(tzSelected));
+       select.onchange = selectTZHandler(documentFragment, tzCookieInfo, tzClassName);
+       div.appendChild(select);
+
+       documentFragment.appendChild(div);
+
+       return documentFragment;
+}
+
+
+/**
+ * Hide (remove from DOM) timezone change UI, ensuring that it is not
+ * garbage collected and that it can be re-enabled later.
+ *
+ * @param {DocumentFragment} documentFragment: contains detached UI
+ * @param {HTMLSelectElement} target: select element inside of UI
+ * @param {String} tzClassName: specifies element where UI was installed
+ * @returns {DocumentFragment} documentFragment
+ */
+function removeChangeTZForm(documentFragment, target, tzClassName) {
+       // find containing element, where we appended timezone selection UI
+       // `target' is somewhere inside timezone menu
+       var container = target.parentNode, popup = target;
+       while (container &&
+              container.className !== tzClassName) {
+               popup = container;
+               container = container.parentNode;
+       }
+       // safety check if we found correct container,
+       // and if it isn't deleted already
+       if (!container || !popup ||
+           container.className !== tzClassName ||
+           popup.className     !== 'popup') {
+               return documentFragment;
+       }
+
+       // timezone selection UI was appended as last child
+       // see also displayChangeTZForm function
+       var removed = popup.parentNode.removeChild(popup);
+       if (documentFragment.firstChild !== removed) { // the only child
+               // re-append it so it would be available for next time
+               documentFragment.appendChild(removed);
+       }
+       // all of inline style was added by this script
+       // it is not really needed to remove it, but it is a good practice
+       container.removeAttribute('style');
+
+       return documentFragment;
+}
+
+
+/**
+ * Display UI for changing common timezone for dates in gitweb output.
+ * To be used from 'onclick' event handler.
+ *
+ * @param {HTMLElement} target: where to install/display UI
+ * @param {DocumentFragment} tzSelectFragment: timezone selection UI
+ */
+function displayChangeTZForm(target, tzSelectFragment) {
+       // for absolute positioning to be related to target element
+       target.style.position = 'relative';
+       target.style.display = 'inline-block';
+
+       // show/display UI for changing timezone
+       target.appendChild(tzSelectFragment);
+}
+
+
+/* ...................................................................... */
+/* List of timezones for timezone selection menu */
+
+/**
+ * Generate list of timezones for creating timezone select UI
+ *
+ * @returns {Object[]} list of e.g. { value: '+0100', descr: 'GMT+01:00' }
+ */
+function generateTZList() {
+       var timezones = [
+               { value: "utc",   descr: "UTC/GMT"},
+               { value: "local", descr: "Local (per browser)"}
+       ];
+
+       // generate all full hour timezones (no fractional timezones)
+       for (var x = -12, idx = timezones.length; x <= +14; x++, idx++) {
+               var hours = (x >= 0 ? '+' : '-') + padLeft(x >=0 ? x : -x, 2);
+               timezones[idx] = { value: hours + '00', descr: 'UTC' + hours + ':00'};
+               if (x === 0) {
+                       timezones[idx].descr = 'UTC\u00B100:00'; // 'UTC&plusmn;00:00'
+               }
+       }
+
+       return timezones;
+}
+
+/**
+ * Generate <options> elements for timezone select UI
+ *
+ * @param {String} tzSelected: default timezone
+ * @returns {DocumentFragment} list of options elements to appendChild
+ */
+function generateTZOptions(tzSelected) {
+       var elems = document.createDocumentFragment();
+       var timezones = generateTZList();
+
+       for (var i = 0, len = timezones.length; i < len; i++) {
+               var tzone = timezones[i];
+               var option = document.createElement("option");
+               if (tzone.value === tzSelected) {
+                       option.defaultSelected = true;
+               }
+               option.value = tzone.value;
+               option.appendChild(document.createTextNode(tzone.descr));
+
+               elems.appendChild(option);
+       }
+
+       return elems;
+}
+
+
+/* ...................................................................... */
+/* Event handlers and/or their generators */
+
+/**
+ * Create event handler that select timezone and closes timezone select UI.
+ * To be used as $('select[name="tzselect"]').onchange handler.
+ *
+ * @param {DocumentFragment} tzSelectFragment: timezone selection UI
+ * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone
+ * @param {String} tzCookieInfo.name: name of cookie to save result of selection
+ * @param {String} tzClassName: specifies element where UI was installed
+ * @returns {Function} event handler
+ */
+function selectTZHandler(tzSelectFragment, tzCookieInfo, tzClassName) {
+       //return function selectTZ(event) {
+       return function (event) {
+               event = event || window.event;
+               var target = event.target || event.srcElement;
+
+               var selected = target.options.item(target.selectedIndex);
+               removeChangeTZForm(tzSelectFragment, target, tzClassName);
+
+               if (selected) {
+                       selected.defaultSelected = true;
+                       setCookie(tzCookieInfo.name, selected.value, tzCookieInfo);
+                       fixDatetimeTZ(selected.value, tzClassName);
+               }
+       };
+}
+
+/**
+ * Create event handler that closes timezone select UI.
+ * To be used e.g. as $('.closebutton').onclick handler.
+ *
+ * @param {DocumentFragment} tzSelectFragment: timezone selection UI
+ * @param {String} tzClassName: specifies element where UI was installed
+ * @returns {Function} event handler
+ */
+function closeTZFormHandler(tzSelectFragment, tzClassName) {
+       //return function closeTZForm(event) {
+       return function (event) {
+               event = event || window.event;
+               var target = event.target || event.srcElement;
+
+               removeChangeTZForm(tzSelectFragment, target, tzClassName);
+       };
+}
+
+/* end of adjust-timezone.js */
diff --git a/gitweb/static/js/blame_incremental.js b/gitweb/static/js/blame_incremental.js
new file mode 100644 (file)
index 0000000..676da6b
--- /dev/null
@@ -0,0 +1,687 @@
+// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>
+//               2007, Petr Baudis <pasky@suse.cz>
+//          2008-2011, Jakub Narebski <jnareb@gmail.com>
+
+/**
+ * @fileOverview JavaScript side of Ajax-y 'blame_incremental' view in gitweb
+ * @license GPLv2 or later
+ */
+
+/* ============================================================ */
+/*
+ * This code uses DOM methods instead of (nonstandard) innerHTML
+ * to modify page.
+ *
+ * innerHTML is non-standard IE extension, though supported by most
+ * browsers; however Firefox up to version 1.5 didn't implement it in
+ * a strict mode (application/xml+xhtml mimetype).
+ *
+ * Also my simple benchmarks show that using elem.firstChild.data =
+ * 'content' is slightly faster than elem.innerHTML = 'content'.  It
+ * is however more fragile (text element fragment must exists), and
+ * less feature-rich (we cannot add HTML).
+ *
+ * Note that DOM 2 HTML is preferred over generic DOM 2 Core; the
+ * equivalent using DOM 2 Core is usually shown in comments.
+ */
+
+
+/* ............................................................ */
+/* utility/helper functions (and variables) */
+
+var xhr;        // XMLHttpRequest object
+var projectUrl; // partial query + separator ('?' or ';')
+
+// 'commits' is an associative map. It maps SHA1s to Commit objects.
+var commits = {};
+
+/**
+ * constructor for Commit objects, used in 'blame'
+ * @class Represents a blamed commit
+ * @param {String} sha1: SHA-1 identifier of a commit
+ */
+function Commit(sha1) {
+       if (this instanceof Commit) {
+               this.sha1 = sha1;
+               this.nprevious = 0; /* number of 'previous', effective parents */
+       } else {
+               return new Commit(sha1);
+       }
+}
+
+/* ............................................................ */
+/* progress info, timing, error reporting */
+
+var blamedLines = 0;
+var totalLines  = '???';
+var div_progress_bar;
+var div_progress_info;
+
+/**
+ * Detects how many lines does a blamed file have,
+ * This information is used in progress info
+ *
+ * @returns {Number|String} Number of lines in file, or string '...'
+ */
+function countLines() {
+       var table =
+               document.getElementById('blame_table') ||
+               document.getElementsByTagName('table')[0];
+
+       if (table) {
+               return table.getElementsByTagName('tr').length - 1; // for header
+       } else {
+               return '...';
+       }
+}
+
+/**
+ * update progress info and length (width) of progress bar
+ *
+ * @globals div_progress_info, div_progress_bar, blamedLines, totalLines
+ */
+function updateProgressInfo() {
+       if (!div_progress_info) {
+               div_progress_info = document.getElementById('progress_info');
+       }
+       if (!div_progress_bar) {
+               div_progress_bar = document.getElementById('progress_bar');
+       }
+       if (!div_progress_info && !div_progress_bar) {
+               return;
+       }
+
+       var percentage = Math.floor(100.0*blamedLines/totalLines);
+
+       if (div_progress_info) {
+               div_progress_info.firstChild.data  = blamedLines + ' / ' + totalLines +
+                       ' (' + padLeftStr(percentage, 3, '\u00A0') + '%)';
+       }
+
+       if (div_progress_bar) {
+               //div_progress_bar.setAttribute('style', 'width: '+percentage+'%;');
+               div_progress_bar.style.width = percentage + '%';
+       }
+}
+
+
+var t_interval_server = '';
+var cmds_server = '';
+var t0 = new Date();
+
+/**
+ * write how much it took to generate data, and to run script
+ *
+ * @globals t0, t_interval_server, cmds_server
+ */
+function writeTimeInterval() {
+       var info_time = document.getElementById('generating_time');
+       if (!info_time || !t_interval_server) {
+               return;
+       }
+       var t1 = new Date();
+       info_time.firstChild.data += ' + (' +
+               t_interval_server + ' sec server blame_data / ' +
+               (t1.getTime() - t0.getTime())/1000 + ' sec client JavaScript)';
+
+       var info_cmds = document.getElementById('generating_cmd');
+       if (!info_time || !cmds_server) {
+               return;
+       }
+       info_cmds.firstChild.data += ' + ' + cmds_server;
+}
+
+/**
+ * show an error message alert to user within page (in progress info area)
+ * @param {String} str: plain text error message (no HTML)
+ *
+ * @globals div_progress_info
+ */
+function errorInfo(str) {
+       if (!div_progress_info) {
+               div_progress_info = document.getElementById('progress_info');
+       }
+       if (div_progress_info) {
+               div_progress_info.className = 'error';
+               div_progress_info.firstChild.data = str;
+       }
+}
+
+/* ............................................................ */
+/* coloring rows during blame_data (git blame --incremental) run */
+
+/**
+ * used to extract N from 'colorN', where N is a number,
+ * @constant
+ */
+var colorRe = /\bcolor([0-9]*)\b/;
+
+/**
+ * return N if <tr class="colorN">, otherwise return null
+ * (some browsers require CSS class names to begin with letter)
+ *
+ * @param {HTMLElement} tr: table row element to check
+ * @param {String} tr.className: 'class' attribute of tr element
+ * @returns {Number|null} N if tr.className == 'colorN', otherwise null
+ *
+ * @globals colorRe
+ */
+function getColorNo(tr) {
+       if (!tr) {
+               return null;
+       }
+       var className = tr.className;
+       if (className) {
+               var match = colorRe.exec(className);
+               if (match) {
+                       return parseInt(match[1], 10);
+               }
+       }
+       return null;
+}
+
+var colorsFreq = [0, 0, 0];
+/**
+ * return one of given possible colors (currently least used one)
+ * example: chooseColorNoFrom(2, 3) returns 2 or 3
+ *
+ * @param {Number[]} arguments: one or more numbers
+ *        assumes that  1 <= arguments[i] <= colorsFreq.length
+ * @returns {Number} Least used color number from arguments
+ * @globals colorsFreq
+ */
+function chooseColorNoFrom() {
+       // choose the color which is least used
+       var colorNo = arguments[0];
+       for (var i = 1; i < arguments.length; i++) {
+               if (colorsFreq[arguments[i]-1] < colorsFreq[colorNo-1]) {
+                       colorNo = arguments[i];
+               }
+       }
+       colorsFreq[colorNo-1]++;
+       return colorNo;
+}
+
+/**
+ * given two neighbor <tr> elements, find color which would be different
+ * from color of both of neighbors; used to 3-color blame table
+ *
+ * @param {HTMLElement} tr_prev
+ * @param {HTMLElement} tr_next
+ * @returns {Number} color number N such that
+ * colorN != tr_prev.className && colorN != tr_next.className
+ */
+function findColorNo(tr_prev, tr_next) {
+       var color_prev = getColorNo(tr_prev);
+       var color_next = getColorNo(tr_next);
+
+
+       // neither of neighbors has color set
+       // THEN we can use any of 3 possible colors
+       if (!color_prev && !color_next) {
+               return chooseColorNoFrom(1,2,3);
+       }
+
+       // either both neighbors have the same color,
+       // or only one of neighbors have color set
+       // THEN we can use any color except given
+       var color;
+       if (color_prev === color_next) {
+               color = color_prev; // = color_next;
+       } else if (!color_prev) {
+               color = color_next;
+       } else if (!color_next) {
+               color = color_prev;
+       }
+       if (color) {
+               return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1);
+       }
+
+       // neighbors have different colors
+       // THEN there is only one color left
+       return (3 - ((color_prev + color_next) % 3));
+}
+
+/* ............................................................ */
+/* coloring rows like 'blame' after 'blame_data' finishes */
+
+/**
+ * returns true if given row element (tr) is first in commit group
+ * to be used only after 'blame_data' finishes (after processing)
+ *
+ * @param {HTMLElement} tr: table row
+ * @returns {Boolean} true if TR is first in commit group
+ */
+function isStartOfGroup(tr) {
+       return tr.firstChild.className === 'sha1';
+}
+
+/**
+ * change colors to use zebra coloring (2 colors) instead of 3 colors
+ * concatenate neighbor commit groups belonging to the same commit
+ *
+ * @globals colorRe
+ */
+function fixColorsAndGroups() {
+       var colorClasses = ['light', 'dark'];
+       var linenum = 1;
+       var tr, prev_group;
+       var colorClass = 0;
+       var table =
+               document.getElementById('blame_table') ||
+               document.getElementsByTagName('table')[0];
+
+       while ((tr = document.getElementById('l'+linenum))) {
+       // index origin is 0, which is table header; start from 1
+       //while ((tr = table.rows[linenum])) { // <- it is slower
+               if (isStartOfGroup(tr, linenum, document)) {
+                       if (prev_group &&
+                           prev_group.firstChild.firstChild.href ===
+                                   tr.firstChild.firstChild.href) {
+                               // we have to concatenate groups
+                               var prev_rows = prev_group.firstChild.rowSpan || 1;
+                               var curr_rows =         tr.firstChild.rowSpan || 1;
+                               prev_group.firstChild.rowSpan = prev_rows + curr_rows;
+                               //tr.removeChild(tr.firstChild);
+                               tr.deleteCell(0); // DOM2 HTML way
+                       } else {
+                               colorClass = (colorClass + 1) % 2;
+                               prev_group = tr;
+                       }
+               }
+               var tr_class = tr.className;
+               tr.className = tr_class.replace(colorRe, colorClasses[colorClass]);
+               linenum++;
+       }
+}
+
+
+/* ============================================================ */
+/* main part: parsing response */
+
+/**
+ * Function called for each blame entry, as soon as it finishes.
+ * It updates page via DOM manipulation, adding sha1 info, etc.
+ *
+ * @param {Commit} commit: blamed commit
+ * @param {Object} group: object representing group of lines,
+ *                        which blame the same commit (blame entry)
+ *
+ * @globals blamedLines
+ */
+function handleLine(commit, group) {
+       /*
+          This is the structure of the HTML fragment we are working
+          with:
+
+          <tr id="l123" class="">
+            <td class="sha1" title=""><a href=""> </a></td>
+            <td class="linenr"><a class="linenr" href="">123</a></td>
+            <td class="pre"># times (my ext3 doesn&#39;t).</td>
+          </tr>
+       */
+
+       var resline = group.resline;
+
+       // format date and time string only once per commit
+       if (!commit.info) {
+               /* e.g. 'Kay Sievers, 2005-08-07 21:49:46 +0200' */
+               commit.info = commit.author + ', ' +
+                       formatDateISOLocal(commit.authorTime, commit.authorTimezone);
+       }
+
+       // color depends on group of lines, not only on blamed commit
+       var colorNo = findColorNo(
+               document.getElementById('l'+(resline-1)),
+               document.getElementById('l'+(resline+group.numlines))
+       );
+
+       // loop over lines in commit group
+       for (var i = 0; i < group.numlines; i++, resline++) {
+               var tr = document.getElementById('l'+resline);
+               if (!tr) {
+                       break;
+               }
+               /*
+                       <tr id="l123" class="">
+                         <td class="sha1" title=""><a href=""> </a></td>
+                         <td class="linenr"><a class="linenr" href="">123</a></td>
+                         <td class="pre"># times (my ext3 doesn&#39;t).</td>
+                       </tr>
+               */
+               var td_sha1  = tr.firstChild;
+               var a_sha1   = td_sha1.firstChild;
+               var a_linenr = td_sha1.nextSibling.firstChild;
+
+               /* <tr id="l123" class=""> */
+               var tr_class = '';
+               if (colorNo !== null) {
+                       tr_class = 'color'+colorNo;
+               }
+               if (commit.boundary) {
+                       tr_class += ' boundary';
+               }
+               if (commit.nprevious === 0) {
+                       tr_class += ' no-previous';
+               } else if (commit.nprevious > 1) {
+                       tr_class += ' multiple-previous';
+               }
+               tr.className = tr_class;
+
+               /* <td class="sha1" title="?" rowspan="?"><a href="?">?</a></td> */
+               if (i === 0) {
+                       td_sha1.title = commit.info;
+                       td_sha1.rowSpan = group.numlines;
+
+                       a_sha1.href = projectUrl + 'a=commit;h=' + commit.sha1;
+                       if (a_sha1.firstChild) {
+                               a_sha1.firstChild.data = commit.sha1.substr(0, 8);
+                       } else {
+                               a_sha1.appendChild(
+                                       document.createTextNode(commit.sha1.substr(0, 8)));
+                       }
+                       if (group.numlines >= 2) {
+                               var fragment = document.createDocumentFragment();
+                               var br   = document.createElement("br");
+                               var match = commit.author.match(/\b([A-Z])\B/g);
+                               if (match) {
+                                       var text = document.createTextNode(
+                                                       match.join(''));
+                               }
+                               if (br && text) {
+                                       var elem = fragment || td_sha1;
+                                       elem.appendChild(br);
+                                       elem.appendChild(text);
+                                       if (fragment) {
+                                               td_sha1.appendChild(fragment);
+                                       }
+                               }
+                       }
+               } else {
+                       //tr.removeChild(td_sha1); // DOM2 Core way
+                       tr.deleteCell(0); // DOM2 HTML way
+               }
+
+               /* <td class="linenr"><a class="linenr" href="?">123</a></td> */
+               var linenr_commit =
+                       ('previous' in commit ? commit.previous : commit.sha1);
+               var linenr_filename =
+                       ('file_parent' in commit ? commit.file_parent : commit.filename);
+               a_linenr.href = projectUrl + 'a=blame_incremental' +
+                       ';hb=' + linenr_commit +
+                       ';f='  + encodeURIComponent(linenr_filename) +
+                       '#l' + (group.srcline + i);
+
+               blamedLines++;
+
+               //updateProgressInfo();
+       }
+}
+
+// ----------------------------------------------------------------------
+
+var inProgress = false;   // are we processing response
+
+/**#@+
+ * @constant
+ */
+var sha1Re = /^([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/;
+var infoRe = /^([a-z-]+) ?(.*)/;
+var endRe  = /^END ?([^ ]*) ?(.*)/;
+/**@-*/
+
+var curCommit = new Commit();
+var curGroup  = {};
+
+var pollTimer = null;
+
+/**
+ * Parse output from 'git blame --incremental [...]', received via
+ * XMLHttpRequest from server (blamedataUrl), and call handleLine
+ * (which updates page) as soon as blame entry is completed.
+ *
+ * @param {String[]} lines: new complete lines from blamedata server
+ *
+ * @globals commits, curCommit, curGroup, t_interval_server, cmds_server
+ * @globals sha1Re, infoRe, endRe
+ */
+function processBlameLines(lines) {
+       var match;
+
+       for (var i = 0, len = lines.length; i < len; i++) {
+
+               if ((match = sha1Re.exec(lines[i]))) {
+                       var sha1 = match[1];
+                       var srcline  = parseInt(match[2], 10);
+                       var resline  = parseInt(match[3], 10);
+                       var numlines = parseInt(match[4], 10);
+
+                       var c = commits[sha1];
+                       if (!c) {
+                               c = new Commit(sha1);
+                               commits[sha1] = c;
+                       }
+                       curCommit = c;
+
+                       curGroup.srcline = srcline;
+                       curGroup.resline = resline;
+                       curGroup.numlines = numlines;
+
+               } else if ((match = infoRe.exec(lines[i]))) {
+                       var info = match[1];
+                       var data = match[2];
+                       switch (info) {
+                       case 'filename':
+                               curCommit.filename = unquote(data);
+                               // 'filename' information terminates the entry
+                               handleLine(curCommit, curGroup);
+                               updateProgressInfo();
+                               break;
+                       case 'author':
+                               curCommit.author = data;
+                               break;
+                       case 'author-time':
+                               curCommit.authorTime = parseInt(data, 10);
+                               break;
+                       case 'author-tz':
+                               curCommit.authorTimezone = data;
+                               break;
+                       case 'previous':
+                               curCommit.nprevious++;
+                               // store only first 'previous' header
+                               if (!'previous' in curCommit) {
+                                       var parts = data.split(' ', 2);
+                                       curCommit.previous    = parts[0];
+                                       curCommit.file_parent = unquote(parts[1]);
+                               }
+                               break;
+                       case 'boundary':
+                               curCommit.boundary = true;
+                               break;
+                       } // end switch
+
+               } else if ((match = endRe.exec(lines[i]))) {
+                       t_interval_server = match[1];
+                       cmds_server = match[2];
+
+               } else if (lines[i] !== '') {
+                       // malformed line
+
+               } // end if (match)
+
+       } // end for (lines)
+}
+
+/**
+ * Process new data and return pointer to end of processed part
+ *
+ * @param {String} unprocessed: new data (from nextReadPos)
+ * @param {Number} nextReadPos: end of last processed data
+ * @return {Number} end of processed data (new value for nextReadPos)
+ */
+function processData(unprocessed, nextReadPos) {
+       var lastLineEnd = unprocessed.lastIndexOf('\n');
+       if (lastLineEnd !== -1) {
+               var lines = unprocessed.substring(0, lastLineEnd).split('\n');
+               nextReadPos += lastLineEnd + 1 /* 1 == '\n'.length */;
+
+               processBlameLines(lines);
+       } // end if
+
+       return nextReadPos;
+}
+
+/**
+ * Handle XMLHttpRequest errors
+ *
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object
+ *
+ * @globals pollTimer, commits, inProgress
+ */
+function handleError(xhr) {
+       errorInfo('Server error: ' +
+               xhr.status + ' - ' + (xhr.statusText || 'Error contacting server'));
+
+       clearInterval(pollTimer);
+       commits = {}; // free memory
+
+       inProgress = false;
+}
+
+/**
+ * Called after XMLHttpRequest finishes (loads)
+ *
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused)
+ *
+ * @globals pollTimer, commits, inProgress
+ */
+function responseLoaded(xhr) {
+       clearInterval(pollTimer);
+
+       fixColorsAndGroups();
+       writeTimeInterval();
+       commits = {}; // free memory
+
+       inProgress = false;
+}
+
+/**
+ * handler for XMLHttpRequest onreadystatechange event
+ * @see startBlame
+ *
+ * @globals xhr, inProgress
+ */
+function handleResponse() {
+
+       /*
+        * xhr.readyState
+        *
+        *  Value  Constant (W3C)    Description
+        *  -------------------------------------------------------------------
+        *  0      UNSENT            open() has not been called yet.
+        *  1      OPENED            send() has not been called yet.
+        *  2      HEADERS_RECEIVED  send() has been called, and headers
+        *                           and status are available.
+        *  3      LOADING           Downloading; responseText holds partial data.
+        *  4      DONE              The operation is complete.
+        */
+
+       if (xhr.readyState !== 4 && xhr.readyState !== 3) {
+               return;
+       }
+
+       // the server returned error
+       // try ... catch block is to work around bug in IE8
+       try {
+               if (xhr.readyState === 3 && xhr.status !== 200) {
+                       return;
+               }
+       } catch (e) {
+               return;
+       }
+       if (xhr.readyState === 4 && xhr.status !== 200) {
+               handleError(xhr);
+               return;
+       }
+
+       // In konqueror xhr.responseText is sometimes null here...
+       if (xhr.responseText === null) {
+               return;
+       }
+
+       // in case we were called before finished processing
+       if (inProgress) {
+               return;
+       } else {
+               inProgress = true;
+       }
+
+       // extract new whole (complete) lines, and process them
+       while (xhr.prevDataLength !== xhr.responseText.length) {
+               if (xhr.readyState === 4 &&
+                   xhr.prevDataLength === xhr.responseText.length) {
+                       break;
+               }
+
+               xhr.prevDataLength = xhr.responseText.length;
+               var unprocessed = xhr.responseText.substring(xhr.nextReadPos);
+               xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos);
+       } // end while
+
+       // did we finish work?
+       if (xhr.readyState === 4 &&
+           xhr.prevDataLength === xhr.responseText.length) {
+               responseLoaded(xhr);
+       }
+
+       inProgress = false;
+}
+
+// ============================================================
+// ------------------------------------------------------------
+
+/**
+ * Incrementally update line data in blame_incremental view in gitweb.
+ *
+ * @param {String} blamedataUrl: URL to server script generating blame data.
+ * @param {String} bUrl: partial URL to project, used to generate links.
+ *
+ * Called from 'blame_incremental' view after loading table with
+ * file contents, a base for blame view.
+ *
+ * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer
+*/
+function startBlame(blamedataUrl, bUrl) {
+
+       xhr = createRequestObject();
+       if (!xhr) {
+               errorInfo('ERROR: XMLHttpRequest not supported');
+               return;
+       }
+
+       t0 = new Date();
+       projectUrl = bUrl + (bUrl.indexOf('?') === -1 ? '?' : ';');
+       if ((div_progress_bar = document.getElementById('progress_bar'))) {
+               //div_progress_bar.setAttribute('style', 'width: 100%;');
+               div_progress_bar.style.cssText = 'width: 100%;';
+       }
+       totalLines = countLines();
+       updateProgressInfo();
+
+       /* add extra properties to xhr object to help processing response */
+       xhr.prevDataLength = -1;  // used to detect if we have new data
+       xhr.nextReadPos = 0;      // where unread part of response starts
+
+       xhr.onreadystatechange = handleResponse;
+       //xhr.onreadystatechange = function () { handleResponse(xhr); };
+
+       xhr.open('GET', blamedataUrl);
+       xhr.setRequestHeader('Accept', 'text/plain');
+       xhr.send(null);
+
+       // not all browsers call onreadystatechange event on each server flush
+       // poll response using timer every second to handle this issue
+       pollTimer = setInterval(xhr.onreadystatechange, 1000);
+}
+
+/* end of blame_incremental.js */
diff --git a/gitweb/static/js/javascript-detection.js b/gitweb/static/js/javascript-detection.js
new file mode 100644 (file)
index 0000000..93dd2bd
--- /dev/null
@@ -0,0 +1,43 @@
+// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>
+//               2007, Petr Baudis <pasky@suse.cz>
+//          2008-2011, Jakub Narebski <jnareb@gmail.com>
+
+/**
+ * @fileOverview Detect if JavaScript is enabled, and pass it to server-side
+ * @license GPLv2 or later
+ */
+
+
+/* ============================================================ */
+/* Manipulating links */
+
+/**
+ * used to check if link has 'js' query parameter already (at end),
+ * and other reasons to not add 'js=1' param at the end of link
+ * @constant
+ */
+var jsExceptionsRe = /[;?]js=[01]$/;
+
+/**
+ * Add '?js=1' or ';js=1' to the end of every link in the document
+ * that doesn't have 'js' query parameter set already.
+ *
+ * Links with 'js=1' lead to JavaScript version of given action, if it
+ * exists (currently there is only 'blame_incremental' for 'blame')
+ *
+ * To be used as `window.onload` handler
+ *
+ * @globals jsExceptionsRe
+ */
+function fixLinks() {
+       var allLinks = document.getElementsByTagName("a") || document.links;
+       for (var i = 0, len = allLinks.length; i < len; i++) {
+               var link = allLinks[i];
+               if (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/;
+                       link.href +=
+                               (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1';
+               }
+       }
+}
+
+/* end of javascript-detection.js */
diff --git a/gitweb/static/js/lib/common-lib.js b/gitweb/static/js/lib/common-lib.js
new file mode 100644 (file)
index 0000000..018bbb7
--- /dev/null
@@ -0,0 +1,224 @@
+// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>
+//               2007, Petr Baudis <pasky@suse.cz>
+//          2008-2011, Jakub Narebski <jnareb@gmail.com>
+
+/**
+ * @fileOverview Generic JavaScript code (helper functions)
+ * @license GPLv2 or later
+ */
+
+
+/* ============================================================ */
+/* ............................................................ */
+/* Padding */
+
+/**
+ * pad INPUT on the left with STR that is assumed to have visible
+ * width of single character (for example nonbreakable spaces),
+ * to WIDTH characters
+ *
+ * example: padLeftStr(12, 3, '\u00A0') == '\u00A012'
+ *          ('\u00A0' is nonbreakable space)
+ *
+ * @param {Number|String} input: number to pad
+ * @param {Number} width: visible width of output
+ * @param {String} str: string to prefix to string, defaults to '\u00A0'
+ * @returns {String} INPUT prefixed with STR x (WIDTH - INPUT.length)
+ */
+function padLeftStr(input, width, str) {
+       var prefix = '';
+       if (typeof str === 'undefined') {
+               ch = '\u00A0'; // using '&nbsp;' doesn't work in all browsers
+       }
+
+       width -= input.toString().length;
+       while (width > 0) {
+               prefix += str;
+               width--;
+       }
+       return prefix + input;
+}
+
+/**
+ * Pad INPUT on the left to WIDTH, using given padding character CH,
+ * for example padLeft('a', 3, '_') is '__a'
+ *             padLeft(4, 2) is '04' (same as padLeft(4, 2, '0'))
+ *
+ * @param {String} input: input value converted to string.
+ * @param {Number} width: desired length of output.
+ * @param {String} ch: single character to prefix to string, defaults to '0'.
+ *
+ * @returns {String} Modified string, at least SIZE length.
+ */
+function padLeft(input, width, ch) {
+       var s = input + "";
+       if (typeof ch === 'undefined') {
+               ch = '0';
+       }
+
+       while (s.length < width) {
+               s = ch + s;
+       }
+       return s;
+}
+
+
+/* ............................................................ */
+/* Handling browser incompatibilities */
+
+/**
+ * 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;
+}
+
+
+/**
+ * Insert rule giving specified STYLE to given SELECTOR at the end of
+ * first CSS stylesheet.
+ *
+ * @param {String} selector: CSS selector, e.g. '.class'
+ * @param {String} style: rule contents, e.g. 'background-color: red;'
+ */
+function addCssRule(selector, style) {
+       var stylesheet = document.styleSheets[0];
+
+       var theRules = [];
+       if (stylesheet.cssRules) {     // W3C way
+               theRules = stylesheet.cssRules;
+       } else if (stylesheet.rules) { // IE way
+               theRules = stylesheet.rules;
+       }
+
+       if (stylesheet.insertRule) {    // W3C way
+               stylesheet.insertRule(selector + ' { ' + style + ' }', theRules.length);
+       } else if (stylesheet.addRule) { // IE way
+               stylesheet.addRule(selector, style);
+       }
+}
+
+
+/* ............................................................ */
+/* Support for legacy browsers */
+
+/**
+ * Provides getElementsByClassName method, if there is no native
+ * implementation of this method.
+ *
+ * NOTE that there are limits and differences compared to native
+ * getElementsByClassName as defined by e.g.:
+ *   https://developer.mozilla.org/en/DOM/document.getElementsByClassName
+ *   http://www.whatwg.org/specs/web-apps/current-work/multipage/dom.html#dom-getelementsbyclassname
+ *   http://www.whatwg.org/specs/web-apps/current-work/multipage/dom.html#dom-document-getelementsbyclassname
+ *
+ * Namely, this implementation supports only single class name as
+ * argument and not set of space-separated tokens representing classes,
+ * it returns Array of nodes rather than live NodeList, and has
+ * additional optional argument where you can limit search to given tags
+ * (via getElementsByTagName).
+ *
+ * Based on
+ *   http://code.google.com/p/getelementsbyclassname/
+ *   http://www.dustindiaz.com/getelementsbyclass/
+ *   http://stackoverflow.com/questions/1818865/do-we-have-getelementsbyclassname-in-javascript
+ *
+ * See also http://ejohn.org/blog/getelementsbyclassname-speed-comparison/
+ *
+ * @param {String} class: name of _single_ class to find
+ * @param {String} [taghint] limit search to given tags
+ * @returns {Node[]} array of matching elements
+ */
+if (!('getElementsByClassName' in document)) {
+       document.getElementsByClassName = function (classname, taghint) {
+               taghint = taghint || "*";
+               var elements = (taghint === "*" && document.all) ?
+                              document.all :
+                              document.getElementsByTagName(taghint);
+               var pattern = new RegExp("(^|\\s)" + classname + "(\\s|$)");
+               var matches= [];
+               for (var i = 0, j = 0, n = elements.length; i < n; i++) {
+                       var el= elements[i];
+                       if (el.className && pattern.test(el.className)) {
+                               // matches.push(el);
+                               matches[j] = el;
+                               j++;
+                       }
+               }
+               return matches;
+       };
+} // end if
+
+
+/* ............................................................ */
+/* unquoting/unescaping filenames */
+
+/**#@+
+ * @constant
+ */
+var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g;
+var octEscRe = /^[0-7]{1,3}$/;
+var maybeQuotedRe = /^\"(.*)\"$/;
+/**#@-*/
+
+/**
+ * unquote maybe C-quoted filename (as used by git, i.e. it is
+ * in double quotes '"' if there is any escape character used)
+ * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a   a'
+ *
+ * @param {String} str: git-quoted string
+ * @returns {String} Unquoted and unescaped string
+ *
+ * @globals escCodeRe, octEscRe, maybeQuotedRe
+ */
+function unquote(str) {
+       function unq(seq) {
+               var es = {
+                       // character escape codes, aka escape sequences (from C)
+                       // replacements are to some extent JavaScript specific
+                       t: "\t",   // tab            (HT, TAB)
+                       n: "\n",   // newline        (NL)
+                       r: "\r",   // return         (CR)
+                       f: "\f",   // form feed      (FF)
+                       b: "\b",   // backspace      (BS)
+                       a: "\x07", // alarm (bell)   (BEL)
+                       e: "\x1B", // escape         (ESC)
+                       v: "\v"    // vertical tab   (VT)
+               };
+
+               if (seq.search(octEscRe) !== -1) {
+                       // octal char sequence
+                       return String.fromCharCode(parseInt(seq, 8));
+               } else if (seq in es) {
+                       // C escape sequence, aka character escape code
+                       return es[seq];
+               }
+               // quoted ordinary character
+               return seq;
+       }
+
+       var match = str.match(maybeQuotedRe);
+       if (match) {
+               str = match[1];
+               // perhaps str = eval('"'+str+'"'); would be enough?
+               str = str.replace(escCodeRe,
+                       function (substr, p1, offset, s) { return unq(p1); });
+       }
+       return str;
+}
+
+/* end of common-lib.js */
diff --git a/gitweb/static/js/lib/cookies.js b/gitweb/static/js/lib/cookies.js
new file mode 100644 (file)
index 0000000..72b51cd
--- /dev/null
@@ -0,0 +1,114 @@
+/**
+ * @fileOverview Accessing cookies from JavaScript
+ * @license GPLv2 or later
+ */
+
+/*
+ * Based on subsection "Cookies in JavaScript" of "Professional
+ * JavaScript for Web Developers" by Nicholas C. Zakas and cookie
+ * plugin from jQuery (dual licensed under the MIT and GPL licenses)
+ */
+
+
+/**
+ * Create a cookie with the given name and value,
+ * and other optional parameters.
+ *
+ * @example
+ *   setCookie('foo', 'bar'); // will be deleted when browser exits
+ *   setCookie('foo', 'bar', { expires: new Date(Date.parse('Jan 1, 2012')) });
+ *   setCookie('foo', 'bar', { expires: 7 }); // 7 days = 1 week
+ *   setCookie('foo', 'bar', { expires: 14, path: '/' });
+ *
+ * @param {String} sName:    Unique name of a cookie (letters, numbers, underscores).
+ * @param {String} sValue:   The string value stored in a cookie.
+ * @param {Object} [options] An object literal containing key/value pairs
+ *                           to provide optional cookie attributes.
+ * @param {String|Number|Date} [options.expires] Either literal string to be used as cookie expires,
+ *                            or an integer specifying the expiration date from now on in days,
+ *                            or a Date object to be used as cookie expiration date.
+ *                            If a negative value is specified or a date in the past),
+ *                            the cookie will be deleted.
+ *                            If set to null or omitted, the cookie will be a session cookie
+ *                            and will not be retained when the the browser exits.
+ * @param {String} [options.path] Restrict access of a cookie to particular directory
+ *                               (default: path of page that created the cookie).
+ * @param {String} [options.domain] Override what web sites are allowed to access cookie
+ *                                  (default: domain of page that created the cookie).
+ * @param {Boolean} [options.secure] If true, the secure attribute of the cookie will be set
+ *                                   and the cookie would be accessible only from secure sites
+ *                                   (cookie transmission will require secure protocol like HTTPS).
+ */
+function setCookie(sName, sValue, options) {
+       options = options || {};
+       if (sValue === null) {
+               sValue = '';
+               option.expires = 'delete';
+       }
+
+       var sCookie = sName + '=' + encodeURIComponent(sValue);
+
+       if (options.expires) {
+               var oExpires = options.expires, sDate;
+               if (oExpires === 'delete') {
+                       sDate = 'Thu, 01 Jan 1970 00:00:00 GMT';
+               } else if (typeof oExpires === 'string') {
+                       sDate = oExpires;
+               } else {
+                       var oDate;
+                       if (typeof oExpires === 'number') {
+                               oDate = new Date();
+                               oDate.setTime(oDate.getTime() + (oExpires * 24 * 60 * 60 * 1000)); // days to ms
+                       } else {
+                               oDate = oExpires;
+                       }
+                       sDate = oDate.toGMTString();
+               }
+               sCookie += '; expires=' + sDate;
+       }
+
+       if (options.path) {
+               sCookie += '; path=' + (options.path);
+       }
+       if (options.domain) {
+               sCookie += '; domain=' + (options.domain);
+       }
+       if (options.secure) {
+               sCookie += '; secure';
+       }
+       document.cookie = sCookie;
+}
+
+/**
+ * Get the value of a cookie with the given name.
+ *
+ * @param {String} sName: Unique name of a cookie (letters, numbers, underscores)
+ * @returns {String|null} The string value stored in a cookie
+ */
+function getCookie(sName) {
+       var sRE = '(?:; )?' + sName + '=([^;]*);?';
+       var oRE = new RegExp(sRE);
+       if (oRE.test(document.cookie)) {
+               return decodeURIComponent(RegExp['$1']);
+       } else {
+               return null;
+       }
+}
+
+/**
+ * Delete cookie with given name
+ *
+ * @param {String} sName:    Unique name of a cookie (letters, numbers, underscores)
+ * @param {Object} [options] An object literal containing key/value pairs
+ *                           to provide optional cookie attributes.
+ * @param {String} [options.path]   Must be the same as when setting a cookie
+ * @param {String} [options.domain] Must be the same as when setting a cookie
+ */
+function deleteCookie(sName, options) {
+       options = options || {};
+       options.expires = 'delete';
+
+       setCookie(sName, '', options);
+}
+
+/* end of cookies.js */
diff --git a/gitweb/static/js/lib/datetime.js b/gitweb/static/js/lib/datetime.js
new file mode 100644 (file)
index 0000000..f78c60a
--- /dev/null
@@ -0,0 +1,176 @@
+// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>
+//               2007, Petr Baudis <pasky@suse.cz>
+//          2008-2011, Jakub Narebski <jnareb@gmail.com>
+
+/**
+ * @fileOverview Datetime manipulation: parsing and formatting
+ * @license GPLv2 or later
+ */
+
+
+/* ............................................................ */
+/* parsing and retrieving datetime related information */
+
+/**
+ * used to extract hours and minutes from timezone info, e.g '-0900'
+ * @constant
+ */
+var tzRe = /^([+\-])([0-9][0-9])([0-9][0-9])$/;
+
+/**
+ * convert numeric timezone +/-ZZZZ to offset from UTC in seconds
+ *
+ * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM'
+ * @returns {Number} offset from UTC in seconds for timezone
+ *
+ * @globals tzRe
+ */
+function timezoneOffset(timezoneInfo) {
+       var match = tzRe.exec(timezoneInfo);
+       var tz_sign = (match[1] === '-' ? -1 : +1);
+       var tz_hour = parseInt(match[2],10);
+       var tz_min  = parseInt(match[3],10);
+
+       return tz_sign*(((tz_hour*60) + tz_min)*60);
+}
+
+/**
+ * return local (browser) timezone as offset from UTC in seconds
+ *
+ * @returns {Number} offset from UTC in seconds for local timezone
+ */
+function localTimezoneOffset() {
+       // getTimezoneOffset returns the time-zone offset from UTC,
+       // in _minutes_, for the current locale
+       return ((new Date()).getTimezoneOffset() * -60);
+}
+
+/**
+ * return local (browser) timezone as numeric timezone '(+|-)HHMM'
+ *
+ * @returns {String} locat timezone as -/+ZZZZ
+ */
+function localTimezoneInfo() {
+       var tzOffsetMinutes = (new Date()).getTimezoneOffset() * -1;
+
+       return formatTimezoneInfo(0, tzOffsetMinutes);
+}
+
+
+/**
+ * Parse RFC-2822 date into a Unix timestamp (into epoch)
+ *
+ * @param {String} date: date in RFC-2822 format, e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'
+ * @returns {Number} epoch i.e. seconds since '00:00:00 1970-01-01 UTC'
+ */
+function parseRFC2822Date(date) {
+       // Date.parse accepts the IETF standard (RFC 1123 Section 5.2.14 and elsewhere)
+       // date syntax, which is defined in RFC 2822 (obsoletes RFC 822)
+       // and returns number of _milli_seconds since January 1, 1970, 00:00:00 UTC
+       return Date.parse(date) / 1000;
+}
+
+
+/* ............................................................ */
+/* formatting date */
+
+/**
+ * format timezone offset as numerical timezone '(+|-)HHMM' or '(+|-)HH:MM'
+ *
+ * @param {Number} hours:    offset in hours, e.g. 2 for '+0200'
+ * @param {Number} [minutes] offset in minutes, e.g. 30 for '-4030';
+ *                           it is split into hours if not 0 <= minutes < 60,
+ *                           for example 1200 would give '+0100';
+ *                           defaults to 0
+ * @param {String} [sep] separator between hours and minutes part,
+ *                       default is '', might be ':' for W3CDTF (rfc-3339)
+ * @returns {String} timezone in '(+|-)HHMM' or '(+|-)HH:MM' format
+ */
+function formatTimezoneInfo(hours, minutes, sep) {
+       minutes = minutes || 0; // to be able to use formatTimezoneInfo(hh)
+       sep = sep || ''; // default format is +/-ZZZZ
+
+       if (minutes < 0 || minutes > 59) {
+               hours = minutes > 0 ? Math.floor(minutes / 60) : Math.ceil(minutes / 60);
+               minutes = Math.abs(minutes - 60*hours); // sign of minutes is sign of hours
+               // NOTE: this works correctly because there is no UTC-00:30 timezone
+       }
+
+       var tzSign = hours >= 0 ? '+' : '-';
+       if (hours < 0) {
+               hours = -hours; // sign is stored in tzSign
+       }
+
+       return tzSign + padLeft(hours, 2, '0') + sep + padLeft(minutes, 2, '0');
+}
+
+/**
+ * translate 'utc' and 'local' to numerical timezone
+ * @param {String} timezoneInfo: might be 'utc' or 'local' (browser)
+ */
+function normalizeTimezoneInfo(timezoneInfo) {
+       switch (timezoneInfo) {
+       case 'utc':
+               return '+0000';
+       case 'local': // 'local' is browser timezone
+               return localTimezoneInfo();
+       }
+       return timezoneInfo;
+}
+
+
+/**
+ * return date in local time formatted in iso-8601 like format
+ * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200'
+ *
+ * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC'
+ * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM'
+ * @returns {String} date in local time in iso-8601 like format
+ */
+function formatDateISOLocal(epoch, timezoneInfo) {
+       // date corrected by timezone
+       var localDate = new Date(1000 * (epoch +
+               timezoneOffset(timezoneInfo)));
+       var localDateStr = // e.g. '2005-08-07'
+               localDate.getUTCFullYear()                 + '-' +
+               padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' +
+               padLeft(localDate.getUTCDate(),    2, '0');
+       var localTimeStr = // e.g. '21:49:46'
+               padLeft(localDate.getUTCHours(),   2, '0') + ':' +
+               padLeft(localDate.getUTCMinutes(), 2, '0') + ':' +
+               padLeft(localDate.getUTCSeconds(), 2, '0');
+
+       return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo;
+}
+
+/**
+ * return date in local time formatted in rfc-2822 format
+ * e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'
+ *
+ * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC'
+ * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM'
+ * @param {Boolean} [padDay] e.g. 'Sun, 07 Aug' if true, 'Sun, 7 Aug' otherwise
+ * @returns {String} date in local time in rfc-2822 format
+ */
+function formatDateRFC2882(epoch, timezoneInfo, padDay) {
+       // A short textual representation of a month, three letters
+       var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+       // A textual representation of a day, three letters
+       var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+       // date corrected by timezone
+       var localDate = new Date(1000 * (epoch +
+               timezoneOffset(timezoneInfo)));
+       var localDateStr = // e.g. 'Sun, 7 Aug 2005' or 'Sun, 07 Aug 2005'
+               days[localDate.getUTCDay()] + ', ' +
+               (padDay ? padLeft(localDate.getUTCDate(),2,'0') : localDate.getUTCDate()) + ' ' +
+               months[localDate.getUTCMonth()] + ' ' +
+               localDate.getUTCFullYear();
+       var localTimeStr = // e.g. '21:49:46'
+               padLeft(localDate.getUTCHours(),   2, '0') + ':' +
+               padLeft(localDate.getUTCMinutes(), 2, '0') + ':' +
+               padLeft(localDate.getUTCSeconds(), 2, '0');
+
+       return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo;
+}
+
+/* end of datetime.js */
diff --git a/grep.c b/grep.c
index 63c4280cac9a87f867451c0d9787d1fe21ea9c2d..d03d9e24c23eff2d60ae7226a412f3ccf66670fd 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -59,6 +59,84 @@ struct grep_opt *grep_opt_dup(const struct grep_opt *opt)
        return ret;
 }
 
+static NORETURN void compile_regexp_failed(const struct grep_pat *p,
+               const char *error)
+{
+       char where[1024];
+
+       if (p->no)
+               sprintf(where, "In '%s' at %d, ", p->origin, p->no);
+       else if (p->origin)
+               sprintf(where, "%s, ", p->origin);
+       else
+               where[0] = 0;
+
+       die("%s'%s': %s", where, p->pattern, error);
+}
+
+#ifdef USE_LIBPCRE
+static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt)
+{
+       const char *error;
+       int erroffset;
+       int options = 0;
+
+       if (opt->ignore_case)
+               options |= PCRE_CASELESS;
+
+       p->pcre_regexp = pcre_compile(p->pattern, options, &error, &erroffset,
+                       NULL);
+       if (!p->pcre_regexp)
+               compile_regexp_failed(p, error);
+
+       p->pcre_extra_info = pcre_study(p->pcre_regexp, 0, &error);
+       if (!p->pcre_extra_info && error)
+               die("%s", error);
+}
+
+static int pcrematch(struct grep_pat *p, const char *line, const char *eol,
+               regmatch_t *match, int eflags)
+{
+       int ovector[30], ret, flags = 0;
+
+       if (eflags & REG_NOTBOL)
+               flags |= PCRE_NOTBOL;
+
+       ret = pcre_exec(p->pcre_regexp, p->pcre_extra_info, line, eol - line,
+                       0, flags, ovector, ARRAY_SIZE(ovector));
+       if (ret < 0 && ret != PCRE_ERROR_NOMATCH)
+               die("pcre_exec failed with error code %d", ret);
+       if (ret > 0) {
+               ret = 0;
+               match->rm_so = ovector[0];
+               match->rm_eo = ovector[1];
+       }
+
+       return ret;
+}
+
+static void free_pcre_regexp(struct grep_pat *p)
+{
+       pcre_free(p->pcre_regexp);
+       pcre_free(p->pcre_extra_info);
+}
+#else /* !USE_LIBPCRE */
+static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt)
+{
+       die("cannot use Perl-compatible regexes when not compiled with USE_LIBPCRE");
+}
+
+static int pcrematch(struct grep_pat *p, const char *line, const char *eol,
+               regmatch_t *match, int eflags)
+{
+       return 1;
+}
+
+static void free_pcre_regexp(struct grep_pat *p)
+{
+}
+#endif /* !USE_LIBPCRE */
+
 static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
 {
        int err;
@@ -70,20 +148,17 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
        if (p->fixed)
                return;
 
+       if (opt->pcre) {
+               compile_pcre_regexp(p, opt);
+               return;
+       }
+
        err = regcomp(&p->regexp, p->pattern, opt->regflags);
        if (err) {
                char errbuf[1024];
-               char where[1024];
-               if (p->no)
-                       sprintf(where, "In '%s' at %d, ",
-                               p->origin, p->no);
-               else if (p->origin)
-                       sprintf(where, "%s, ", p->origin);
-               else
-                       where[0] = 0;
                regerror(err, &p->regexp, errbuf, 1024);
                regfree(&p->regexp);
-               die("%s'%s': %s", where, p->pattern, errbuf);
+               compile_regexp_failed(p, errbuf);
        }
 }
 
@@ -320,7 +395,10 @@ void free_grep_patterns(struct grep_opt *opt)
                case GREP_PATTERN: /* atom */
                case GREP_PATTERN_HEAD:
                case GREP_PATTERN_BODY:
-                       regfree(&p->regexp);
+                       if (p->pcre_regexp)
+                               free_pcre_regexp(p);
+                       else
+                               regfree(&p->regexp);
                        break;
                default:
                        break;
@@ -412,6 +490,21 @@ static int regmatch(const regex_t *preg, char *line, char *eol,
        return regexec(preg, line, 1, match, eflags);
 }
 
+static int patmatch(struct grep_pat *p, char *line, char *eol,
+                   regmatch_t *match, int eflags)
+{
+       int hit;
+
+       if (p->fixed)
+               hit = !fixmatch(p, line, eol, match);
+       else if (p->pcre_regexp)
+               hit = !pcrematch(p, line, eol, match, eflags);
+       else
+               hit = !regmatch(&p->regexp, line, eol, match, eflags);
+
+       return hit;
+}
+
 static int strip_timestamp(char *bol, char **eol_p)
 {
        char *eol = *eol_p;
@@ -461,10 +554,7 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
        }
 
  again:
-       if (p->fixed)
-               hit = !fixmatch(p, bol, eol, pmatch);
-       else
-               hit = !regmatch(&p->regexp, bol, eol, pmatch, eflags);
+       hit = patmatch(p, bol, eol, pmatch, eflags);
 
        if (hit && p->word_regexp) {
                if ((pmatch[0].rm_so < 0) ||
@@ -791,10 +881,7 @@ static int look_ahead(struct grep_opt *opt,
                int hit;
                regmatch_t m;
 
-               if (p->fixed)
-                       hit = !fixmatch(p, bol, bol + *left_p, &m);
-               else
-                       hit = !regmatch(&p->regexp, bol, bol + *left_p, &m, 0);
+               hit = patmatch(p, bol, bol + *left_p, &m, 0);
                if (!hit || m.rm_so < 0 || m.rm_eo < 0)
                        continue;
                if (earliest < 0 || m.rm_so < earliest)
@@ -891,7 +978,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                int hit;
 
                /*
-                * look_ahead() skips quicly to the line that possibly
+                * look_ahead() skips quickly to the line that possibly
                 * has the next hit; don't call it if we need to do
                 * something more than just skipping the current line
                 * in response to an unmatch for the current line.  E.g.
diff --git a/grep.h b/grep.h
index 06621fe663545af52fbc42827a8374ab5bd42f38..cd055cdfa8cac903382d592f1ec7e2a22bf7f897 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -1,6 +1,12 @@
 #ifndef GREP_H
 #define GREP_H
 #include "color.h"
+#ifdef USE_LIBPCRE
+#include <pcre.h>
+#else
+typedef int pcre;
+typedef int pcre_extra;
+#endif
 
 enum grep_pat_token {
        GREP_PATTERN,
@@ -33,6 +39,8 @@ struct grep_pat {
        size_t patternlen;
        enum grep_header_field field;
        regex_t regexp;
+       pcre *pcre_regexp;
+       pcre_extra *pcre_extra_info;
        unsigned fixed:1;
        unsigned ignore_case:1;
        unsigned word_regexp:1;
@@ -83,6 +91,7 @@ struct grep_opt {
 #define GREP_BINARY_TEXT       2
        int binary;
        int extended;
+       int pcre;
        int relative;
        int pathname;
        int null_following_name;
diff --git a/ident.c b/ident.c
index 8e56b5e941e85c9634d420a3e8140c328bca9c30..35a6f264737e700ddd45714a0e966422a0d756c7 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -9,6 +9,12 @@
 
 static char git_default_date[50];
 
+#ifdef NO_GECOS_IN_PWENT
+#define get_gecos(ignored) "&"
+#else
+#define get_gecos(struct_passwd) ((struct_passwd)->pw_gecos)
+#endif
+
 static void copy_gecos(const struct passwd *w, char *name, size_t sz)
 {
        char *src, *dst;
@@ -20,7 +26,7 @@ static void copy_gecos(const struct passwd *w, char *name, size_t sz)
         * with commas.  Also & stands for capitalized form of the login name.
         */
 
-       for (len = 0, dst = name, src = w->pw_gecos; len < sz; src++) {
+       for (len = 0, dst = name, src = get_gecos(w); len < sz; src++) {
                int ch = *src;
                if (ch != '&') {
                        *dst++ = ch;
index 28046a998426e88dec9ad6ab431624bb41a81ce1..e1aaf43b438d0cfb6b7b0c723060a662a915bcea 100644 (file)
@@ -707,7 +707,7 @@ int notes_merge_commit(struct notes_merge_options *o,
                /* write file as blob, and add to partial_tree */
                if (stat(ent->name, &st))
                        die_errno("Failed to stat '%s'", ent->name);
-               if (index_path(blob_sha1, ent->name, &st, 1))
+               if (index_path(blob_sha1, ent->name, &st, HASH_WRITE_OBJECT))
                        die("Failed to write blob object from '%s'", ent->name);
                if (add_note(partial_tree, obj_sha1, blob_sha1, NULL))
                        die("Failed to add resolved note '%s' to notes tree",
index dff5c8d1831ca4019d1a502a393cb95f20ad18f7..52174fd14722d131d533013120517f5fcb8d82f6 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -1084,7 +1084,7 @@ void userformat_find_requirements(const char *fmt, struct userformat_want *w)
                        return;
                fmt = user_format;
        }
-       strbuf_expand(&dummy, user_format, userformat_want_item, w);
+       strbuf_expand(&dummy, fmt, userformat_want_item, w);
        strbuf_release(&dummy);
 }
 
index f38471cac3ac57d8d52429cde2dcc4cf5b92557b..4ac9a037f478e769a69072c324a47876e298cae4 100644 (file)
@@ -92,7 +92,7 @@ static int ce_compare_data(struct cache_entry *ce, struct stat *st)
 
        if (fd >= 0) {
                unsigned char sha1[20];
-               if (!index_fd(sha1, fd, st, 0, OBJ_BLOB, ce->name, 0))
+               if (!index_fd(sha1, fd, st, OBJ_BLOB, ce->name, 0))
                        match = hashcmp(sha1, ce->sha1);
                /* index_fd() closed the file descriptor already */
        }
@@ -641,7 +641,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
                return 0;
        }
        if (!intent_only) {
-               if (index_path(ce->sha1, path, st, 1))
+               if (index_path(ce->sha1, path, st, HASH_WRITE_OBJECT))
                        return error("unable to index file %s", path);
        } else
                record_intent_to_add(ce);
index dee2cb1514d1e97c47a4999ed66f8996035d71fe..dcb525a4d03faeba53d108d2e175a6d04f99d160 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -47,8 +47,14 @@ static void read_rr(struct string_list *rr)
                name = xstrdup(buf);
                if (fgetc(in) != '\t')
                        die("corrupt MERGE_RR");
-               for (i = 0; i < sizeof(buf) && (buf[i] = fgetc(in)); i++)
-                       ; /* do nothing */
+               for (i = 0; i < sizeof(buf); i++) {
+                       int c = fgetc(in);
+                       if (c < 0)
+                               die("corrupt MERGE_RR");
+                       buf[i] = c;
+                       if (c == 0)
+                                break;
+               }
                if (i == sizeof(buf))
                        die("filename too long");
                string_list_insert(rr, buf)->util = name;
@@ -739,6 +745,7 @@ void rerere_gc(struct string_list *rr)
                if (then < now - cutoff * 86400)
                        string_list_append(&to_remove, e->d_name);
        }
+       closedir(dir);
        for (i = 0; i < to_remove.nr; i++)
                unlink_rr_item(to_remove.items[i].string);
        string_list_clear(&to_remove, 0);
index 7934b2f68bbaf9327ff3957aaa3eb316c2a7fb05..c46cfaa3e4d2f06fd67ccd71c7ba47891796fd60 100644 (file)
@@ -133,6 +133,8 @@ void mark_parents_uninteresting(struct commit *commit)
 
 static void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode)
 {
+       if (!obj)
+               return;
        if (revs->no_walk && (obj->flags & UNINTERESTING))
                revs->no_walk = 0;
        if (revs->reflog_info && obj->type == OBJ_COMMIT) {
@@ -174,8 +176,11 @@ static struct object *get_reference(struct rev_info *revs, const char *name, con
        struct object *object;
 
        object = parse_object(sha1);
-       if (!object)
+       if (!object) {
+               if (revs->ignore_missing)
+                       return object;
                die("bad object %s", name);
+       }
        object->flags |= flags;
        return object;
 }
@@ -906,6 +911,8 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
                return 0;
        while (1) {
                it = get_reference(revs, arg, sha1, 0);
+               if (!it && revs->ignore_missing)
+                       return 0;
                if (it->type != OBJ_TAG)
                        break;
                if (!((struct tag*)it)->tagged)
@@ -1044,6 +1051,8 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
                        a = lookup_commit_reference(from_sha1);
                        b = lookup_commit_reference(sha1);
                        if (!a || !b) {
+                               if (revs->ignore_missing)
+                                       return 0;
                                die(symmetric ?
                                    "Invalid symmetric difference expression %s...%s" :
                                    "Invalid revision range %s..%s",
@@ -1090,7 +1099,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
                arg++;
        }
        if (get_sha1_with_mode(arg, sha1, &mode))
-               return -1;
+               return revs->ignore_missing ? 0 : -1;
        if (!cant_be_filename)
                verify_non_filename(revs->prefix, arg);
        object = get_reference(revs, arg, sha1, flags ^ local_flags);
@@ -1428,6 +1437,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                        revs->abbrev = 40;
        } else if (!strcmp(arg, "--abbrev-commit")) {
                revs->abbrev_commit = 1;
+               revs->abbrev_commit_given = 1;
+       } else if (!strcmp(arg, "--no-abbrev-commit")) {
+               revs->abbrev_commit = 0;
        } else if (!strcmp(arg, "--full-diff")) {
                revs->diff = 1;
                revs->full_diff = 1;
@@ -1474,6 +1486,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
        } else if (!strcmp(arg, "--children")) {
                revs->children.name = "children";
                revs->limited = 1;
+       } else if (!strcmp(arg, "--ignore-missing")) {
+               revs->ignore_missing = 1;
        } else {
                int opts = diff_opt_parse(&revs->diffopt, argv, argc);
                if (!opts)
index bca9947977c50d151b0fed90931ec52112e72bae..4499cebded6bc4348c0648a77d6e0dae4cf00acf 100644 (file)
@@ -36,7 +36,8 @@ struct rev_info {
        const char *prefix;
        const char *def;
        struct pathspec prune_data;
-       unsigned int early_output;
+       unsigned int    early_output:1,
+                       ignore_missing:1;
 
        /* Traversal flags */
        unsigned int    dense:1,
@@ -90,6 +91,7 @@ struct rev_info {
                        show_notes_given:1,
                        pretty_given:1,
                        abbrev_commit:1,
+                       abbrev_commit_given:1,
                        use_terminator:1,
                        missing_newline:1,
                        date_mode_explicit:1;
diff --git a/setup.c b/setup.c
index 013ad1127534367e64afeec3749e617ceccfb5d4..ce87900ce3c68ace0f231827bdda0e9a65ef15b3 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -382,7 +382,7 @@ const char *read_gitfile_gently(const char *path)
        const char *slash;
        struct stat st;
        int fd;
-       size_t len;
+       ssize_t len;
 
        if (stat(path, &st))
                return NULL;
index 71250930dba09fbc6b1b638230cfa30253915a26..2eb0ee44b87a08750e3789ff9132d55a6abd96f9 100644 (file)
@@ -68,7 +68,7 @@ int
 main (int argc, char *argv[])
 {
   /* Default values for command line options.  */
-  unsigned short int show_variables = 0;
+  /* unsigned short int show_variables = 0; */
 
   switch (argc)
        {
@@ -88,7 +88,7 @@ main (int argc, char *argv[])
          /* git sh-i18n--envsubst --variables '$foo and $bar' */
          if (strcmp(argv[1], "--variables"))
                error ("first argument must be --variables when two are given");
-         show_variables = 1;
+         /* show_variables = 1; */
       print_variables (argv[2]);
          break;
        default:
diff --git a/sha1-array.c b/sha1-array.c
new file mode 100644 (file)
index 0000000..b2f47f9
--- /dev/null
@@ -0,0 +1,59 @@
+#include "cache.h"
+#include "sha1-array.h"
+#include "sha1-lookup.h"
+
+void sha1_array_append(struct sha1_array *array, const unsigned char *sha1)
+{
+       ALLOC_GROW(array->sha1, array->nr + 1, array->alloc);
+       hashcpy(array->sha1[array->nr++], sha1);
+       array->sorted = 0;
+}
+
+static int void_hashcmp(const void *a, const void *b)
+{
+       return hashcmp(a, b);
+}
+
+void sha1_array_sort(struct sha1_array *array)
+{
+       qsort(array->sha1, array->nr, sizeof(*array->sha1), void_hashcmp);
+       array->sorted = 1;
+}
+
+static const unsigned char *sha1_access(size_t index, void *table)
+{
+       unsigned char (*array)[20] = table;
+       return array[index];
+}
+
+int sha1_array_lookup(struct sha1_array *array, const unsigned char *sha1)
+{
+       if (!array->sorted)
+               sha1_array_sort(array);
+       return sha1_pos(sha1, array->sha1, array->nr, sha1_access);
+}
+
+void sha1_array_clear(struct sha1_array *array)
+{
+       free(array->sha1);
+       array->sha1 = NULL;
+       array->nr = 0;
+       array->alloc = 0;
+       array->sorted = 0;
+}
+
+void sha1_array_for_each_unique(struct sha1_array *array,
+                               for_each_sha1_fn fn,
+                               void *data)
+{
+       int i;
+
+       if (!array->sorted)
+               sha1_array_sort(array);
+
+       for (i = 0; i < array->nr; i++) {
+               if (i > 0 && !hashcmp(array->sha1[i], array->sha1[i-1]))
+                       continue;
+               fn(array->sha1[i], data);
+       }
+}
diff --git a/sha1-array.h b/sha1-array.h
new file mode 100644 (file)
index 0000000..4499b5d
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef SHA1_ARRAY_H
+#define SHA1_ARRAY_H
+
+struct sha1_array {
+       unsigned char (*sha1)[20];
+       int nr;
+       int alloc;
+       int sorted;
+};
+
+#define SHA1_ARRAY_INIT { NULL, 0, 0, 0 }
+
+void sha1_array_append(struct sha1_array *array, const unsigned char *sha1);
+void sha1_array_sort(struct sha1_array *array);
+int sha1_array_lookup(struct sha1_array *array, const unsigned char *sha1);
+void sha1_array_clear(struct sha1_array *array);
+
+typedef void (*for_each_sha1_fn)(const unsigned char sha1[20],
+                                void *data);
+void sha1_array_for_each_unique(struct sha1_array *array,
+                               for_each_sha1_fn fn,
+                               void *data);
+
+#endif /* SHA1_ARRAY_H */
index 357f9ab7ff6aad644857986f36fd55da8348744f..064a33040812ba8782bf602c693abf08613d6ec7 100644 (file)
@@ -11,6 +11,7 @@
 #include "pack.h"
 #include "blob.h"
 #include "commit.h"
+#include "run-command.h"
 #include "tag.h"
 #include "tree.h"
 #include "tree-walk.h"
@@ -2578,10 +2579,11 @@ static void check_tag(const void *buf, size_t size)
 }
 
 static int index_mem(unsigned char *sha1, void *buf, size_t size,
-                    int write_object, enum object_type type,
-                    const char *path, int format_check)
+                    enum object_type type,
+                    const char *path, unsigned flags)
 {
        int ret, re_allocated = 0;
+       int write_object = flags & HASH_WRITE_OBJECT;
 
        if (!type)
                type = OBJ_BLOB;
@@ -2597,7 +2599,7 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size,
                        re_allocated = 1;
                }
        }
-       if (format_check) {
+       if (flags & HASH_FORMAT_CHECK) {
                if (type == OBJ_TREE)
                        check_tree(buf, size);
                if (type == OBJ_COMMIT)
@@ -2615,44 +2617,141 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size,
        return ret;
 }
 
+static int index_pipe(unsigned char *sha1, int fd, enum object_type type,
+                     const char *path, unsigned flags)
+{
+       struct strbuf sbuf = STRBUF_INIT;
+       int ret;
+
+       if (strbuf_read(&sbuf, fd, 4096) >= 0)
+               ret = index_mem(sha1, sbuf.buf, sbuf.len, type, path, flags);
+       else
+               ret = -1;
+       strbuf_release(&sbuf);
+       return ret;
+}
+
 #define SMALL_FILE_SIZE (32*1024)
 
-int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
-            enum object_type type, const char *path, int format_check)
+static int index_core(unsigned char *sha1, int fd, size_t size,
+                     enum object_type type, const char *path,
+                     unsigned flags)
 {
        int ret;
-       size_t size = xsize_t(st->st_size);
 
-       if (!S_ISREG(st->st_mode)) {
-               struct strbuf sbuf = STRBUF_INIT;
-               if (strbuf_read(&sbuf, fd, 4096) >= 0)
-                       ret = index_mem(sha1, sbuf.buf, sbuf.len, write_object,
-                                       type, path, format_check);
-               else
-                       ret = -1;
-               strbuf_release(&sbuf);
-       } else if (!size) {
-               ret = index_mem(sha1, NULL, size, write_object, type, path,
-                               format_check);
+       if (!size) {
+               ret = index_mem(sha1, NULL, size, type, path, flags);
        } else if (size <= SMALL_FILE_SIZE) {
                char *buf = xmalloc(size);
                if (size == read_in_full(fd, buf, size))
-                       ret = index_mem(sha1, buf, size, write_object, type,
-                                       path, format_check);
+                       ret = index_mem(sha1, buf, size, type, path, flags);
                else
                        ret = error("short read %s", strerror(errno));
                free(buf);
        } else {
                void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
-               ret = index_mem(sha1, buf, size, write_object, type, path,
-                               format_check);
+               ret = index_mem(sha1, buf, size, type, path, flags);
                munmap(buf, size);
        }
+       return ret;
+}
+
+/*
+ * This creates one packfile per large blob, because the caller
+ * immediately wants the result sha1, and fast-import can report the
+ * object name via marks mechanism only by closing the created
+ * packfile.
+ *
+ * This also bypasses the usual "convert-to-git" dance, and that is on
+ * purpose. We could write a streaming version of the converting
+ * functions and insert that before feeding the data to fast-import
+ * (or equivalent in-core API described above), but the primary
+ * motivation for trying to stream from the working tree file and to
+ * avoid mmaping it in core is to deal with large binary blobs, and
+ * by definition they do _not_ want to get any conversion.
+ */
+static int index_stream(unsigned char *sha1, int fd, size_t size,
+                       enum object_type type, const char *path,
+                       unsigned flags)
+{
+       struct child_process fast_import;
+       char export_marks[512];
+       const char *argv[] = { "fast-import", "--quiet", export_marks, NULL };
+       char tmpfile[512];
+       char fast_import_cmd[512];
+       char buf[512];
+       int len, tmpfd;
+
+       strcpy(tmpfile, git_path("hashstream_XXXXXX"));
+       tmpfd = git_mkstemp_mode(tmpfile, 0600);
+       if (tmpfd < 0)
+               die_errno("cannot create tempfile: %s", tmpfile);
+       if (close(tmpfd))
+               die_errno("cannot close tempfile: %s", tmpfile);
+       sprintf(export_marks, "--export-marks=%s", tmpfile);
+
+       memset(&fast_import, 0, sizeof(fast_import));
+       fast_import.in = -1;
+       fast_import.argv = argv;
+       fast_import.git_cmd = 1;
+       if (start_command(&fast_import))
+               die_errno("index-stream: git fast-import failed");
+
+       len = sprintf(fast_import_cmd, "blob\nmark :1\ndata %lu\n",
+                     (unsigned long) size);
+       write_or_whine(fast_import.in, fast_import_cmd, len,
+                      "index-stream: feeding fast-import");
+       while (size) {
+               char buf[10240];
+               size_t sz = size < sizeof(buf) ? size : sizeof(buf);
+               size_t actual;
+
+               actual = read_in_full(fd, buf, sz);
+               if (actual < 0)
+                       die_errno("index-stream: reading input");
+               if (write_in_full(fast_import.in, buf, actual) != actual)
+                       die_errno("index-stream: feeding fast-import");
+               size -= actual;
+       }
+       if (close(fast_import.in))
+               die_errno("index-stream: closing fast-import");
+       if (finish_command(&fast_import))
+               die_errno("index-stream: finishing fast-import");
+
+       tmpfd = open(tmpfile, O_RDONLY);
+       if (tmpfd < 0)
+               die_errno("index-stream: cannot open fast-import mark");
+       len = read(tmpfd, buf, sizeof(buf));
+       if (len < 0)
+               die_errno("index-stream: reading fast-import mark");
+       if (close(tmpfd) < 0)
+               die_errno("index-stream: closing fast-import mark");
+       if (unlink(tmpfile))
+               die_errno("index-stream: unlinking fast-import mark");
+       if (len != 44 ||
+           memcmp(":1 ", buf, 3) ||
+           get_sha1_hex(buf + 3, sha1))
+               die_errno("index-stream: unexpected fast-import mark: <%s>", buf);
+       return 0;
+}
+
+int index_fd(unsigned char *sha1, int fd, struct stat *st,
+            enum object_type type, const char *path, unsigned flags)
+{
+       int ret;
+       size_t size = xsize_t(st->st_size);
+
+       if (!S_ISREG(st->st_mode))
+               ret = index_pipe(sha1, fd, type, path, flags);
+       else if (size <= big_file_threshold || type != OBJ_BLOB)
+               ret = index_core(sha1, fd, size, type, path, flags);
+       else
+               ret = index_stream(sha1, fd, size, type, path, flags);
        close(fd);
        return ret;
 }
 
-int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object)
+int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags)
 {
        int fd;
        struct strbuf sb = STRBUF_INIT;
@@ -2663,7 +2762,7 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write
                if (fd < 0)
                        return error("open(\"%s\"): %s", path,
                                     strerror(errno));
-               if (index_fd(sha1, fd, st, write_object, OBJ_BLOB, path, 0) < 0)
+               if (index_fd(sha1, fd, st, OBJ_BLOB, path, flags) < 0)
                        return error("%s: failed to insert into database",
                                     path);
                break;
@@ -2673,7 +2772,7 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write
                        return error("readlink(\"%s\"): %s", path,
                                     errstr);
                }
-               if (!write_object)
+               if (!(flags & HASH_WRITE_OBJECT))
                        hash_sha1_file(sb.buf, sb.len, blob_type, sha1);
                else if (write_sha1_file(sb.buf, sb.len, blob_type, sha1))
                        return error("%s: failed to insert into database",
index cad36dd7502b81ef8e27476e9686562547b5a1c0..c85abaffb3b8c2142e87f6e0525fa67c6b62c1a1 100644 (file)
--- a/t/README
+++ b/t/README
@@ -588,6 +588,11 @@ use these, and "test_set_prereq" for how to define your own.
    Test is not run by root user, and an attempt to write to an
    unwritable file is expected to fail correctly.
 
+ - LIBPCRE
+
+   Git was compiled with USE_LIBPCRE=YesPlease. Wrap any tests
+   that use git-grep --perl-regexp or git-grep -P in these.
+
 Tips for Writing Tests
 ----------------------
 
index 8106af8fba918b8de82857d40a7f693224c84e81..ad664105646d4579a4e31ea5d230268acc33c0f5 100755 (executable)
@@ -409,7 +409,7 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' '
        cd newdir &&
        mv .git here &&
        ln -s here .git &&
-       git init -L ../realgitdir
+       git init --separate-git-dir ../realgitdir
        ) &&
        echo "gitdir: `pwd`/realgitdir" >expected &&
        test_cmp expected newdir/.git &&
index 5067d1e15b566721c1a3ae79052cbcb023a03656..bd83ed371acb5075b2b3cbb7866d36aa4bd48519 100755 (executable)
@@ -2,74 +2,14 @@
 
 test_description="Test the svn importer's input handling routines.
 
-These tests exercise the line_buffer library, but their real purpose
-is to check the assumptions that library makes of the platform's input
-routines.  Processes engaged in bi-directional communication would
-hang if fread or fgets is too greedy.
+These tests provide some simple checks that the line_buffer API
+behaves as advertised.
 
 While at it, check that input of newlines and null bytes are handled
 correctly.
 "
 . ./test-lib.sh
 
-test -n "$GIT_REMOTE_SVN_TEST_BIG_FILES" && test_set_prereq EXPENSIVE
-
-generate_tens_of_lines () {
-       tens=$1 &&
-       line=$2 &&
-
-       i=0 &&
-       while test $i -lt "$tens"
-       do
-               for j in a b c d e f g h i j
-               do
-                       echo "$line"
-               done &&
-               : $((i = $i + 1)) ||
-               return
-       done
-}
-
-long_read_test () {
-       : each line is 10 bytes, including newline &&
-       line=abcdefghi &&
-       echo "$line" >expect &&
-
-       if ! test_declared_prereq PIPE
-       then
-               echo >&4 "long_read_test: need to declare PIPE prerequisite"
-               return 127
-       fi &&
-       tens_of_lines=$(($1 / 100 + 1)) &&
-       lines=$(($tens_of_lines * 10)) &&
-       readsize=$((($lines - 1) * 10 + 3)) &&
-       copysize=7 &&
-       rm -f input &&
-       mkfifo input &&
-       {
-               (
-                       generate_tens_of_lines $tens_of_lines "$line" &&
-                       exec sleep 100
-               ) >input &
-       } &&
-       test-line-buffer input <<-EOF >output &&
-       binary $readsize
-       copy $copysize
-       EOF
-       kill $! &&
-       test_line_count = $lines output &&
-       tail -n 1 <output >actual &&
-       test_cmp expect actual
-}
-
-test_expect_success 'setup: have pipes?' '
-      rm -f frob &&
-      if mkfifo frob
-      then
-               test_set_prereq PIPE
-      fi
-'
-
 test_expect_success 'hello world' '
        echo ">HELLO" >expect &&
        test-line-buffer <<-\EOF >actual &&
@@ -79,21 +19,6 @@ test_expect_success 'hello world' '
        test_cmp expect actual
 '
 
-test_expect_success PIPE '0-length read, no input available' '
-       printf ">" >expect &&
-       rm -f input &&
-       mkfifo input &&
-       {
-               sleep 100 >input &
-       } &&
-       test-line-buffer input <<-\EOF >actual &&
-       binary 0
-       copy 0
-       EOF
-       kill $! &&
-       test_cmp expect actual
-'
-
 test_expect_success '0-length read, send along greeting' '
        echo ">HELLO" >expect &&
        test-line-buffer <<-\EOF >actual &&
@@ -104,33 +29,6 @@ test_expect_success '0-length read, send along greeting' '
        test_cmp expect actual
 '
 
-test_expect_success PIPE '1-byte read, no input available' '
-       printf ">%s" ab >expect &&
-       rm -f input &&
-       mkfifo input &&
-       {
-               (
-                       printf "%s" a &&
-                       printf "%s" b &&
-                       exec sleep 100
-               ) >input &
-       } &&
-       test-line-buffer input <<-\EOF >actual &&
-       binary 1
-       copy 1
-       EOF
-       kill $! &&
-       test_cmp expect actual
-'
-
-test_expect_success PIPE 'long read (around 8192 bytes)' '
-       long_read_test 8192
-'
-
-test_expect_success PIPE,EXPENSIVE 'longer read (around 65536 bytes)' '
-       long_read_test 65536
-'
-
 test_expect_success 'read from file descriptor' '
        rm -f input &&
        echo hello >expect &&
diff --git a/t/t1050-large.sh b/t/t1050-large.sh
new file mode 100755 (executable)
index 0000000..deba111
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Copyright (c) 2011, Google Inc.
+
+test_description='adding and checking out large blobs'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       git config core.bigfilethreshold 200k &&
+       echo X | dd of=large bs=1k seek=2000
+'
+
+test_expect_success 'add a large file' '
+       git add large &&
+       # make sure we got a packfile and no loose objects
+       test -f .git/objects/pack/pack-*.pack &&
+       test ! -f .git/objects/??/??????????????????????????????????????
+'
+
+test_expect_success 'checkout a large file' '
+       large=$(git rev-parse :large) &&
+       git update-index --add --cacheinfo 100644 $large another &&
+       git checkout another &&
+       cmp large another ;# this must not be test_cmp
+'
+
+test_done
index 53fb8228cf18e2b58f3ea63e98a0220fa0fd39f2..3db56267ee89e1b585a548f7bee13386e47395f4 100755 (executable)
@@ -897,4 +897,11 @@ test_expect_success 'key sanity-checking' '
        git config foo."ba =z".bar false
 '
 
+test_expect_success 'git -c works with aliases of builtins' '
+       git config alias.checkconfig "-c foo.check=bar config foo.check" &&
+       echo bar >expect &&
+       git checkconfig >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 28e17c8920cbe9f2b13ff5cc30939eab8171bae2..16de05aff941c73b3e5f5371bd4739959f679efa 100755 (executable)
@@ -435,6 +435,81 @@ test_expect_success 'removing non-existing note should not create new commit' '
        test_cmp before_commit after_commit
 '
 
+test_expect_success 'removing more than one' '
+       before=$(git rev-parse --verify refs/notes/commits) &&
+       test_when_finished "git update-ref refs/notes/commits $before" &&
+
+       # We have only two -- add another and make sure it stays
+       git notes add -m "extra" &&
+       git notes list HEAD >after-removal-expect &&
+       git notes remove HEAD^^ HEAD^^^ &&
+       git notes list | sed -e "s/ .*//" >actual &&
+       test_cmp after-removal-expect actual
+'
+
+test_expect_success 'removing is atomic' '
+       before=$(git rev-parse --verify refs/notes/commits) &&
+       test_when_finished "git update-ref refs/notes/commits $before" &&
+       test_must_fail git notes remove HEAD^^ HEAD^^^ HEAD^ &&
+       after=$(git rev-parse --verify refs/notes/commits) &&
+       test "$before" = "$after"
+'
+
+test_expect_success 'removing with --ignore-missing' '
+       before=$(git rev-parse --verify refs/notes/commits) &&
+       test_when_finished "git update-ref refs/notes/commits $before" &&
+
+       # We have only two -- add another and make sure it stays
+       git notes add -m "extra" &&
+       git notes list HEAD >after-removal-expect &&
+       git notes remove --ignore-missing HEAD^^ HEAD^^^ HEAD^ &&
+       git notes list | sed -e "s/ .*//" >actual &&
+       test_cmp after-removal-expect actual
+'
+
+test_expect_success 'removing with --ignore-missing but bogus ref' '
+       before=$(git rev-parse --verify refs/notes/commits) &&
+       test_when_finished "git update-ref refs/notes/commits $before" &&
+       test_must_fail git notes remove --ignore-missing HEAD^^ HEAD^^^ NO-SUCH-COMMIT &&
+       after=$(git rev-parse --verify refs/notes/commits) &&
+       test "$before" = "$after"
+'
+
+test_expect_success 'remove reads from --stdin' '
+       before=$(git rev-parse --verify refs/notes/commits) &&
+       test_when_finished "git update-ref refs/notes/commits $before" &&
+
+       # We have only two -- add another and make sure it stays
+       git notes add -m "extra" &&
+       git notes list HEAD >after-removal-expect &&
+       git rev-parse HEAD^^ HEAD^^^ >input &&
+       git notes remove --stdin <input &&
+       git notes list | sed -e "s/ .*//" >actual &&
+       test_cmp after-removal-expect actual
+'
+
+test_expect_success 'remove --stdin is also atomic' '
+       before=$(git rev-parse --verify refs/notes/commits) &&
+       test_when_finished "git update-ref refs/notes/commits $before" &&
+       git rev-parse HEAD^^ HEAD^^^ HEAD^ >input &&
+       test_must_fail git notes remove --stdin <input &&
+       after=$(git rev-parse --verify refs/notes/commits) &&
+       test "$before" = "$after"
+'
+
+test_expect_success 'removing with --stdin --ignore-missing' '
+       before=$(git rev-parse --verify refs/notes/commits) &&
+       test_when_finished "git update-ref refs/notes/commits $before" &&
+
+       # We have only two -- add another and make sure it stays
+       git notes add -m "extra" &&
+       git notes list HEAD >after-removal-expect &&
+       git rev-parse HEAD^^ HEAD^^^ HEAD^ >input &&
+       git notes remove --ignore-missing --stdin <input &&
+       git notes list | sed -e "s/ .*//" >actual &&
+       test_cmp after-removal-expect actual
+'
+
 test_expect_success 'list notes with "git notes list"' '
        git notes list > output &&
        test_cmp expect output
index 364693062399228cd6a18b52a21db173519d8e94..b68c56b68c0f80a0d96b513733bc5fb17db6fed4 100755 (executable)
@@ -9,8 +9,7 @@ test_description='Test custom diff function name patterns'
 
 LF='
 '
-
-cat > Beer.java << EOF
+cat >Beer.java <<\EOF
 public class Beer
 {
        int special;
@@ -29,61 +28,163 @@ public class Beer
        }
 }
 EOF
+sed 's/beer\\/beer,\\/' <Beer.java >Beer-correct.java
+cat >Beer.perl <<\EOT
+package Beer;
+
+use strict;
+use warnings;
+use parent qw(Exporter);
+our @EXPORT_OK = qw(round finalround);
+
+sub other; # forward declaration
+
+# hello
+
+sub round {
+       my ($n) = @_;
+       print "$n bottles of beer on the wall ";
+       print "$n bottles of beer\n";
+       print "Take one down, pass it around, ";
+       $n = $n - 1;
+       print "$n bottles of beer on the wall.\n";
+}
+
+sub finalround
+{
+       print "Go to the store, buy some more\n";
+       print "99 bottles of beer on the wall.\n");
+}
+
+sub withheredocument {
+       print <<"EOF"
+decoy here-doc
+EOF
+       # some lines of context
+       # to pad it out
+       print "hello\n";
+}
+
+__END__
+
+=head1 NAME
+
+Beer - subroutine to output fragment of a drinking song
+
+=head1 SYNOPSIS
+
+       use Beer qw(round finalround);
+
+       sub song {
+               for (my $i = 99; $i > 0; $i--) {
+                       round $i;
+               }
+               finalround;
+       }
 
-sed 's/beer\\/beer,\\/' < Beer.java > Beer-correct.java
+       song;
 
-builtin_patterns="bibtex cpp csharp fortran html java objc pascal perl php python ruby tex"
-for p in $builtin_patterns
+=cut
+EOT
+sed -e '
+       s/hello/goodbye/
+       s/beer\\/beer,\\/
+       s/more\\/more,\\/
+       s/song;/song();/
+' <Beer.perl >Beer-correct.perl
+
+test_config () {
+       git config "$1" "$2" &&
+       test_when_finished "git config --unset $1"
+}
+
+test_expect_funcname () {
+       lang=${2-java}
+       test_expect_code 1 git diff --no-index -U1 \
+               "Beer.$lang" "Beer-correct.$lang" >diff &&
+       grep "^@@.*@@ $1" diff
+}
+
+for p in bibtex cpp csharp fortran html java objc pascal perl php python ruby tex
 do
        test_expect_success "builtin $p pattern compiles" '
-               echo "*.java diff=$p" > .gitattributes &&
-               ! { git diff --no-index Beer.java Beer-correct.java 2>&1 |
-                       grep "fatal" > /dev/null; }
+               echo "*.java diff=$p" >.gitattributes &&
+               test_expect_code 1 git diff --no-index \
+                       Beer.java Beer-correct.java 2>msg &&
+               ! grep fatal msg &&
+               ! grep error msg
        '
        test_expect_success "builtin $p wordRegex pattern compiles" '
-               ! { git diff --no-index --word-diff \
-                       Beer.java Beer-correct.java 2>&1 |
-                       grep "fatal" > /dev/null; }
+               echo "*.java diff=$p" >.gitattributes &&
+               test_expect_code 1 git diff --no-index --word-diff \
+                       Beer.java Beer-correct.java 2>msg &&
+               ! grep fatal msg &&
+               ! grep error msg
        '
 done
 
 test_expect_success 'default behaviour' '
        rm -f .gitattributes &&
-       git diff --no-index Beer.java Beer-correct.java |
-       grep "^@@.*@@ public class Beer"
+       test_expect_funcname "public class Beer\$"
+'
+
+test_expect_success 'set up .gitattributes declaring drivers to test' '
+       cat >.gitattributes <<-\EOF
+       *.java diff=java
+       *.perl diff=perl
+       EOF
 '
 
 test_expect_success 'preset java pattern' '
-       echo "*.java diff=java" >.gitattributes &&
-       git diff --no-index Beer.java Beer-correct.java |
-       grep "^@@.*@@ public static void main("
+       test_expect_funcname "public static void main("
 '
 
-git config diff.java.funcname '!static
-!String
-[^     ].*s.*'
+test_expect_success 'preset perl pattern' '
+       test_expect_funcname "sub round {\$" perl
+'
+
+test_expect_success 'perl pattern accepts K&R style brace placement, too' '
+       test_expect_funcname "sub finalround\$" perl
+'
+
+test_expect_success 'but is not distracted by end of <<here document' '
+       test_expect_funcname "sub withheredocument {\$" perl
+'
+
+test_expect_success 'perl pattern is not distracted by sub within POD' '
+       test_expect_funcname "=head" perl
+'
+
+test_expect_success 'perl pattern gets full line of POD header' '
+       test_expect_funcname "=head1 SYNOPSIS\$" perl
+'
+
+test_expect_success 'perl pattern is not distracted by forward declaration' '
+       test_expect_funcname "package Beer;\$" perl
+'
 
 test_expect_success 'custom pattern' '
-       git diff --no-index Beer.java Beer-correct.java |
-       grep "^@@.*@@ int special;$"
+       test_config diff.java.funcname "!static
+!String
+[^     ].*s.*" &&
+       test_expect_funcname "int special;\$"
 '
 
 test_expect_success 'last regexp must not be negated' '
-       git config diff.java.funcname "!static" &&
-       git diff --no-index Beer.java Beer-correct.java 2>&1 |
-       grep "fatal: Last expression must not be negated:"
+       test_config diff.java.funcname "!static" &&
+       test_expect_code 128 git diff --no-index Beer.java Beer-correct.java 2>msg &&
+       grep ": Last expression must not be negated:" msg
 '
 
 test_expect_success 'pattern which matches to end of line' '
-       git config diff.java.funcname "Beer$" &&
-       git diff --no-index Beer.java Beer-correct.java |
-       grep "^@@.*@@ Beer"
+       test_config diff.java.funcname "Beer\$" &&
+       test_expect_funcname "Beer\$"
 '
 
 test_expect_success 'alternation in pattern' '
-       git config diff.java.xfuncname "^[      ]*((public|static).*)$" &&
-       git diff --no-index Beer.java Beer-correct.java |
-       grep "^@@.*@@ public static void main("
+       test_config diff.java.funcname "Beer$" &&
+       test_config diff.java.xfuncname "^[     ]*((public|static).*)$" &&
+       test_expect_funcname "public static void main("
 '
 
 test_done
index 2fcc31a6f3d346e533b697eecf853e219d174cd0..983e34bec67a4cc4ba1182332e495377cc879ac9 100755 (executable)
@@ -448,6 +448,59 @@ test_expect_success 'log.decorate configuration' '
        git log --oneline --decorate >actual &&
        test_cmp expect.short actual
 
+       git config --unset-all log.decorate &&
+       git log --pretty=raw >expect.raw &&
+       git config log.decorate full &&
+       git log --pretty=raw >actual &&
+       test_cmp expect.raw actual
+
+'
+
+test_expect_success 'reflog is expected format' '
+       test_might_fail git config --remove-section log &&
+       git log -g --abbrev-commit --pretty=oneline >expect &&
+       git reflog >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'whatchanged is expected format' '
+       git log --no-merges --raw >expect &&
+       git whatchanged >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log.abbrevCommit configuration' '
+       test_when_finished "git config --unset log.abbrevCommit" &&
+
+       test_might_fail git config --unset log.abbrevCommit &&
+
+       git log --abbrev-commit >expect.log.abbrev &&
+       git log --no-abbrev-commit >expect.log.full &&
+       git log --pretty=raw >expect.log.raw &&
+       git reflog --abbrev-commit >expect.reflog.abbrev &&
+       git reflog --no-abbrev-commit >expect.reflog.full &&
+       git whatchanged --abbrev-commit >expect.whatchanged.abbrev &&
+       git whatchanged --no-abbrev-commit >expect.whatchanged.full &&
+
+       git config log.abbrevCommit true &&
+
+       git log >actual &&
+       test_cmp expect.log.abbrev actual &&
+       git log --no-abbrev-commit >actual &&
+       test_cmp expect.log.full actual &&
+
+       git log --pretty=raw >actual &&
+       test_cmp expect.log.raw actual &&
+
+       git reflog >actual &&
+       test_cmp expect.reflog.abbrev actual &&
+       git reflog --no-abbrev-commit >actual &&
+       test_cmp expect.reflog.full actual &&
+
+       git whatchanged >actual &&
+       test_cmp expect.whatchanged.abbrev actual &&
+       git whatchanged --no-abbrev-commit >actual &&
+       test_cmp expect.whatchanged.full actual
 '
 
 test_expect_success 'show added path under "--follow -M"' '
index 8184c264cf942bcd8e6c2f75b5526ddfdc5a11e9..69bd576d1c1a0bfcd086b68d8dc07c03c22b7a5a 100755 (executable)
@@ -26,6 +26,17 @@ test_expect_success setup '
                echo foo mmap bar_mmap
                echo foo_mmap bar mmap baz
        } >file &&
+       {
+               echo Hello world
+               echo HeLLo world
+               echo Hello_world
+               echo HeLLo_world
+       } >hello_world &&
+       {
+               echo "a+b*c"
+               echo "a+bc"
+               echo "abc"
+       } >ab &&
        echo vvv >v &&
        echo ww w >w &&
        echo x x xx x >x &&
@@ -221,7 +232,17 @@ do
                git grep --max-depth 0 -n -e vvv $H -- t . >actual &&
                test_cmp expected actual
        '
+       test_expect_success "grep $L with grep.extendedRegexp=false" '
+               echo "ab:a+bc" >expected &&
+               git -c grep.extendedRegexp=false grep "a+b*c" ab >actual &&
+               test_cmp expected actual
+       '
 
+       test_expect_success "grep $L with grep.extendedRegexp=true" '
+               echo "ab:abc" >expected &&
+               git -c grep.extendedRegexp=true grep "a+b*c" ab >actual &&
+               test_cmp expected actual
+       '
 done
 
 cat >expected <<EOF
@@ -599,4 +620,100 @@ test_expect_success 'grep -e -- -- path' '
        test_cmp expected actual
 '
 
+cat >expected <<EOF
+hello.c:int main(int argc, const char **argv)
+hello.c:       printf("Hello world.\n");
+EOF
+
+test_expect_success LIBPCRE 'grep --perl-regexp pattern' '
+       git grep --perl-regexp "\p{Ps}.*?\p{Pe}" hello.c >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success LIBPCRE 'grep -P pattern' '
+       git grep -P "\p{Ps}.*?\p{Pe}" hello.c >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'grep pattern with grep.extendedRegexp=true' '
+       >empty &&
+       test_must_fail git -c grep.extendedregexp=true \
+               grep "\p{Ps}.*?\p{Pe}" hello.c >actual &&
+       test_cmp empty actual
+'
+
+test_expect_success LIBPCRE 'grep -P pattern with grep.extendedRegexp=true' '
+       git -c grep.extendedregexp=true \
+               grep -P "\p{Ps}.*?\p{Pe}" hello.c >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success LIBPCRE 'grep -P -v pattern' '
+       {
+               echo "ab:a+b*c"
+               echo "ab:a+bc"
+       } >expected &&
+       git grep -P -v "abc" ab >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success LIBPCRE 'grep -P -i pattern' '
+       {
+               echo "hello.c:  printf(\"Hello world.\n\");"
+       } >expected &&
+       git grep -P -i "PRINTF\([^\d]+\)" hello.c >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success LIBPCRE 'grep -P -w pattern' '
+       {
+               echo "hello_world:Hello world"
+               echo "hello_world:HeLLo world"
+       } >expected &&
+       git grep -P -w "He((?i)ll)o" hello_world >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'grep -G invalidpattern properly dies ' '
+       test_must_fail git grep -G "a["
+'
+
+test_expect_success 'grep -E invalidpattern properly dies ' '
+       test_must_fail git grep -E "a["
+'
+
+test_expect_success LIBPCRE 'grep -P invalidpattern properly dies ' '
+       test_must_fail git grep -P "a["
+'
+
+test_expect_success 'grep -G -E -F pattern' '
+       echo "ab:a+b*c" >expected &&
+       git grep -G -E -F "a+b*c" ab >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'grep -E -F -G pattern' '
+       echo "ab:a+bc" >expected &&
+       git grep -E -F -G "a+b*c" ab >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'grep -F -G -E pattern' '
+       echo "ab:abc" >expected &&
+       git grep -F -G -E "a+b*c" ab >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'grep -G -F -P -E pattern' '
+       >empty &&
+       test_must_fail git grep -G -F -P -E "a\x{2b}b\x{2a}c" ab >actual &&
+       test_cmp empty actual
+'
+
+test_expect_success LIBPCRE 'grep -G -F -E -P pattern' '
+       echo "ab:a+b*c" >expected &&
+       git grep -G -F -E -P "a\x{2b}b\x{2a}c" ab >actual &&
+       test_cmp expected actual
+'
+
 test_done
index 71ef0acb1ba64d36a7909b5308ab6b6e89c31466..f5648a669481e66f3cbbb87b769f0f939aad8fab 100755 (executable)
@@ -644,4 +644,12 @@ test_expect_success \
        'ctags: search projects by non existent tag' \
        'gitweb_run "by_tag=non-existent"'
 
+# ----------------------------------------------------------------------
+# categories
+
+test_expect_success \
+       'categories: projects list, only default category' \
+       'echo "\$projects_list_group_categories = 1;" >>gitweb_config.perl &&
+        gitweb_run'
+
 test_done
index b2ce2bc4b22f43c0b324ab3f596a7f865fa58cc1..64390d716d68b79b5a0915a707c95f5c2df39a46 100644 (file)
@@ -731,12 +731,11 @@ test_expect_code () {
        exit_code=$?
        if test $exit_code = $want_code
        then
-               echo >&2 "test_expect_code: command exited with $exit_code: $*"
                return 0
-       else
-               echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*"
-               return 1
        fi
+
+       echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*"
+       return 1
 }
 
 # test_cmp is a helper function to compare actual and expected output.
@@ -1072,6 +1071,7 @@ esac
 
 test -z "$NO_PERL" && test_set_prereq PERL
 test -z "$NO_PYTHON" && test_set_prereq PYTHON
+test -n "$USE_LIBPCRE" && test_set_prereq LIBPCRE
 
 # Can we rely on git's output in the C locale?
 if test -n "$GETTEXT_POISON"
index a02f79aae3d91ea4109d21a9ed9ca2d962125375..c9c8056f9de69bd378cd271d70363b5560f13e07 100644 (file)
@@ -156,7 +156,7 @@ static void set_upstreams(struct transport *transport, struct ref *refs,
                        continue;
                if (!ref->peer_ref)
                        continue;
-               if (!ref->new_sha1 || is_null_sha1(ref->new_sha1))
+               if (is_null_sha1(ref->new_sha1))
                        continue;
 
                /* Follow symbolic refs (mainly for HEAD). */
@@ -1190,14 +1190,20 @@ char *transport_anonymize_url(const char *url)
        return xstrdup(url);
 }
 
-int refs_from_alternate_cb(struct alternate_object_database *e, void *cb)
+struct alternate_refs_data {
+       alternate_ref_fn *fn;
+       void *data;
+};
+
+static int refs_from_alternate_cb(struct alternate_object_database *e,
+                                 void *data)
 {
        char *other;
        size_t len;
        struct remote *remote;
        struct transport *transport;
        const struct ref *extra;
-       alternate_ref_fn *ref_fn = cb;
+       struct alternate_refs_data *cb = data;
 
        e->name[-1] = '\0';
        other = xstrdup(real_path(e->base));
@@ -1218,8 +1224,16 @@ int refs_from_alternate_cb(struct alternate_object_database *e, void *cb)
        for (extra = transport_get_remote_refs(transport);
             extra;
             extra = extra->next)
-               ref_fn(extra, NULL);
+               cb->fn(extra, cb->data);
        transport_disconnect(transport);
        free(other);
        return 0;
 }
+
+void for_each_alternate_ref(alternate_ref_fn fn, void *data)
+{
+       struct alternate_refs_data cb;
+       cb.fn = fn;
+       cb.data = data;
+       foreach_alt_odb(refs_from_alternate_cb, &cb);
+}
index efb19688692b5f1be4fc085cc36d737ace2df417..161d724bba5c132950b45866286c558148560d01 100644 (file)
@@ -167,6 +167,6 @@ void transport_print_push_status(const char *dest, struct ref *refs,
                  int verbose, int porcelain, int *nonfastforward);
 
 typedef void alternate_ref_fn(const struct ref *, void *);
-extern int refs_from_alternate_cb(struct alternate_object_database *e, void *cb);
+extern void for_each_alternate_ref(alternate_ref_fn, void *);
 
 #endif
index 1ff47977d549992ada8c1236187d4516b648ee7d..e55310cd02c8e7d1f0404b2fee9539e7340993d9 100644 (file)
@@ -60,10 +60,24 @@ PATTERNS("pascal",
         "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
         "|<>|<=|>=|:=|\\.\\."),
 PATTERNS("perl",
-        "^[ \t]*package .*;\n"
-        "^[ \t]*sub .* \\{\n"
-        "^[A-Z]+ \\{\n"        /* BEGIN, END, ... */
-        "^=head[0-9] ",        /* POD */
+        "^package .*\n"
+        "^sub [[:alnum:]_':]+[ \t]*"
+               "(\\([^)]*\\)[ \t]*)?" /* prototype */
+               /*
+                * Attributes.  A regex can't count nested parentheses,
+                * so just slurp up whatever we see, taking care not
+                * to accept lines like "sub foo; # defined elsewhere".
+                *
+                * An attribute could contain a semicolon, but at that
+                * point it seems reasonable enough to give up.
+                */
+               "(:[^;#]*)?"
+               "(\\{[ \t]*)?" /* brace can come here or on the next line */
+               "(#.*)?$\n" /* comment */
+        "^(BEGIN|END|INIT|CHECK|UNITCHECK|AUTOLOAD|DESTROY)[ \t]*"
+               "(\\{[ \t]*)?" /* brace can come here or on the next line */
+               "(#.*)?$\n"
+        "^=head[0-9] .*",      /* POD */
         /* -- */
         "[[:alpha:]_'][[:alnum:]_']*"
         "|0[xb]?[0-9a-fA-F_]*"