Merge branch 'md/interix-update'
authorJunio C Hamano <gitster@pobox.com>
Wed, 13 Jul 2011 21:31:36 +0000 (14:31 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 13 Jul 2011 21:31:36 +0000 (14:31 -0700)
* md/interix-update:
Update the Interix default build configuration.

166 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/RelNotes/1.7.7.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-cvsserver.txt
Documentation/git-diff-index.txt
Documentation/git-diff-tree.txt
Documentation/git-grep.txt
Documentation/git-instaweb.txt
Documentation/git-notes.txt
Documentation/git-read-tree.txt
Documentation/git-remote-helpers.txt
Documentation/git-rev-list.txt
Documentation/git-sh-i18n--envsubst.txt
Documentation/git-sh-setup.txt
Documentation/git-status.txt
Documentation/git.txt
Documentation/rev-list-options.txt
Documentation/technical/api-diff.txt
Documentation/technical/pack-protocol.txt
GIT-VERSION-GEN
Makefile
RelNotes
abspath.c
bisect.c
builtin/branch.c
builtin/cat-file.c
builtin/checkout.c
builtin/commit.c
builtin/diff.c
builtin/fetch-pack.c
builtin/fetch.c
builtin/gc.c
builtin/grep.c
builtin/log.c
builtin/mailinfo.c
builtin/merge.c
builtin/notes.c
builtin/read-tree.c
builtin/receive-pack.c
builtin/remote.c
builtin/rev-list.c
builtin/rev-parse.c
builtin/shortlog.c
builtin/show-branch.c
builtin/update-index.c
builtin/verify-pack.c
cache.h
combine-diff.c
commit.h
compat/mingw.c
compat/mingw.h
config.c
config.mak.in
configure.ac
contrib/completion/git-completion.bash
convert.c
convert.h [new file with mode: 0644]
diff-lib.c
diff.c
diff.h
entry.c
fsck.c
git-am.sh
git-bisect.sh
git-compat-util.h
git-instaweb.sh
git-mergetool--lib.sh
git-pull.sh
git-rebase--interactive.sh
git-rebase.sh
git-sh-setup.sh
git-stash.sh
git-submodule.sh
git-svn.perl
git.c
gitweb/INSTALL
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
http.c
ident.c
log-tree.c
pretty.c
read-cache.c
refs.c
refs.h
remote-curl.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
streaming.c [new file with mode: 0644]
streaming.h [new file with mode: 0644]
t/Makefile
t/README
t/gitweb-lib.sh
t/lib-read-tree.sh [new file with mode: 0644]
t/t0021-conversion.sh
t/t0081-line-buffer.sh
t/t1000-read-tree-m-3way.sh
t/t1001-read-tree-m-2way.sh
t/t1002-read-tree-m-u-2way.sh
t/t1004-read-tree-m-u-wf.sh
t/t1005-read-tree-reset.sh
t/t1008-read-tree-overlay.sh
t/t1011-read-tree-sparse-checkout.sh
t/t1012-read-tree-df.sh
t/t1020-subdirectory.sh
t/t1300-repo-config.sh
t/t2018-checkout-branch.sh
t/t3301-notes.sh
t/t3404-rebase-interactive.sh
t/t3703-add-magic-pathspec.sh
t/t3903-stash.sh
t/t4014-format-patch.sh
t/t4018-diff-funcname.sh
t/t4040-whitespace-status.sh
t/t4048-diff-combined-binary.sh [new file with mode: 0755]
t/t4049-diff-stat-count.sh [new file with mode: 0755]
t/t4150-am.sh
t/t4151-am-abort.sh
t/t4152-am-subjects.sh [new file with mode: 0755]
t/t4203-mailmap.sh
t/t4205-log-pretty-formats.sh
t/t5505-remote.sh
t/t5506-remote-groups.sh
t/t5516-fetch-push.sh
t/t7400-submodule-basic.sh
t/t7401-submodule-summary.sh
t/t7406-submodule-update.sh
t/t7407-submodule-foreach.sh
t/t7503-pre-commit-hook.sh
t/t7508-status.sh
t/t7810-grep.sh
t/t9159-git-svn-no-parent-mergeinfo.sh [new file with mode: 0755]
t/t9300-fast-import.sh
t/t9500-gitweb-standalone-no-errors.sh
t/test-lib.sh
transport.c
transport.h
tree-diff.c
unpack-trees.c
unpack-trees.h
userdiff.c
userdiff.h
wrapper.c
wt-status.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..cf3f455
--- /dev/null
@@ -0,0 +1,21 @@
+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.
+
+ * Setting a git command that uses custom configuration via "-c var=val"
+   as an alias caused a crash due to a realloc(3) failure.
+
+ * "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..9ec498ea39d969a60c74192bf0863ef9fabe4bc1 100644 (file)
@@ -1,4 +1,4 @@
-Git v1.7.6 Release Notes (draft)
+Git v1.7.6 Release Notes
 ========================
 
 Updates since v1.7.5
@@ -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.
@@ -38,16 +48,11 @@ Updates since v1.7.5
    easier to parse.
 
  * Aborting "git commit --interactive" discards updates to the index
-   made during the interctive session.
+   made during the interactive session.
 
  * "git commit" learned a "--patch" option to directly jump to the
    per-hunk selection UI of the interactive mode.
 
- * "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 diff" and its family of commands learned --dirstat=0 to show
    directories that contribute less than 0.1% of changes.
 
@@ -62,10 +67,17 @@ Updates since v1.7.5
    characters in it, e.g. "Junio C. Hamano" <jch@example.com>.  Earlier
    it was up to the user to do this when using its output.
 
+ * "git format-patch" can take an empty --subject-prefix now.
+
+ * "git grep" learned the "-P" option to take pcre regular expressions.
+
  * "git log" and friends learned a new "--notes" option to replace the
    "--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.
 
@@ -80,9 +92,20 @@ Updates since v1.7.5
 
  * p4-import (from contrib) learned a new option --preserve-user.
 
+ * "git read-tree -m" learned "--dry-run" option that reports if a merge
+   would fail without touching the index nor the working tree.
+
  * "git rebase" that does not specify on top of which branch to rebase
    the current branch now uses @{upstream} of the current branch.
 
+ * "git rebase" finished either normally or with --abort did not
+   update the reflog for HEAD to record the event to come back to
+   where it started from.
+
+ * "git remote add -t only-this-branch --mirror=fetch" is now allowed. Earlier
+   a fetch-mode mirror meant mirror everything, but now it only means refs are
+   not renamed.
+
  * "git rev-list --count" used with "--cherry-mark" counts the cherry-picked
    commits separately, producing more a useful output.
 
@@ -93,10 +116,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 +126,11 @@ 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)
+ * "git diff --quiet" did not work well with --diff-filter.
+   (merge jk/diff-not-so-quick later)
 
----
-exec >/var/tmp/1
-echo O=$(git describe master)
-O=v1.7.5.2-352-g4961210
-git shortlog --no-merges ^maint ^$O master
+ * "git status -z" did not default to --porcelain output format.
+   (merge bc/maint-status-z-to-use-porcelain later)
diff --git a/Documentation/RelNotes/1.7.7.txt b/Documentation/RelNotes/1.7.7.txt
new file mode 100644 (file)
index 0000000..c5a5441
--- /dev/null
@@ -0,0 +1,36 @@
+Git v1.7.7 Release Notes
+========================
+
+Updates since v1.7.6
+--------------------
+
+ * The scripting part of the codebase is getting prepared for i18n/l10n.
+
+ * "git checkout" (both the code to update the files upon checking out a
+   different branch, the code to checkout specific set of files) learned
+   to stream the data from object store when possible, without having to
+   read the entire contents of a file in memory first.
+
+ * "git diff --cc" learned to correctly ignore binary files.
+
+ * "git diff --stat" learned --stat-count option to limit the output of
+   diffstat report.
+
+ * "git grep" learned --break and --heading options, to let users mimic
+   output format of "ack".
+
+Also contains various documentation updates and minor miscellaneous
+changes.
+
+
+Fixes since v1.7.6
+------------------
+
+Unless otherwise noted, all the fixes in 1.7.6.X maintenance track are
+included in this release.
+
+--
+exec >/var/tmp/1
+echo O=$(git describe master)
+O=v1.7.6-133-g395f65d
+git shortlog --no-merges ^maint ^$O master
index 6b937771994f5b0a532b6f2cc522a9d3f35c9c09..c631d1cbf9096b8973322366e2a3fe199286b9da 100644 (file)
@@ -1196,6 +1196,14 @@ http.proxy::
        environment variable (see linkgit:curl[1]).  This can be overridden
        on a per-remote basis; see remote.<name>.proxy
 
+http.cookiefile::
+       File containing previously stored cookie lines which should be used
+       in the git http session, if they match the server. The file format
+       of the file to read cookies from should be plain HTTP headers or
+       the Netscape/Mozilla cookie file format (see linkgit:curl[1]).
+       NOTE that the file specified with http.cookiefile is only used as
+       input. No cookies will be stored in the file.
+
 http.sslVerify::
        Whether to verify the SSL certificate when fetching or pushing
        over HTTPS. Can be overridden by the 'GIT_SSL_NO_VERIFY' environment
index c7ed94635786789baca1de41d482cd6334d1181a..b620b3afeca14166840fb76b4cefe1e4038fb47e 100644 (file)
@@ -48,11 +48,17 @@ endif::git-format-patch[]
 --patience::
        Generate a diff using the "patience diff" algorithm.
 
---stat[=<width>[,<name-width>]]::
+--stat[=<width>[,<name-width>[,<count>]]]::
        Generate a diffstat.  You can override the default
        output width for 80-column terminal by `--stat=<width>`.
        The width of the filename part can be controlled by
        giving another width to it separated by a comma.
+       By giving a third parameter `<count>`, you can limit the
+       output to the first `<count>` lines, followed by
+       `...` if there are more.
++
+These parameters can also be set individually with `--stat-width=<width>`,
+`--stat-name-width=<name-width>` and `--stat-count=<count>`.
 
 --numstat::
        Similar to `\--stat`, but shows number of added and
@@ -224,10 +230,14 @@ endif::git-format-patch[]
 
 ifndef::git-format-patch[]
 --check::
-       Warn if changes introduce trailing whitespace
-       or an indent that uses a space before a tab. Exits with
-       non-zero status if problems are found. Not compatible with
-       --exit-code.
+       Warn if changes introduce whitespace errors.  What are
+       considered whitespace errors is controlled by `core.whitespace`
+       configuration.  By default, trailing whitespaces (including
+       lines that solely consist of whitespaces) and a space character
+       that is immediately followed by a tab character inside the
+       initial indent of the line are considered whitespace errors.
+       Exits with non-zero status if problems are found. Not compatible
+       with --exit-code.
 endif::git-format-patch[]
 
 --full-index::
@@ -412,6 +422,17 @@ endif::git-format-patch[]
 --no-ext-diff::
        Disallow external diff drivers.
 
+--textconv::
+--no-textconv::
+       Allow (or disallow) external text conversion filters to be run
+       when comparing binary files. See linkgit:gitattributes[5] for
+       details. Because textconv filters are typically a one-way
+       conversion, the resulting diff is suitable for human
+       consumption, but cannot be applied. For this reason, textconv
+       filters are enabled by default only for linkgit:git-diff[1] and
+       linkgit:git-log[1], but not for linkgit:git-format-patch[1] or
+       diff plumbing commands.
+
 --ignore-submodules[=<when>]::
        Ignore changes to submodules in the diff generation. <when> can be
        either "none", "untracked", "dirty" or "all", which is the default
index 88d814af0e050eb3b31f5763cbf12f03a5f6cbc3..827bc988ed5562c2dbc6566d6732b01f20bb4f05 100644 (file)
@@ -252,7 +252,7 @@ Configuring database backend
 
 'git-cvsserver' uses the Perl DBI module. Please also read
 its documentation if changing these variables, especially
-about `DBI->connect()`.
+about `DBI\->connect()`.
 
 gitcvs.dbname::
        Database name. The exact meaning depends on the
index 6d184864022cef90e284113a627103db99327052..2ea22abca2bc72abb0cedfe6f47616c52a60d3f4 100644 (file)
@@ -96,8 +96,8 @@ show that. So let's say that you have edited `kernel/sched.c`, but
 have not actually done a 'git update-index' on it yet - there is no
 "object" associated with the new state, and you get:
 
-  torvalds@ppc970:~/v2.6/linux> git diff-index HEAD
-  *100644->100664 blob    7476bb......->000000......      kernel/sched.c
+  torvalds@ppc970:~/v2.6/linux> git diff-index --abbrev HEAD
+  :100644 100664 7476bb... 000000...      kernel/sched.c
 
 i.e., it shows that the tree has changed, and that `kernel/sched.c` has is
 not up-to-date and may contain new stuff. The all-zero sha1 means that to
index 4e5f127efa634dd4ea68bb73508c59bd1cad32e5..1439486e40ec4a8a1146310bf929d688eb01d533 100644 (file)
@@ -138,8 +138,8 @@ so it can be used to name subdirectories.
 
 An example of normal usage is:
 
-  torvalds@ppc970:~/git> git diff-tree 5319e4......
-  *100664->100664 blob    ac348b.......->a01513.......      git-fsck-objects.c
+  torvalds@ppc970:~/git> git diff-tree --abbrev 5319e4
+  :100664 100664 ac348b... a01513...   git-fsck-objects.c
 
 which tells you that the last commit changed just one file (it's from
 this one:
index d7523b3e45ff06bd7a8abf4ff466fe619c8268e7..07b3c6a0866c5b4ff0b4653b462b332ea0d348c9 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
@@ -142,6 +148,13 @@ OPTIONS
        gives the default to color output.
        Same as `--color=never`.
 
+--break::
+       Print an empty line between matches from different files.
+
+--heading::
+       Show the filename above the matches in that file instead of
+       at the start of each shown line.
+
 -[ABC] <context>::
        Show `context` trailing (`A` -- after), or leading (`B`
        -- before), or both (`C` -- context) lines, and place a
index 08f85ba046598070432e9d9cd45052ae20484402..ea95c90460b976ee187833c248366af579065f83 100644 (file)
@@ -51,8 +51,8 @@ OPTIONS
 
 start::
 --start::
-       Start the httpd instance and exit.  This does not generate
-       any of the configuration files for spawning a new instance.
+       Start the httpd instance and exit.  Regenerate configuration files
+       as necessary for spawning a new instance.
 
 stop::
 --stop::
@@ -62,8 +62,8 @@ stop::
 
 restart::
 --restart::
-       Restart the httpd instance and exit.  This does not generate
-       any of the configuration files for spawning a new instance.
+       Restart the httpd instance and exit.  Regenerate configuration files
+       as necessary for spawning a new instance.
 
 CONFIGURATION
 -------------
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 26fdadc642a3534c4005488cb9ca2b2a47a495a1..46a96f2313e0cb48bff9d5de26f403b35d327d2c 100644 (file)
@@ -53,6 +53,11 @@ OPTIONS
        trees that are not directly related to the current
        working tree status into a temporary index file.
 
+-n::
+--dry-run::
+       Check if the command would error out, without updating the index
+       nor the files in the working tree for real.
+
 -v::
        Show the progress of checking files out.
 
index 87cd11f2c47b66387d31c38f648c02f4c2734955..58f6ad4994dae770b1898888d3073fca777743af 100644 (file)
@@ -181,11 +181,11 @@ CAPABILITIES
        When using the import command, expect the source ref to have
        been written to the destination ref. The earliest applicable
        refspec takes precedence. For example
-       "refs/heads/*:refs/svn/origin/branches/*" means that, after an
-       "import refs/heads/name", the script has written to
+       "refs/heads/{asterisk}:refs/svn/origin/branches/{asterisk}" means
+       that, after an "import refs/heads/name", the script has written to
        refs/svn/origin/branches/name. If this capability is used at
        all, it must cover all refs reported by the list command; if
-       it is not used, it is effectively "*:*"
+       it is not used, it is effectively "{asterisk}:{asterisk}"
 
 REF LIST ATTRIBUTES
 -------------------
index 415f4f0b303da22e9eec19c1b9e24732a74ecf67..38fafcaa6b4173ad7f900c71efaa8ea25470072f 100644 (file)
@@ -29,6 +29,7 @@ SYNOPSIS
             [ \--tags[=<pattern>] ]
             [ \--remotes[=<pattern>] ]
             [ \--glob=<glob-pattern> ]
+            [ \--ignore-missing ]
             [ \--stdin ]
             [ \--quiet ]
             [ \--topo-order ]
index f5bbf7750db6f907d6f6d3b873154d7e098ef062..61e4c08dacf796853284ee03c3ccdbebddb51a9b 100644 (file)
@@ -5,6 +5,16 @@ NAME
 ----
 git-sh-i18n--envsubst - Git's own envsubst(1) for i18n fallbacks
 
+SYNOPSIS
+--------
+[verse]
+eval_gettext () {
+       printf "%s" "$1" | (
+               export PATH $('git sh-i18n--envsubst' --variables "$1");
+               'git sh-i18n--envsubst' "$1"
+       )
+}
+
 DESCRIPTION
 -----------
 
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 00b699fef71b45c78e298fa48609b553a32fd175..8ecc99d995fb86e4327c018467ed52d5345e6930 100644 (file)
@@ -32,9 +32,10 @@ OPTIONS
        Show the branch and tracking info even in short-format.
 
 --porcelain::
-       Give the output in a stable, easy-to-parse format for scripts.
-       Currently this is identical to --short output, but is guaranteed
-       not to change in the future, making it safe for scripts.
+       Give the output in an easy-to-parse format for scripts.
+       This is similar to the short output, but will remain stable
+       across git versions and regardless of user configuration. See
+       below for details.
 
 -u[<mode>]::
 --untracked-files[=<mode>]::
@@ -68,6 +69,9 @@ configuration variable documented in linkgit:git-config[1].
        (and suppresses the output of submodule summaries when the config option
        `status.submodulesummary` is set).
 
+--ignored::
+       Show ignored files as well.
+
 -z::
        Terminate entries with NUL, instead of LF.  This implies
        the `--porcelain` output format if no other format is given.
@@ -78,23 +82,27 @@ OUTPUT
 The output from this command is designed to be used as a commit
 template comment, and all the output lines are prefixed with '#'.
 The default, long format, is designed to be human readable,
-verbose and descriptive.  They are subject to change in any time.
+verbose and descriptive.  Its contents and format are subject to change
+at any time.
 
 The paths mentioned in the output, unlike many other git commands, are
 made relative to the current directory if you are working in a
 subdirectory (this is on purpose, to help cutting and pasting). See
 the status.relativePaths config option below.
 
-In short-format, the status of each path is shown as
+Short Format
+~~~~~~~~~~~~
+
+In the short-format, the status of each path is shown as
 
        XY PATH1 -> PATH2
 
-where `PATH1` is the path in the `HEAD`, and -> PATH2` part is
+where `PATH1` is the path in the `HEAD`, and the ` \-> PATH2` part is
 shown only when `PATH1` corresponds to a different path in the
 index/worktree (i.e. the file is renamed). The 'XY' is a two-letter
 status code.
 
-The fields (including the `->`) are separated from each other by a
+The fields (including the `\->`) are separated from each other by a
 single space. If a filename contains whitespace or other nonprintable
 characters, that field will be quoted in the manner of a C string
 literal: surrounded by ASCII double quote (34) characters, and with
@@ -114,7 +122,8 @@ codes can be interpreted as follows:
 * 'C' = copied
 * 'U' = updated but unmerged
 
-Ignored files are not listed.
+Ignored files are not listed, unless `--ignored` option is in effect,
+in which case `XY` are `!!`.
 
     X          Y     Meaning
     -------------------------------------------------
@@ -137,16 +146,32 @@ Ignored files are not listed.
     U           U    unmerged, both modified
     -------------------------------------------------
     ?           ?    untracked
+    !           !    ignored
     -------------------------------------------------
 
 If -b is used the short-format status is preceded by a line
 
 ## branchname tracking info
 
-There is an alternate -z format recommended for machine parsing.  In
+Porcelain Format
+~~~~~~~~~~~~~~~~
+
+The porcelain format is similar to the short format, but is guaranteed
+not to change in a backwards-incompatible way between git versions or
+based on user configuration. This makes it ideal for parsing by scripts.
+The description of the short format above also describes the porcelain
+format, with a few exceptions:
+
+1. The user's color.status configuration is not respected; color will
+   always be off.
+
+2. The user's status.relativePaths configuration is not respected; paths
+   shown will always be relative to the repository root.
+
+There is also an alternate -z format recommended for machine parsing. In
 that format, the status field is the same, but some other things
-change.  First, the '->' is omitted from rename entries and the field
-order is reversed (e.g 'from -> to' becomes 'to from'). Second, a NUL
+change.  First, the '\->' is omitted from rename entries and the field
+order is reversed (e.g 'from \-> to' becomes 'to from'). Second, a NUL
 (ASCII 0) follows each filename, replacing space as a field separator
 and the terminating newline (but a space still separates the status
 field from the first filename).  Third, filenames containing special
index 19d4deff7031b433eb1f6b80c873b86183faefbb..3c7a832343ea983f7122c5d33429a4129fdba70a 100644 (file)
@@ -44,9 +44,16 @@ 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.6/git.html[documentation for release 1.7.6]
 
 * release notes for
+  link:RelNotes/1.7.6.txt[1.7.6].
+
+* link:v1.7.5.4/git.html[documentation for release 1.7.5.4]
+
+* release notes for
+  link:RelNotes/1.7.5.4.txt[1.7.5.4],
+  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 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 369f91d3b949b23682c4deda8234f13513f15732..a7004c63e7ff5a4fe237267121a5b7c76a4c5496 100644 (file)
@@ -179,34 +179,36 @@ and descriptions.
 
 Packfile Negotiation
 --------------------
-After reference and capabilities discovery, the client can decide
-to terminate the connection by sending a flush-pkt, telling the
-server it can now gracefully terminate (as happens with the ls-remote
-command) or it can enter the negotiation phase, where the client and
-server determine what the minimal packfile necessary for transport is.
-
-Once the client has the initial list of references that the server
-has, as well as the list of capabilities, it will begin telling the
-server what objects it wants and what objects it has, so the server
-can make a packfile that only contains the objects that the client needs.
-The client will also send a list of the capabilities it wants to be in
-effect, out of what the server said it could do with the first 'want' line.
+After reference and capabilities discovery, the client can decide to
+terminate the connection by sending a flush-pkt, telling the server it can
+now gracefully terminate, and disconnect, when it does not need any pack
+data. This can happen with the ls-remote command, and also can happen when
+the client already is up-to-date.
+
+Otherwise, it enters the negotiation phase, where the client and
+server determine what the minimal packfile necessary for transport is,
+by telling the server what objects it wants, its shallow objects
+(if any), and the maximum commit depth it wants (if any).  The client
+will also send a list of the capabilities it wants to be in effect,
+out of what the server said it could do with the first 'want' line.
 
 ----
   upload-request    =  want-list
-                      have-list
-                      compute-end
+                      *shallow-line
+                      *1depth-request
+                      flush-pkt
 
   want-list         =  first-want
                       *additional-want
-                      flush-pkt
+
+  shallow-line      =  PKT_LINE("shallow" SP obj-id)
+
+  depth-request     =  PKT_LINE("deepen" SP depth)
 
   first-want        =  PKT-LINE("want" SP obj-id SP capability-list LF)
   additional-want   =  PKT-LINE("want" SP obj-id LF)
 
-  have-list         =  *have-line
-  have-line         =  PKT-LINE("have" SP obj-id LF)
-  compute-end       =  flush-pkt / PKT-LINE("done")
+  depth             =  1*DIGIT
 ----
 
 Clients MUST send all the obj-ids it wants from the reference
@@ -215,21 +217,64 @@ discovery phase as 'want' lines. Clients MUST send at least one
 obj-id in a 'want' command which did not appear in the response
 obtained through ref discovery.
 
-If client is requesting a shallow clone, it will now send a 'deepen'
-line with the depth it is requesting.
+The client MUST write all obj-ids which it only has shallow copies
+of (meaning that it does not have the parents of a commit) as
+'shallow' lines so that the server is aware of the limitations of
+the client's history. Clients MUST NOT mention an obj-id which
+it does not know exists on the server.
+
+The client now sends the maximum commit history depth it wants for
+this transaction, which is the number of commits it wants from the
+tip of the history, if any, as a 'deepen' line.  A depth of 0 is the
+same as not making a depth request. The client does not want to receive
+any commits beyond this depth, nor objects needed only to complete
+those commits. Commits whose parents are not received as a result are
+defined as shallow and marked as such in the server. This information
+is sent back to the client in the next step.
+
+Once all the 'want's and 'shallow's (and optional 'deepen') are
+transferred, clients MUST send a flush-pkt, to tell the server side
+that it is done sending the list.
+
+Otherwise, if the client sent a positive depth request, the server
+will determine which commits will and will not be shallow and
+send this information to the client. If the client did not request
+a positive depth, this step is skipped.
 
-Once all the "want"s (and optional 'deepen') are transferred,
-clients MUST send a flush-pkt. If the client has all the references
-on the server, client flushes and disconnects.
+----
+  shallow-update   =  *shallow-line
+                     *unshallow-line
+                     flush-pkt
 
-TODO: shallow/unshallow response and document the deepen command in the ABNF.
+  shallow-line     =  PKT-LINE("shallow" SP obj-id)
+
+  unshallow-line   =  PKT-LINE("unshallow" SP obj-id)
+----
+
+If the client has requested a positive depth, the server will compute
+the set of commits which are no deeper than the desired depth, starting
+at the client's wants. The server writes 'shallow' lines for each
+commit whose parents will not be sent as a result. The server writes
+an 'unshallow' line for each commit which the client has indicated is
+shallow, but is no longer shallow at the currently requested depth
+(that is, its parents will now be sent). The server MUST NOT mark
+as unshallow anything which the client has not indicated was shallow.
 
 Now the client will send a list of the obj-ids it has using 'have'
-lines.  In multi_ack mode, the canonical implementation will send up
-to 32 of these at a time, then will send a flush-pkt.  The canonical
-implementation will skip ahead and send the next 32 immediately,
-so that there is always a block of 32 "in-flight on the wire" at a
-time.
+lines, so the server can make a packfile that only contains the objects
+that the client needs. In multi_ack mode, the canonical implementation
+will send up to 32 of these at a time, then will send a flush-pkt. The
+canonical implementation will skip ahead and send the next 32 immediately,
+so that there is always a block of 32 "in-flight on the wire" at a time.
+
+----
+  upload-haves      =  have-list
+                      compute-end
+
+  have-list         =  *have-line
+  have-line         =  PKT-LINE("have" SP obj-id LF)
+  compute-end       =  flush-pkt / PKT-LINE("done")
+----
 
 If the server reads 'have' lines, it then will respond by ACKing any
 of the obj-ids the client said it had that the server also has. The
index 7f48227494da7fe1bd57883d41e10b487c3f2fbf..8270abb5f982463119309423eded333a8153317e 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.7.5.GIT
+DEF_VER=v1.7.6.GIT
 
 LF='
 '
index c47cd4e3031882839cf92431c2f2d0980fad158c..8d8f61e4f031e242ac5056adaccc9f57d2d1efba 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,10 +551,12 @@ 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
 LIB_H += strbuf.h
+LIB_H += streaming.h
 LIB_H += string-list.h
 LIB_H += submodule.h
 LIB_H += tag.h
@@ -644,6 +655,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
@@ -651,6 +663,7 @@ LIB_OBJS += shallow.o
 LIB_OBJS += sideband.o
 LIB_OBJS += sigchain.o
 LIB_OBJS += strbuf.o
+LIB_OBJS += streaming.o
 LIB_OBJS += string-list.o
 LIB_OBJS += submodule.o
 LIB_OBJS += symlinks.o
@@ -1255,6 +1268,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 =
@@ -2086,6 +2108,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 5fcc4ef74770e8587a1469fff5e3f5610dabf0e1..6372427426d80b01b03437fd1a55fd412fb8f75d 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/1.7.6.txt
\ No newline at end of file
+Documentation/RelNotes/1.7.7.txt
\ No newline at end of file
index 3005aedde68b48297aacd2082797b2e2f249b094..01858eb7bca277a125c414ef4bf1904fd0ea9059 100644 (file)
--- a/abspath.c
+++ b/abspath.c
@@ -40,7 +40,7 @@ const char *real_path(const char *path)
 
        while (depth--) {
                if (!is_directory(buf)) {
-                       char *last_slash = strrchr(buf, '/');
+                       char *last_slash = find_last_dir_sep(buf);
                        if (last_slash) {
                                *last_slash = '\0';
                                last_elem = xstrdup(last_slash + 1);
@@ -65,7 +65,7 @@ const char *real_path(const char *path)
                        if (len + strlen(last_elem) + 2 > PATH_MAX)
                                die ("Too long path name: '%s/%s'",
                                                buf, last_elem);
-                       if (len && buf[len-1] != '/')
+                       if (len && !is_dir_sep(buf[len-1]))
                                buf[len++] = '/';
                        strcpy(buf + len, last_elem);
                        free(last_elem);
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 9cca1b9afc20caf6333d0269386249f41fa8746f..3142daa57a6fa1c8a7d21095946bf5d26443d0e0 100644 (file)
@@ -19,7 +19,7 @@
 static const char * const builtin_branch_usage[] = {
        "git branch [options] [-r | -a] [--merged | --no-merged]",
        "git branch [options] [-l] [-f] <branchname> [<start-point>]",
-       "git branch [options] [-r] (-d | -D) <branchname>",
+       "git branch [options] [-r] (-d | -D) <branchname>...",
        "git branch [options] (-m | -M) [<oldbranch>] <newbranch>",
        NULL
 };
@@ -399,9 +399,7 @@ static void add_verbose_info(struct strbuf *out, struct ref_item *item,
        struct commit *commit = item->commit;
 
        if (commit && !parse_commit(commit)) {
-               struct pretty_print_context ctx = {0};
-               pretty_print_commit(CMIT_FMT_ONELINE, commit,
-                                   &subject, &ctx);
+               pp_commit_easy(CMIT_FMT_ONELINE, commit, &subject);
                sub = subject.buf;
        }
 
index 94632dbdb400f9a2986d10f06dd16119ce1b4e54..07bd984084fbbfbb826ef5b784fc68069675e73c 100644 (file)
@@ -187,6 +187,8 @@ static int batch_one_object(const char *obj_name, int print_contents)
        if (type <= 0) {
                printf("%s missing\n", obj_name);
                fflush(stdout);
+               if (print_contents == BATCH)
+                       free(contents);
                return 0;
        }
 
index 4761769512a8abcdd5652a93d39be18154e251dd..f152adf9ab71287e02e9e85c0b84aae096e5bec7 100644 (file)
@@ -306,9 +306,8 @@ static void show_local_changes(struct object *head, struct diff_options *opts)
 static void describe_detached_head(const char *msg, struct commit *commit)
 {
        struct strbuf sb = STRBUF_INIT;
-       struct pretty_print_context ctx = {0};
        parse_commit(commit);
-       pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, &ctx);
+       pp_commit_easy(CMIT_FMT_ONELINE, commit, &sb);
        fprintf(stderr, "%s %s... %s\n", msg,
                find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf);
        strbuf_release(&sb);
@@ -623,14 +622,12 @@ static int clear_commit_marks_from_one_ref(const char *refname,
 
 static void describe_one_orphan(struct strbuf *sb, struct commit *commit)
 {
-       struct pretty_print_context ctx = { 0 };
-
        parse_commit(commit);
        strbuf_addstr(sb, "  ");
        strbuf_addstr(sb,
                find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
        strbuf_addch(sb, ' ');
-       pretty_print_commit(CMIT_FMT_ONELINE, commit, sb, &ctx);
+       pp_commit_easy(CMIT_FMT_ONELINE, commit, sb);
        strbuf_addch(sb, '\n');
 }
 
@@ -660,24 +657,25 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
                "Warning: you are leaving %d commit behind, "
                "not connected to\n"
                "any of your branches:\n\n"
-               "%s\n"
-               "If you want to keep it by creating a new branch, "
-               "this may be a good time\nto do so with:\n\n"
-               " git branch new_branch_name %s\n\n",
+               "%s\n",
                /* The plural version */
                "Warning: you are leaving %d commits behind, "
                "not connected to\n"
                "any of your branches:\n\n"
-               "%s\n"
-               "If you want to keep them by creating a new branch, "
-               "this may be a good time\nto do so with:\n\n"
-               " git branch new_branch_name %s\n\n",
+               "%s\n",
                /* Give ngettext() the count */
                lost),
                lost,
-               sb.buf,
-               sha1_to_hex(commit->object.sha1));
+               sb.buf);
        strbuf_release(&sb);
+
+       if (advice_detached_head)
+               fprintf(stderr,
+                       _(
+                       "If you want to keep them by creating a new branch, "
+                       "this may be a good time\nto do so with:\n\n"
+                       " git branch new_branch_name %s\n\n"),
+                       sha1_to_hex(commit->object.sha1));
 }
 
 /*
@@ -1074,7 +1072,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                if (strbuf_check_branch_ref(&buf, opts.new_branch))
                        die(_("git checkout: we do not like '%s' as a branch name."),
                            opts.new_branch);
-               if (!get_sha1(buf.buf, rev)) {
+               if (ref_exists(buf.buf)) {
                        opts.branch_exists = 1;
                        if (!opts.new_branch_force)
                                die(_("git checkout: branch %s already exists"),
index 5286432f39b87e03a08cc86bb622b6ca3b911365..e1af9b19f0be71484ae9341762dc2bf89cabb70c 100644 (file)
@@ -1207,9 +1207,6 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_status_usage, builtin_status_options);
 
-       if (null_termination && status_format == STATUS_FORMAT_LONG)
-               status_format = STATUS_FORMAT_PORCELAIN;
-
        wt_status_prepare(&s);
        gitmodules_config();
        git_config(git_status_config, &s);
@@ -1217,6 +1214,10 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix,
                             builtin_status_options,
                             builtin_status_usage, 0);
+
+       if (null_termination && status_format == STATUS_FORMAT_LONG)
+               status_format = STATUS_FORMAT_PORCELAIN;
+
        handle_untracked_files_arg(&s);
        if (show_ignored_in_status)
                s.show_ignored_files = 1;
index 14bd14fce0fa6b9c9b047142d23d4a2237b57a36..69cd5eed78cb402839813e7eca65b5598afa4a90 100644 (file)
@@ -182,6 +182,7 @@ static int builtin_diff_combined(struct rev_info *revs,
                hashcpy((unsigned char *)(parent + i), ent[i].item->sha1);
        diff_tree_combined(parent[0], parent + 1, ents - 1,
                           revs->dense_combined_merges, revs);
+       free(parent);
        return 0;
 }
 
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 f9c41da475289b84fe849f7641406ebc94059630..93c99385a95e6ab525fa64c1dbb70d5d4ca103ba 100644 (file)
@@ -875,6 +875,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
 {
        int i;
        static const char **refs = NULL;
+       struct refspec *refspec;
        int ref_nr = 0;
        int exit_code;
 
@@ -915,8 +916,9 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
 
        sigchain_push_common(unlock_pack_on_signal);
        atexit(unlock_pack);
-       exit_code = do_fetch(transport,
-                       parse_fetch_refspec(ref_nr, refs), ref_nr);
+       refspec = parse_fetch_refspec(ref_nr, refs);
+       exit_code = do_fetch(transport, refspec, ref_nr);
+       free(refspec);
        transport_disconnect(transport);
        transport = NULL;
        return exit_code;
index ff5f73ba87b23fc7e361a1751c2d9a64e0267163..0498094711d1addd40f526f0c76dd8ddb76ef550 100644 (file)
@@ -225,7 +225,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                        fprintf(stderr,
                                        _("Auto packing the repository for optimum performance. You may also\n"
                                        "run \"git gc\" manually. See "
-                                       "\"git help gc\" for more information."));
+                                       "\"git help gc\" for more information.\n"));
        } else
                append_option(argv_repack,
                              prune_expire && !strcmp(prune_expire, "now")
index 931eee0d750cd8662347e2a7c8e8a89d23228e17..cccf8da6d2a600154536ea642250699d9356f148 100644 (file)
@@ -93,8 +93,7 @@ static pthread_cond_t cond_write;
 /* Signalled when we are finished with everything. */
 static pthread_cond_t cond_result;
 
-static int print_hunk_marks_between_files;
-static int printed_something;
+static int skip_first_line;
 
 static void add_work(enum work_type type, char *name, void *id)
 {
@@ -160,10 +159,20 @@ static void work_done(struct work_item *w)
            todo_done = (todo_done+1) % ARRAY_SIZE(todo)) {
                w = &todo[todo_done];
                if (w->out.len) {
-                       if (print_hunk_marks_between_files && printed_something)
-                               write_or_die(1, "--\n", 3);
-                       write_or_die(1, w->out.buf, w->out.len);
-                       printed_something = 1;
+                       const char *p = w->out.buf;
+                       size_t len = w->out.len;
+
+                       /* Skip the leading hunk mark of the first file. */
+                       if (skip_first_line) {
+                               while (len) {
+                                       len--;
+                                       if (*p++ == '\n')
+                                               break;
+                               }
+                               skip_first_line = 0;
+                       }
+
+                       write_or_die(1, p, len);
                }
                free(w->name);
                free(w->identifier);
@@ -753,6 +762,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 +792,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),
@@ -799,6 +822,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                OPT_BOOLEAN('c', "count", &opt.count,
                        "show the number of matches instead of matching lines"),
                OPT__COLOR(&opt.color, "highlight matches"),
+               OPT_BOOLEAN(0, "break", &opt.file_break,
+                       "print empty line between matches from different files"),
+               OPT_BOOLEAN(0, "heading", &opt.heading,
+                       "show filename only once above matches from same file"),
                OPT_GROUP(""),
                OPT_CALLBACK('C', NULL, &opt, "n",
                        "show <n> context lines before and after matches",
@@ -886,6 +913,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,16 +974,14 @@ 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))
                use_threads = 0;
 
        if (use_threads) {
-               if (opt.pre_context || opt.post_context)
-                       print_hunk_marks_between_files = 1;
+               if (opt.pre_context || opt.post_context || opt.file_break)
+                       skip_first_line = 1;
                start_threads(&opt);
        }
 #else
index 27849dc91de03a46d4c9357ca0d7b3f79188cc98..5c2af590047d92554d87acc5d7113a2f1f730a96 100644 (file)
@@ -107,6 +107,8 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
                             PARSE_OPT_KEEP_DASHDASH);
 
        argc = setup_revisions(argc, argv, rev, opt);
+       if (quiet)
+               rev->diffopt.output_format |= DIFF_FORMAT_NO_OUTPUT;
 
        /* Any arguments at this point are not recognized */
        if (argc > 1)
@@ -374,9 +376,11 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
 static void show_tagger(char *buf, int len, struct rev_info *rev)
 {
        struct strbuf out = STRBUF_INIT;
+       struct pretty_print_context pp = {0};
 
-       pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode,
-               get_log_output_encoding());
+       pp.fmt = rev->commit_format;
+       pp.date_mode = rev->date_mode;
+       pp_user_info(&pp, "Tagger", &out, buf, get_log_output_encoding());
        printf("%s", out.buf);
        strbuf_release(&out);
 }
@@ -760,10 +764,8 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
                              int quiet)
 {
        const char *committer;
-       const char *subject_start = NULL;
        const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
        const char *msg;
-       const char *extra_headers = rev->extra_headers;
        struct shortlog log;
        struct strbuf sb = STRBUF_INIT;
        int i;
@@ -771,6 +773,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
        struct diff_options opts;
        int need_8bit_cte = 0;
        struct commit *commit = NULL;
+       struct pretty_print_context pp = {0};
 
        if (rev->commit_format != CMIT_FMT_EMAIL)
                die(_("Cover letter needs email format"));
@@ -802,7 +805,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
                free(commit);
        }
 
-       log_write_email_headers(rev, head, &subject_start, &extra_headers,
+       log_write_email_headers(rev, head, &pp.subject, &pp.after_subject,
                                &need_8bit_cte);
 
        for (i = 0; !need_8bit_cte && i < nr; i++)
@@ -810,11 +813,11 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
                        need_8bit_cte = 1;
 
        msg = body;
-       pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822,
-                    encoding);
-       pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers,
-                     encoding, need_8bit_cte);
-       pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0);
+       pp.fmt = CMIT_FMT_EMAIL;
+       pp.date_mode = DATE_RFC2822;
+       pp_user_info(&pp, NULL, &sb, committer, encoding);
+       pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
+       pp_remainder(&pp, &msg, &sb, 0);
        printf("%s\n", sb.buf);
 
        strbuf_release(&sb);
@@ -1178,6 +1181,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                die (_("-n and -k are mutually exclusive."));
        if (keep_subject && subject_prefix)
                die (_("--subject-prefix and -k are mutually exclusive."));
+       rev.preserve_subject = keep_subject;
 
        argc = setup_revisions(argc, argv, &rev, &s_r_opt);
        if (argc > 1)
@@ -1408,8 +1412,7 @@ static void print_commit(char sign, struct commit *commit, int verbose,
                       find_unique_abbrev(commit->object.sha1, abbrev));
        } else {
                struct strbuf buf = STRBUF_INIT;
-               struct pretty_print_context ctx = {0};
-               pretty_print_commit(CMIT_FMT_ONELINE, commit, &buf, &ctx);
+               pp_commit_easy(CMIT_FMT_ONELINE, commit, &buf);
                printf("%c %s %s\n", sign,
                       find_unique_abbrev(commit->object.sha1, abbrev),
                       buf.buf);
index 71e6262a87d883a4a82953db5f742bccf99e5c77..bfb32b7233850a68bdc226038a9c0f973037499b 100644 (file)
@@ -400,7 +400,7 @@ static int read_one_header_line(struct strbuf *line, FILE *in)
                        break;
                if (strbuf_getline(&continuation, in, '\n'))
                        break;
-               continuation.buf[0] = '\n';
+               continuation.buf[0] = ' ';
                strbuf_rtrim(&continuation);
                strbuf_addbuf(line, &continuation);
        }
index 5a2a1eb797c88990337f0b9cbb5dcabc73222a22..325891edb610945d01899b102993202af279bf3f 100644 (file)
@@ -339,13 +339,14 @@ static void squash_message(void)
 
        ctx.abbrev = rev.abbrev;
        ctx.date_mode = rev.date_mode;
+       ctx.fmt = rev.commit_format;
 
        strbuf_addstr(&out, "Squashed commit of the following:\n");
        while ((commit = get_revision(&rev)) != NULL) {
                strbuf_addch(&out, '\n');
                strbuf_addf(&out, "commit %s\n",
                        sha1_to_hex(commit->object.sha1));
-               pretty_print_commit(rev.commit_format, commit, &out, &ctx);
+               pretty_print_commit(&ctx, commit, &out);
        }
        if (write(fd, out.buf, out.len) < 0)
                die_errno(_("Writing SQUASH_MSG"));
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 93c92814cf28855aee87cabf4a3b906e11a36125..df6c4c8819e7903f34cd6dc6bb0078a6cddac310 100644 (file)
@@ -130,6 +130,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                  PARSE_OPT_NONEG, exclude_per_directory_cb },
                OPT_SET_INT('i', NULL, &opts.index_only,
                            "don't check the working tree after merging", 1),
+               OPT__DRY_RUN(&opts.dry_run, "don't update the index or the work tree"),
                OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout,
                            "skip applying sparse checkout filter", 1),
                OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack,
@@ -219,7 +220,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
        if (unpack_trees(nr_trees, t, &opts))
                return 128;
 
-       if (opts.debug_unpack)
+       if (opts.debug_unpack || opts.dry_run)
                return 0; /* do not write the index out */
 
        /*
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 8424152269e309cb86e4ff530b9feacc23fc17a6..9ff1cac69b9fd21c5f7dfc06b5859fedccc67644 100644 (file)
@@ -193,8 +193,8 @@ static int add(int argc, const char **argv)
 
        if (mirror && master)
                die("specifying a master branch makes no sense with --mirror");
-       if (mirror && track.nr)
-               die("specifying branches to track makes no sense with --mirror");
+       if (mirror && !(mirror & MIRROR_FETCH) && track.nr)
+               die("specifying branches to track makes sense only with fetch mirrors");
 
        name = argv[0];
        url = argv[1];
index 4be66998f6bcb998df094d0c434dfab6bbdcc8c7..56727e8c1d9d87fe3e3bf3919ba86d27d0735758 100644 (file)
@@ -104,7 +104,8 @@ static void show_commit(struct commit *commit, void *data)
                struct pretty_print_context ctx = {0};
                ctx.abbrev = revs->abbrev;
                ctx.date_mode = revs->date_mode;
-               pretty_print_commit(revs->commit_format, commit, &buf, &ctx);
+               ctx.fmt = revs->commit_format;
+               pretty_print_commit(&ctx, commit, &buf);
                if (revs->graph) {
                        if (buf.len) {
                                if (revs->commit_format != CMIT_FMT_ONELINE)
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 b6f4b0eb03b66dda2f691ff5c80b78321c1b701d..37f3193a3366725a6b11bdb55db7b6ffc6e9b3ce 100644 (file)
@@ -138,9 +138,8 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
        const char *author = NULL, *buffer;
        struct strbuf buf = STRBUF_INIT;
        struct strbuf ufbuf = STRBUF_INIT;
-       struct pretty_print_context ctx = {0};
 
-       pretty_print_commit(CMIT_FMT_RAW, commit, &buf, &ctx);
+       pp_commit_easy(CMIT_FMT_RAW, commit, &buf);
        buffer = buf.buf;
        while (*buffer && *buffer != '\n') {
                const char *eol = strchr(buffer, '\n');
@@ -159,11 +158,12 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
                    sha1_to_hex(commit->object.sha1));
        if (log->user_format) {
                struct pretty_print_context ctx = {0};
+               ctx.fmt = CMIT_FMT_USERFORMAT;
                ctx.abbrev = log->abbrev;
                ctx.subject = "";
                ctx.after_subject = "";
                ctx.date_mode = DATE_NORMAL;
-               pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &ufbuf, &ctx);
+               pretty_print_commit(&ctx, commit, &ufbuf);
                buffer = ufbuf.buf;
        } else if (*buffer) {
                buffer++;
index 1abcd9e02e64022e2619db3de40c7b3a731d4479..facc63a79ec0c86fd9df59792cd4f20030af5db1 100644 (file)
@@ -283,8 +283,7 @@ static void show_one_commit(struct commit *commit, int no_name)
        struct commit_name *name = commit->util;
 
        if (commit->object.parsed) {
-               struct pretty_print_context ctx = {0};
-               pretty_print_commit(CMIT_FMT_ONELINE, commit, &pretty, &ctx);
+               pp_commit_easy(CMIT_FMT_ONELINE, commit, &pretty);
                pretty_str = pretty.buf;
        }
        if (!prefixcmp(pretty_str, "[PATCH] "))
index f14bc908309c0bb01ec251d71f26b490b294622c..a6a23fa1f3c7782566d7fcfe470dd424b876e4a5 100644 (file)
@@ -100,8 +100,10 @@ static int add_one_path(struct cache_entry *old, const char *path, int len, stru
        ce->ce_mode = ce_mode_from_stat(old, st->st_mode);
 
        if (index_path(ce->sha1, path, st,
-                      info_only ? 0 : HASH_WRITE_OBJECT))
+                      info_only ? 0 : HASH_WRITE_OBJECT)) {
+               free(ce);
                return -1;
+       }
        option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
        option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
        if (add_cache_entry(ce, option))
index b6079ae6cb03c7f3112c6eebc8c9a012d690a125..3a919b170726a95b19c16f984ade250d4fc24c07 100644 (file)
@@ -33,9 +33,9 @@ static void show_pack_info(struct packed_git *p, unsigned int flags)
                if (!sha1)
                        die("internal error pack-check nth-packed-object");
                offset = nth_packed_object_offset(p, i);
-               type = packed_object_info_detail(p, offset, &size, &store_size,
+               type = typename(packed_object_info_detail(p, offset, &size, &store_size,
                                                 &delta_chain_length,
-                                                base_sha1);
+                                                base_sha1));
                if (!stat_only)
                        printf("%s ", sha1_to_hex(sha1));
                if (!delta_chain_length) {
diff --git a/cache.h b/cache.h
index 009b365370f0815e4493f4775f96ebda3cfa6e80..bc9e5eb15e23c23b41b76e1f353e078ea3858344 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -6,6 +6,7 @@
 #include "hash.h"
 #include "advice.h"
 #include "gettext.h"
+#include "convert.h"
 
 #include SHA1_HEADER
 #ifndef git_SHA_CTX
@@ -582,35 +583,6 @@ extern int fsync_object_files;
 extern int core_preload_index;
 extern int core_apply_sparse_checkout;
 
-enum safe_crlf {
-       SAFE_CRLF_FALSE = 0,
-       SAFE_CRLF_FAIL = 1,
-       SAFE_CRLF_WARN = 2
-};
-
-extern enum safe_crlf safe_crlf;
-
-enum auto_crlf {
-       AUTO_CRLF_FALSE = 0,
-       AUTO_CRLF_TRUE = 1,
-       AUTO_CRLF_INPUT = -1
-};
-
-extern enum auto_crlf auto_crlf;
-
-enum eol {
-       EOL_UNSET,
-       EOL_CRLF,
-       EOL_LF,
-#ifdef NATIVE_CRLF
-       EOL_NATIVE = EOL_CRLF
-#else
-       EOL_NATIVE = EOL_LF
-#endif
-};
-
-extern enum eol core_eol;
-
 enum branch_track {
        BRANCH_TRACK_UNSPECIFIED = -1,
        BRANCH_TRACK_NEVER = 0,
@@ -747,7 +719,7 @@ extern char *expand_user_path(const char *path);
 char *enter_repo(char *path, int strict);
 static inline int is_absolute_path(const char *path)
 {
-       return path[0] == '/' || has_dos_drive_prefix(path);
+       return is_dir_sep(path[0]) || has_dos_drive_prefix(path);
 }
 int is_directory(const char *);
 const char *real_path(const char *path);
@@ -780,6 +752,9 @@ extern int hash_sha1_file(const void *buf, unsigned long len, const char *type,
 extern int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
 extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
 extern int force_object_loose(const unsigned char *sha1, time_t mtime);
+extern void *map_sha1_file(const unsigned char *sha1, unsigned long *size);
+extern int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz);
+extern int parse_sha1_header(const char *hdr, unsigned long *sizep);
 
 /* global flag to enable extra checks when accessing packed objects */
 extern int do_check_packed_object_crc;
@@ -1021,7 +996,37 @@ extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *);
 extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *);
 extern unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
 extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t);
-extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
+extern int packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
+extern int unpack_object_header(struct packed_git *, struct pack_window **, off_t *, unsigned long *);
+
+struct object_info {
+       /* Request */
+       unsigned long *sizep;
+
+       /* Response */
+       enum {
+               OI_CACHED,
+               OI_LOOSE,
+               OI_PACKED,
+               OI_DBCACHED
+       } whence;
+       union {
+               /*
+                * struct {
+                *      ... Nothing to expose in this case
+                * } cached;
+                * struct {
+                *      ... Nothing to expose in this case
+                * } loose;
+                */
+               struct {
+                       struct packed_git *pack;
+                       off_t offset;
+                       unsigned int is_delta;
+               } packed;
+       } u;
+};
+extern int sha1_object_info_extended(const unsigned char *, struct object_info *);
 
 /* Dumb servers support */
 extern int update_server_info(int);
@@ -1040,8 +1045,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);
@@ -1131,13 +1134,6 @@ extern void trace_strbuf(const char *key, const struct strbuf *buf);
 
 void packet_trace_identity(const char *prog);
 
-/* convert.c */
-/* returns 1 if *dst was used */
-extern int convert_to_git(const char *path, const char *src, size_t len,
-                          struct strbuf *dst, enum safe_crlf checksafe);
-extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst);
-extern int renormalize_buffer(const char *path, const char *src, size_t len, struct strbuf *dst);
-
 /* add */
 /*
  * return 0 if success, 1 - if addition of a file failed and
index 655fa89d8a7ffe3c3823080f5af3e5e385e95440..be67cfcd45469497f81e0c36ed87a11fd409daee 100644 (file)
@@ -7,6 +7,7 @@
 #include "xdiff-interface.h"
 #include "log-tree.h"
 #include "refs.h"
+#include "userdiff.h"
 
 static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent)
 {
@@ -92,7 +93,9 @@ struct sline {
        unsigned long *p_lno;
 };
 
-static char *grab_blob(const unsigned char *sha1, unsigned int mode, unsigned long *size)
+static char *grab_blob(const unsigned char *sha1, unsigned int mode,
+                      unsigned long *size, struct userdiff_driver *textconv,
+                      const char *path)
 {
        char *blob;
        enum object_type type;
@@ -105,6 +108,11 @@ static char *grab_blob(const unsigned char *sha1, unsigned int mode, unsigned lo
                /* deleted blob */
                *size = 0;
                return xcalloc(1, 1);
+       } else if (textconv) {
+               struct diff_filespec *df = alloc_filespec(path);
+               fill_filespec(df, sha1, mode);
+               *size = fill_textconv(textconv, df, &blob);
+               free_filespec(df);
        } else {
                blob = read_sha1_file(sha1, &type, size);
                if (type != OBJ_BLOB)
@@ -204,7 +212,9 @@ static void consume_line(void *state_, char *line, unsigned long len)
 static void combine_diff(const unsigned char *parent, unsigned int mode,
                         mmfile_t *result_file,
                         struct sline *sline, unsigned int cnt, int n,
-                        int num_parent, int result_deleted)
+                        int num_parent, int result_deleted,
+                        struct userdiff_driver *textconv,
+                        const char *path)
 {
        unsigned int p_lno, lno;
        unsigned long nmask = (1UL << n);
@@ -217,7 +227,7 @@ static void combine_diff(const unsigned char *parent, unsigned int mode,
        if (result_deleted)
                return; /* result deleted */
 
-       parent_file.ptr = grab_blob(parent, mode, &sz);
+       parent_file.ptr = grab_blob(parent, mode, &sz, textconv, path);
        parent_file.size = sz;
        memset(&xpp, 0, sizeof(xpp));
        xpp.flags = 0;
@@ -681,6 +691,82 @@ static void dump_quoted_path(const char *head,
        puts(buf.buf);
 }
 
+static void show_combined_header(struct combine_diff_path *elem,
+                                int num_parent,
+                                int dense,
+                                struct rev_info *rev,
+                                int mode_differs,
+                                int show_file_header)
+{
+       struct diff_options *opt = &rev->diffopt;
+       int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
+       const char *a_prefix = opt->a_prefix ? opt->a_prefix : "a/";
+       const char *b_prefix = opt->b_prefix ? opt->b_prefix : "b/";
+       int use_color = DIFF_OPT_TST(opt, COLOR_DIFF);
+       const char *c_meta = diff_get_color(use_color, DIFF_METAINFO);
+       const char *c_reset = diff_get_color(use_color, DIFF_RESET);
+       const char *abb;
+       int added = 0;
+       int deleted = 0;
+       int i;
+
+       if (rev->loginfo && !rev->no_commit_id)
+               show_log(rev);
+
+       dump_quoted_path(dense ? "diff --cc " : "diff --combined ",
+                        "", elem->path, c_meta, c_reset);
+       printf("%sindex ", c_meta);
+       for (i = 0; i < num_parent; i++) {
+               abb = find_unique_abbrev(elem->parent[i].sha1,
+                                        abbrev);
+               printf("%s%s", i ? "," : "", abb);
+       }
+       abb = find_unique_abbrev(elem->sha1, abbrev);
+       printf("..%s%s\n", abb, c_reset);
+
+       if (mode_differs) {
+               deleted = !elem->mode;
+
+               /* We say it was added if nobody had it */
+               added = !deleted;
+               for (i = 0; added && i < num_parent; i++)
+                       if (elem->parent[i].status !=
+                           DIFF_STATUS_ADDED)
+                               added = 0;
+               if (added)
+                       printf("%snew file mode %06o",
+                              c_meta, elem->mode);
+               else {
+                       if (deleted)
+                               printf("%sdeleted file ", c_meta);
+                       printf("mode ");
+                       for (i = 0; i < num_parent; i++) {
+                               printf("%s%06o", i ? "," : "",
+                                      elem->parent[i].mode);
+                       }
+                       if (elem->mode)
+                               printf("..%06o", elem->mode);
+               }
+               printf("%s\n", c_reset);
+       }
+
+       if (!show_file_header)
+               return;
+
+       if (added)
+               dump_quoted_path("--- ", "", "/dev/null",
+                                c_meta, c_reset);
+       else
+               dump_quoted_path("--- ", a_prefix, elem->path,
+                                c_meta, c_reset);
+       if (deleted)
+               dump_quoted_path("+++ ", "", "/dev/null",
+                                c_meta, c_reset);
+       else
+               dump_quoted_path("+++ ", b_prefix, elem->path,
+                                c_meta, c_reset);
+}
+
 static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                            int dense, struct rev_info *rev)
 {
@@ -692,17 +778,22 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
        int mode_differs = 0;
        int i, show_hunks;
        int working_tree_file = is_null_sha1(elem->sha1);
-       int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
-       const char *a_prefix, *b_prefix;
        mmfile_t result_file;
+       struct userdiff_driver *userdiff;
+       struct userdiff_driver *textconv = NULL;
+       int is_binary;
 
        context = opt->context;
-       a_prefix = opt->a_prefix ? opt->a_prefix : "a/";
-       b_prefix = opt->b_prefix ? opt->b_prefix : "b/";
+       userdiff = userdiff_find_by_path(elem->path);
+       if (!userdiff)
+               userdiff = userdiff_find_by_name("default");
+       if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV))
+               textconv = userdiff_get_textconv(userdiff);
 
        /* Read the result of merge first */
        if (!working_tree_file)
-               result = grab_blob(elem->sha1, elem->mode, &result_size);
+               result = grab_blob(elem->sha1, elem->mode, &result_size,
+                                  textconv, elem->path);
        else {
                /* Used by diff-tree to read from the working tree */
                struct stat st;
@@ -725,9 +816,16 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                } else if (S_ISDIR(st.st_mode)) {
                        unsigned char sha1[20];
                        if (resolve_gitlink_ref(elem->path, "HEAD", sha1) < 0)
-                               result = grab_blob(elem->sha1, elem->mode, &result_size);
+                               result = grab_blob(elem->sha1, elem->mode,
+                                                  &result_size, NULL, NULL);
                        else
-                               result = grab_blob(sha1, elem->mode, &result_size);
+                               result = grab_blob(sha1, elem->mode,
+                                                  &result_size, NULL, NULL);
+               } else if (textconv) {
+                       struct diff_filespec *df = alloc_filespec(elem->path);
+                       fill_filespec(df, null_sha1, st.st_mode);
+                       result_size = fill_textconv(textconv, df, &result);
+                       free_filespec(df);
                } else if (0 <= (fd = open(elem->path, O_RDONLY))) {
                        size_t len = xsize_t(st.st_size);
                        ssize_t done;
@@ -777,6 +875,38 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                        close(fd);
        }
 
+       for (i = 0; i < num_parent; i++) {
+               if (elem->parent[i].mode != elem->mode) {
+                       mode_differs = 1;
+                       break;
+               }
+       }
+
+       if (textconv)
+               is_binary = 0;
+       else if (userdiff->binary != -1)
+               is_binary = userdiff->binary;
+       else {
+               is_binary = buffer_is_binary(result, result_size);
+               for (i = 0; !is_binary && i < num_parent; i++) {
+                       char *buf;
+                       unsigned long size;
+                       buf = grab_blob(elem->parent[i].sha1,
+                                       elem->parent[i].mode,
+                                       &size, NULL, NULL);
+                       if (buffer_is_binary(buf, size))
+                               is_binary = 1;
+                       free(buf);
+               }
+       }
+       if (is_binary) {
+               show_combined_header(elem, num_parent, dense, rev,
+                                    mode_differs, 0);
+               printf("Binary files differ\n");
+               free(result);
+               return;
+       }
+
        for (cnt = 0, cp = result; cp < result + result_size; cp++) {
                if (*cp == '\n')
                        cnt++;
@@ -824,71 +954,15 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                        combine_diff(elem->parent[i].sha1,
                                     elem->parent[i].mode,
                                     &result_file, sline,
-                                    cnt, i, num_parent, result_deleted);
-               if (elem->parent[i].mode != elem->mode)
-                       mode_differs = 1;
+                                    cnt, i, num_parent, result_deleted,
+                                    textconv, elem->path);
        }
 
        show_hunks = make_hunks(sline, cnt, num_parent, dense);
 
        if (show_hunks || mode_differs || working_tree_file) {
-               const char *abb;
-               int use_color = DIFF_OPT_TST(opt, COLOR_DIFF);
-               const char *c_meta = diff_get_color(use_color, DIFF_METAINFO);
-               const char *c_reset = diff_get_color(use_color, DIFF_RESET);
-               int added = 0;
-               int deleted = 0;
-
-               if (rev->loginfo && !rev->no_commit_id)
-                       show_log(rev);
-               dump_quoted_path(dense ? "diff --cc " : "diff --combined ",
-                                "", elem->path, c_meta, c_reset);
-               printf("%sindex ", c_meta);
-               for (i = 0; i < num_parent; i++) {
-                       abb = find_unique_abbrev(elem->parent[i].sha1,
-                                                abbrev);
-                       printf("%s%s", i ? "," : "", abb);
-               }
-               abb = find_unique_abbrev(elem->sha1, abbrev);
-               printf("..%s%s\n", abb, c_reset);
-
-               if (mode_differs) {
-                       deleted = !elem->mode;
-
-                       /* We say it was added if nobody had it */
-                       added = !deleted;
-                       for (i = 0; added && i < num_parent; i++)
-                               if (elem->parent[i].status !=
-                                   DIFF_STATUS_ADDED)
-                                       added = 0;
-                       if (added)
-                               printf("%snew file mode %06o",
-                                      c_meta, elem->mode);
-                       else {
-                               if (deleted)
-                                       printf("%sdeleted file ", c_meta);
-                               printf("mode ");
-                               for (i = 0; i < num_parent; i++) {
-                                       printf("%s%06o", i ? "," : "",
-                                              elem->parent[i].mode);
-                               }
-                               if (elem->mode)
-                                       printf("..%06o", elem->mode);
-                       }
-                       printf("%s\n", c_reset);
-               }
-               if (added)
-                       dump_quoted_path("--- ", "", "/dev/null",
-                                        c_meta, c_reset);
-               else
-                       dump_quoted_path("--- ", a_prefix, elem->path,
-                                        c_meta, c_reset);
-               if (deleted)
-                       dump_quoted_path("+++ ", "", "/dev/null",
-                                        c_meta, c_reset);
-               else
-                       dump_quoted_path("+++ ", b_prefix, elem->path,
-                                        c_meta, c_reset);
+               show_combined_header(elem, num_parent, dense, rev,
+                                    mode_differs, 1);
                dump_sline(sline, cnt, num_parent,
                           DIFF_OPT_TST(opt, COLOR_DIFF), result_deleted);
        }
index 3114bd1781c3c5e735dfc1b1a7b4131270992f14..a2d571b97410fa857b4c177325c4556dac50fe3f 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -69,9 +69,11 @@ enum cmit_fmt {
 };
 
 struct pretty_print_context {
+       enum cmit_fmt fmt;
        int abbrev;
        const char *subject;
        const char *after_subject;
+       int preserve_subject;
        enum date_mode date_mode;
        int need_8bit_cte;
        int show_notes;
@@ -96,20 +98,20 @@ extern void userformat_find_requirements(const char *fmt, struct userformat_want
 extern void format_commit_message(const struct commit *commit,
                                  const char *format, struct strbuf *sb,
                                  const struct pretty_print_context *context);
-extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
-                               struct strbuf *sb,
-                               const struct pretty_print_context *context);
-void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
-                  const char *line, enum date_mode dmode,
-                  const char *encoding);
-void pp_title_line(enum cmit_fmt fmt,
+extern void pretty_print_commit(const struct pretty_print_context *pp,
+                               const struct commit *commit,
+                               struct strbuf *sb);
+extern void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
+                          struct strbuf *sb);
+void pp_user_info(const struct pretty_print_context *pp,
+                 const char *what, struct strbuf *sb,
+                 const char *line, const char *encoding);
+void pp_title_line(const struct pretty_print_context *pp,
                   const char **msg_p,
                   struct strbuf *sb,
-                  const char *subject,
-                  const char *after_subject,
                   const char *encoding,
                   int need_8bit_cte);
-void pp_remainder(enum cmit_fmt fmt,
+void pp_remainder(const struct pretty_print_context *pp,
                  const char **msg_p,
                  struct strbuf *sb,
                  int indent);
index f6e9ff7762356e099d2fe20fd31359bc0a1f68a2..6ef0cc4f99becd772a6fed1cfe2484a67b3a9000 100644 (file)
@@ -178,7 +178,7 @@ static int ask_yes_no_if_possible(const char *format, ...)
        vsnprintf(question, sizeof(question), format, args);
        va_end(args);
 
-       if ((retry_hook[0] = getenv("GIT_ASK_YESNO"))) {
+       if ((retry_hook[0] = mingw_getenv("GIT_ASK_YESNO"))) {
                retry_hook[1] = question;
                return !run_command_v_opt(retry_hook, 0);
        }
@@ -599,19 +599,6 @@ char *mingw_getcwd(char *pointer, int len)
        return ret;
 }
 
-#undef getenv
-char *mingw_getenv(const char *name)
-{
-       char *result = getenv(name);
-       if (!result && !strcmp(name, "TMPDIR")) {
-               /* on Windows it is TMP and TEMP */
-               result = getenv("TMP");
-               if (!result)
-                       result = getenv("TEMP");
-       }
-       return result;
-}
-
 /*
  * See http://msdn2.microsoft.com/en-us/library/17w5ykft(vs.71).aspx
  * (Parsing C++ Command-Line Arguments)
@@ -711,7 +698,7 @@ static const char *parse_interpreter(const char *cmd)
  */
 static char **get_path_split(void)
 {
-       char *p, **path, *envpath = getenv("PATH");
+       char *p, **path, *envpath = mingw_getenv("PATH");
        int i, n = 0;
 
        if (!envpath || !*envpath)
@@ -1128,6 +1115,36 @@ char **make_augmented_environ(const char *const *vars)
        return env;
 }
 
+#undef getenv
+
+/*
+ * The system's getenv looks up the name in a case-insensitive manner.
+ * This version tries a case-sensitive lookup and falls back to
+ * case-insensitive if nothing was found.  This is necessary because,
+ * as a prominent example, CMD sets 'Path', but not 'PATH'.
+ * Warning: not thread-safe.
+ */
+static char *getenv_cs(const char *name)
+{
+       size_t len = strlen(name);
+       int i = lookup_env(environ, name, len);
+       if (i >= 0)
+               return environ[i] + len + 1;    /* skip past name and '=' */
+       return getenv(name);
+}
+
+char *mingw_getenv(const char *name)
+{
+       char *result = getenv_cs(name);
+       if (!result && !strcmp(name, "TMPDIR")) {
+               /* on Windows it is TMP and TEMP */
+               result = getenv_cs("TMP");
+               if (!result)
+                       result = getenv_cs("TEMP");
+       }
+       return result;
+}
+
 /*
  * Note, this isn't a complete replacement for getaddrinfo. It assumes
  * that service contains a numerical port, or that it is null. It
index 547568b9181d61d50f06cf5b4b0ab43af78e5aa2..ce9dd980eb211c0301c4008249c61fb0b09f1280 100644 (file)
@@ -300,6 +300,15 @@ int winansi_fprintf(FILE *stream, const char *format, ...) __attribute__((format
 
 #define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':')
 #define is_dir_sep(c) ((c) == '/' || (c) == '\\')
+static inline char *mingw_find_last_dir_sep(const char *path)
+{
+       char *ret = NULL;
+       for (; *path; ++path)
+               if (is_dir_sep(*path))
+                       ret = (char *)path;
+       return ret;
+}
+#define find_last_dir_sep mingw_find_last_dir_sep
 #define PATH_SEP ';'
 #define PRIuMAX "I64u"
 
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..ab371012a22f39bf31f512e276e12f55b6d1b8b7 100644 (file)
@@ -18,6 +18,7 @@ bindir = @bindir@
 gitexecdir = @libexecdir@/git-core
 datarootdir = @datarootdir@
 template_dir = @datadir@/git-core/templates
+sysconfdir = @sysconfdir@
 
 mandir=@mandir@
 
@@ -61,6 +62,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..5a8309076dc633f431797c4bd59876490b0d7408 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
@@ -2057,7 +2058,7 @@ _git_config ()
                color.ui
                commit.status
                commit.template
-               core.abbrevguard
+               core.abbrev
                core.askpass
                core.attributesfile
                core.autocrlf
index efc7e07d475c66f7835dc6cbbd3bc358f01c41c3..85939c29be67d4179a2afb9069b575afed4d8bb2 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -813,3 +813,400 @@ int renormalize_buffer(const char *path, const char *src, size_t len, struct str
        }
        return ret | convert_to_git(path, src, len, dst, 0);
 }
+
+/*****************************************************************
+ *
+ * Streaming converison support
+ *
+ *****************************************************************/
+
+typedef int (*filter_fn)(struct stream_filter *,
+                        const char *input, size_t *isize_p,
+                        char *output, size_t *osize_p);
+typedef void (*free_fn)(struct stream_filter *);
+
+struct stream_filter_vtbl {
+       filter_fn filter;
+       free_fn free;
+};
+
+struct stream_filter {
+       struct stream_filter_vtbl *vtbl;
+};
+
+static int null_filter_fn(struct stream_filter *filter,
+                         const char *input, size_t *isize_p,
+                         char *output, size_t *osize_p)
+{
+       size_t count;
+
+       if (!input)
+               return 0; /* we do not keep any states */
+       count = *isize_p;
+       if (*osize_p < count)
+               count = *osize_p;
+       if (count) {
+               memmove(output, input, count);
+               *isize_p -= count;
+               *osize_p -= count;
+       }
+       return 0;
+}
+
+static void null_free_fn(struct stream_filter *filter)
+{
+       ; /* nothing -- null instances are shared */
+}
+
+static struct stream_filter_vtbl null_vtbl = {
+       null_filter_fn,
+       null_free_fn,
+};
+
+static struct stream_filter null_filter_singleton = {
+       &null_vtbl,
+};
+
+int is_null_stream_filter(struct stream_filter *filter)
+{
+       return filter == &null_filter_singleton;
+}
+
+
+/*
+ * LF-to-CRLF filter
+ */
+static int lf_to_crlf_filter_fn(struct stream_filter *filter,
+                               const char *input, size_t *isize_p,
+                               char *output, size_t *osize_p)
+{
+       size_t count;
+
+       if (!input)
+               return 0; /* we do not keep any states */
+       count = *isize_p;
+       if (count) {
+               size_t i, o;
+               for (i = o = 0; o < *osize_p && i < count; i++) {
+                       char ch = input[i];
+                       if (ch == '\n') {
+                               if (o + 1 < *osize_p)
+                                       output[o++] = '\r';
+                               else
+                                       break;
+                       }
+                       output[o++] = ch;
+               }
+
+               *osize_p -= o;
+               *isize_p -= i;
+       }
+       return 0;
+}
+
+static struct stream_filter_vtbl lf_to_crlf_vtbl = {
+       lf_to_crlf_filter_fn,
+       null_free_fn,
+};
+
+static struct stream_filter lf_to_crlf_filter_singleton = {
+       &lf_to_crlf_vtbl,
+};
+
+
+/*
+ * Cascade filter
+ */
+#define FILTER_BUFFER 1024
+struct cascade_filter {
+       struct stream_filter filter;
+       struct stream_filter *one;
+       struct stream_filter *two;
+       char buf[FILTER_BUFFER];
+       int end, ptr;
+};
+
+static int cascade_filter_fn(struct stream_filter *filter,
+                            const char *input, size_t *isize_p,
+                            char *output, size_t *osize_p)
+{
+       struct cascade_filter *cas = (struct cascade_filter *) filter;
+       size_t filled = 0;
+       size_t sz = *osize_p;
+       size_t to_feed, remaining;
+
+       /*
+        * input -- (one) --> buf -- (two) --> output
+        */
+       while (filled < sz) {
+               remaining = sz - filled;
+
+               /* do we already have something to feed two with? */
+               if (cas->ptr < cas->end) {
+                       to_feed = cas->end - cas->ptr;
+                       if (stream_filter(cas->two,
+                                         cas->buf + cas->ptr, &to_feed,
+                                         output + filled, &remaining))
+                               return -1;
+                       cas->ptr += (cas->end - cas->ptr) - to_feed;
+                       filled = sz - remaining;
+                       continue;
+               }
+
+               /* feed one from upstream and have it emit into our buffer */
+               to_feed = input ? *isize_p : 0;
+               if (input && !to_feed)
+                       break;
+               remaining = sizeof(cas->buf);
+               if (stream_filter(cas->one,
+                                 input, &to_feed,
+                                 cas->buf, &remaining))
+                       return -1;
+               cas->end = sizeof(cas->buf) - remaining;
+               cas->ptr = 0;
+               if (input) {
+                       size_t fed = *isize_p - to_feed;
+                       *isize_p -= fed;
+                       input += fed;
+               }
+
+               /* do we know that we drained one completely? */
+               if (input || cas->end)
+                       continue;
+
+               /* tell two to drain; we have nothing more to give it */
+               to_feed = 0;
+               remaining = sz - filled;
+               if (stream_filter(cas->two,
+                                 NULL, &to_feed,
+                                 output + filled, &remaining))
+                       return -1;
+               if (remaining == (sz - filled))
+                       break; /* completely drained two */
+               filled = sz - remaining;
+       }
+       *osize_p -= filled;
+       return 0;
+}
+
+static void cascade_free_fn(struct stream_filter *filter)
+{
+       struct cascade_filter *cas = (struct cascade_filter *)filter;
+       free_stream_filter(cas->one);
+       free_stream_filter(cas->two);
+       free(filter);
+}
+
+static struct stream_filter_vtbl cascade_vtbl = {
+       cascade_filter_fn,
+       cascade_free_fn,
+};
+
+static struct stream_filter *cascade_filter(struct stream_filter *one,
+                                           struct stream_filter *two)
+{
+       struct cascade_filter *cascade;
+
+       if (!one || is_null_stream_filter(one))
+               return two;
+       if (!two || is_null_stream_filter(two))
+               return one;
+
+       cascade = xmalloc(sizeof(*cascade));
+       cascade->one = one;
+       cascade->two = two;
+       cascade->end = cascade->ptr = 0;
+       cascade->filter.vtbl = &cascade_vtbl;
+       return (struct stream_filter *)cascade;
+}
+
+/*
+ * ident filter
+ */
+#define IDENT_DRAINING (-1)
+#define IDENT_SKIPPING (-2)
+struct ident_filter {
+       struct stream_filter filter;
+       struct strbuf left;
+       int state;
+       char ident[45]; /* ": x40 $" */
+};
+
+static int is_foreign_ident(const char *str)
+{
+       int i;
+
+       if (prefixcmp(str, "$Id: "))
+               return 0;
+       for (i = 5; str[i]; i++) {
+               if (isspace(str[i]) && str[i+1] != '$')
+                       return 1;
+       }
+       return 0;
+}
+
+static void ident_drain(struct ident_filter *ident, char **output_p, size_t *osize_p)
+{
+       size_t to_drain = ident->left.len;
+
+       if (*osize_p < to_drain)
+               to_drain = *osize_p;
+       if (to_drain) {
+               memcpy(*output_p, ident->left.buf, to_drain);
+               strbuf_remove(&ident->left, 0, to_drain);
+               *output_p += to_drain;
+               *osize_p -= to_drain;
+       }
+       if (!ident->left.len)
+               ident->state = 0;
+}
+
+static int ident_filter_fn(struct stream_filter *filter,
+                          const char *input, size_t *isize_p,
+                          char *output, size_t *osize_p)
+{
+       struct ident_filter *ident = (struct ident_filter *)filter;
+       static const char head[] = "$Id";
+
+       if (!input) {
+               /* drain upon eof */
+               switch (ident->state) {
+               default:
+                       strbuf_add(&ident->left, head, ident->state);
+               case IDENT_SKIPPING:
+                       /* fallthru */
+               case IDENT_DRAINING:
+                       ident_drain(ident, &output, osize_p);
+               }
+               return 0;
+       }
+
+       while (*isize_p || (ident->state == IDENT_DRAINING)) {
+               int ch;
+
+               if (ident->state == IDENT_DRAINING) {
+                       ident_drain(ident, &output, osize_p);
+                       if (!*osize_p)
+                               break;
+                       continue;
+               }
+
+               ch = *(input++);
+               (*isize_p)--;
+
+               if (ident->state == IDENT_SKIPPING) {
+                       /*
+                        * Skipping until '$' or LF, but keeping them
+                        * in case it is a foreign ident.
+                        */
+                       strbuf_addch(&ident->left, ch);
+                       if (ch != '\n' && ch != '$')
+                               continue;
+                       if (ch == '$' && !is_foreign_ident(ident->left.buf)) {
+                               strbuf_setlen(&ident->left, sizeof(head) - 1);
+                               strbuf_addstr(&ident->left, ident->ident);
+                       }
+                       ident->state = IDENT_DRAINING;
+                       continue;
+               }
+
+               if (ident->state < sizeof(head) &&
+                   head[ident->state] == ch) {
+                       ident->state++;
+                       continue;
+               }
+
+               if (ident->state)
+                       strbuf_add(&ident->left, head, ident->state);
+               if (ident->state == sizeof(head) - 1) {
+                       if (ch != ':' && ch != '$') {
+                               strbuf_addch(&ident->left, ch);
+                               ident->state = 0;
+                               continue;
+                       }
+
+                       if (ch == ':') {
+                               strbuf_addch(&ident->left, ch);
+                               ident->state = IDENT_SKIPPING;
+                       } else {
+                               strbuf_addstr(&ident->left, ident->ident);
+                               ident->state = IDENT_DRAINING;
+                       }
+                       continue;
+               }
+
+               strbuf_addch(&ident->left, ch);
+               ident->state = IDENT_DRAINING;
+       }
+       return 0;
+}
+
+static void ident_free_fn(struct stream_filter *filter)
+{
+       struct ident_filter *ident = (struct ident_filter *)filter;
+       strbuf_release(&ident->left);
+       free(filter);
+}
+
+static struct stream_filter_vtbl ident_vtbl = {
+       ident_filter_fn,
+       ident_free_fn,
+};
+
+static struct stream_filter *ident_filter(const unsigned char *sha1)
+{
+       struct ident_filter *ident = xmalloc(sizeof(*ident));
+
+       sprintf(ident->ident, ": %s $", sha1_to_hex(sha1));
+       strbuf_init(&ident->left, 0);
+       ident->filter.vtbl = &ident_vtbl;
+       ident->state = 0;
+       return (struct stream_filter *)ident;
+}
+
+/*
+ * Return an appropriately constructed filter for the path, or NULL if
+ * the contents cannot be filtered without reading the whole thing
+ * in-core.
+ *
+ * Note that you would be crazy to set CRLF, smuge/clean or ident to a
+ * large binary blob you would want us not to slurp into the memory!
+ */
+struct stream_filter *get_stream_filter(const char *path, const unsigned char *sha1)
+{
+       struct conv_attrs ca;
+       enum crlf_action crlf_action;
+       struct stream_filter *filter = NULL;
+
+       convert_attrs(&ca, path);
+
+       if (ca.drv && (ca.drv->smudge || ca.drv->clean))
+               return filter;
+
+       if (ca.ident)
+               filter = ident_filter(sha1);
+
+       crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr);
+
+       if ((crlf_action == CRLF_BINARY) || (crlf_action == CRLF_INPUT) ||
+           (crlf_action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE))
+               filter = cascade_filter(filter, &null_filter_singleton);
+
+       else if (output_eol(crlf_action) == EOL_CRLF &&
+                !(crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS))
+               filter = cascade_filter(filter, &lf_to_crlf_filter_singleton);
+
+       return filter;
+}
+
+void free_stream_filter(struct stream_filter *filter)
+{
+       filter->vtbl->free(filter);
+}
+
+int stream_filter(struct stream_filter *filter,
+                 const char *input, size_t *isize_p,
+                 char *output, size_t *osize_p)
+{
+       return filter->vtbl->filter(filter, input, isize_p, output, osize_p);
+}
diff --git a/convert.h b/convert.h
new file mode 100644 (file)
index 0000000..d799a16
--- /dev/null
+++ b/convert.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#ifndef CONVERT_H
+#define CONVERT_H
+
+enum safe_crlf {
+       SAFE_CRLF_FALSE = 0,
+       SAFE_CRLF_FAIL = 1,
+       SAFE_CRLF_WARN = 2
+};
+
+extern enum safe_crlf safe_crlf;
+
+enum auto_crlf {
+       AUTO_CRLF_FALSE = 0,
+       AUTO_CRLF_TRUE = 1,
+       AUTO_CRLF_INPUT = -1
+};
+
+extern enum auto_crlf auto_crlf;
+
+enum eol {
+       EOL_UNSET,
+       EOL_CRLF,
+       EOL_LF,
+#ifdef NATIVE_CRLF
+       EOL_NATIVE = EOL_CRLF
+#else
+       EOL_NATIVE = EOL_LF
+#endif
+};
+
+extern enum eol core_eol;
+
+/* returns 1 if *dst was used */
+extern int convert_to_git(const char *path, const char *src, size_t len,
+                         struct strbuf *dst, enum safe_crlf checksafe);
+extern int convert_to_working_tree(const char *path, const char *src,
+                                  size_t len, struct strbuf *dst);
+extern int renormalize_buffer(const char *path, const char *src, size_t len,
+                             struct strbuf *dst);
+
+/*****************************************************************
+ *
+ * Streaming converison support
+ *
+ *****************************************************************/
+
+struct stream_filter; /* opaque */
+
+extern struct stream_filter *get_stream_filter(const char *path, const unsigned char *);
+extern void free_stream_filter(struct stream_filter *);
+extern int is_null_stream_filter(struct stream_filter *);
+
+/*
+ * Use as much input up to *isize_p and fill output up to *osize_p;
+ * update isize_p and osize_p to indicate how much buffer space was
+ * consumed and filled. Return 0 on success, non-zero on error.
+ *
+ * Some filters may need to buffer the input and look-ahead inside it
+ * to decide what to output, and they may consume more than zero bytes
+ * of input and still not produce any output. After feeding all the
+ * input, pass NULL as input and keep calling this function, to let
+ * such filters know there is no more input coming and it is time for
+ * them to produce the remaining output based on the buffered input.
+ */
+extern int stream_filter(struct stream_filter *,
+                        const char *input, size_t *isize_p,
+                        char *output, size_t *osize_p);
+
+#endif /* CONVERT_H */
index 3b5f2242a597ff1b44a3af6be72cb14e6e0d5455..2e09500c8210e22d6c81484aa198f1d811a9933d 100644 (file)
@@ -102,9 +102,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                int changed;
                unsigned dirty_submodule = 0;
 
-               if (DIFF_OPT_TST(&revs->diffopt, QUICK) &&
-                   !revs->diffopt.filter &&
-                   DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
+               if (diff_can_quit_early(&revs->diffopt))
                        break;
 
                if (!ce_path_match(ce, &revs->prune_data))
@@ -435,8 +433,13 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o)
        if (tree == o->df_conflict_entry)
                tree = NULL;
 
-       if (ce_path_match(idx ? idx : tree, &revs->prune_data))
+       if (ce_path_match(idx ? idx : tree, &revs->prune_data)) {
                do_oneway_diff(o, idx, tree);
+               if (diff_can_quit_early(&revs->diffopt)) {
+                       o->exiting_early = 1;
+                       return -1;
+               }
+       }
 
        return 0;
 }
diff --git a/diff.c b/diff.c
index 8f4815bfd7d798f2a99131cd2e79155e933c7876..918c70ca457bcdca54b9d524ff14b60d88b5a631 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -1316,9 +1316,10 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
        int i, len, add, del, adds = 0, dels = 0;
        uintmax_t max_change = 0, max_len = 0;
        int total_files = data->nr;
-       int width, name_width;
+       int width, name_width, count;
        const char *reset, *add_c, *del_c;
        const char *line_prefix = "";
+       int extra_shown = 0;
        struct strbuf *msg = NULL;
 
        if (data->nr == 0)
@@ -1331,6 +1332,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
 
        width = options->stat_width ? options->stat_width : 80;
        name_width = options->stat_name_width ? options->stat_name_width : 50;
+       count = options->stat_count ? options->stat_count : data->nr;
 
        /* Sanity: give at least 5 columns to the graph,
         * but leave at least 10 columns for the name.
@@ -1347,9 +1349,14 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
        add_c = diff_get_color_opt(options, DIFF_FILE_NEW);
        del_c = diff_get_color_opt(options, DIFF_FILE_OLD);
 
-       for (i = 0; i < data->nr; i++) {
+       for (i = 0; (i < count) && (i < data->nr); i++) {
                struct diffstat_file *file = data->files[i];
                uintmax_t change = file->added + file->deleted;
+               if (!data->files[i]->is_renamed &&
+                        (change == 0)) {
+                       count++; /* not shown == room for one more */
+                       continue;
+               }
                fill_print_name(file);
                len = strlen(file->print_name);
                if (max_len < len)
@@ -1360,6 +1367,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                if (max_change < change)
                        max_change = change;
        }
+       count = i; /* min(count, data->nr) */
 
        /* Compute the width of the graph part;
         * 10 is for one blank at the beginning of the line plus
@@ -1374,13 +1382,18 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
        else
                width = max_change;
 
-       for (i = 0; i < data->nr; i++) {
+       for (i = 0; i < count; i++) {
                const char *prefix = "";
                char *name = data->files[i]->print_name;
                uintmax_t added = data->files[i]->added;
                uintmax_t deleted = data->files[i]->deleted;
                int name_len;
 
+               if (!data->files[i]->is_renamed &&
+                        (added + deleted == 0)) {
+                       total_files--;
+                       continue;
+               }
                /*
                 * "scale" the filename
                 */
@@ -1415,11 +1428,6 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                        fprintf(options->file, "  Unmerged\n");
                        continue;
                }
-               else if (!data->files[i]->is_renamed &&
-                        (added + deleted == 0)) {
-                       total_files--;
-                       continue;
-               }
 
                /*
                 * scale the add/delete
@@ -1441,6 +1449,20 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                show_graph(options->file, '-', del, del_c, reset);
                fprintf(options->file, "\n");
        }
+       for (i = count; i < data->nr; i++) {
+               uintmax_t added = data->files[i]->added;
+               uintmax_t deleted = data->files[i]->deleted;
+               if (!data->files[i]->is_renamed &&
+                        (added + deleted == 0)) {
+                       total_files--;
+                       continue;
+               }
+               adds += added;
+               dels += deleted;
+               if (!extra_shown)
+                       fprintf(options->file, "%s ...\n", line_prefix);
+               extra_shown = 1;
+       }
        fprintf(options->file, "%s", line_prefix);
        fprintf(options->file,
               " %d files changed, %d insertions(+), %d deletions(-)\n",
@@ -1984,19 +2006,7 @@ struct userdiff_driver *get_textconv(struct diff_filespec *one)
                return NULL;
 
        diff_filespec_load_driver(one);
-       if (!one->driver->textconv)
-               return NULL;
-
-       if (one->driver->textconv_want_cache && !one->driver->textconv_cache) {
-               struct notes_cache *c = xmalloc(sizeof(*c));
-               struct strbuf name = STRBUF_INIT;
-
-               strbuf_addf(&name, "textconv/%s", one->driver->name);
-               notes_cache_init(c, name.buf, one->driver->textconv);
-               one->driver->textconv_cache = c;
-       }
-
-       return one->driver;
+       return userdiff_get_textconv(one->driver);
 }
 
 static void builtin_diff(const char *name_a,
@@ -3220,6 +3230,7 @@ static int stat_opt(struct diff_options *options, const char **av)
        char *end;
        int width = options->stat_width;
        int name_width = options->stat_name_width;
+       int count = options->stat_count;
        int argcount = 1;
 
        arg += strlen("--stat");
@@ -3247,12 +3258,24 @@ static int stat_opt(struct diff_options *options, const char **av)
                                name_width = strtoul(av[1], &end, 10);
                                argcount = 2;
                        }
+               } else if (!prefixcmp(arg, "-count")) {
+                       arg += strlen("-count");
+                       if (*arg == '=')
+                               count = strtoul(arg + 1, &end, 10);
+                       else if (!*arg && !av[1])
+                               die("Option '--stat-count' requires a value");
+                       else if (!*arg) {
+                               count = strtoul(av[1], &end, 10);
+                               argcount = 2;
+                       }
                }
                break;
        case '=':
                width = strtoul(arg+1, &end, 10);
                if (*end == ',')
                        name_width = strtoul(end+1, &end, 10);
+               if (*end == ',')
+                       count = strtoul(end+1, &end, 10);
        }
 
        /* Important! This checks all the error cases! */
@@ -3261,6 +3284,7 @@ static int stat_opt(struct diff_options *options, const char **av)
        options->output_format |= DIFF_FORMAT_DIFFSTAT;
        options->stat_name_width = name_width;
        options->stat_width = width;
+       options->stat_count = count;
        return argcount;
 }
 
@@ -3325,7 +3349,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "-s"))
                options->output_format |= DIFF_FORMAT_NO_OUTPUT;
        else if (!prefixcmp(arg, "--stat"))
-               /* --stat, --stat-width, or --stat-name-width */
+               /* --stat, --stat-width, --stat-name-width, or --stat-count */
                return stat_opt(options, av);
 
        /* renames options */
@@ -4456,6 +4480,13 @@ int diff_result_code(struct diff_options *opt, int status)
        return result;
 }
 
+int diff_can_quit_early(struct diff_options *opt)
+{
+       return (DIFF_OPT_TST(opt, QUICK) &&
+               !opt->filter &&
+               DIFF_OPT_TST(opt, HAS_CHANGES));
+}
+
 /*
  * Shall changes to this submodule be ignored?
  *
diff --git a/diff.h b/diff.h
index adb40ba273ffec74010a36e98a538fcc204919f1..b920a20f80936ab66f5fe88c950dd35e38e1e856 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -125,6 +125,7 @@ struct diff_options {
 
        int stat_width;
        int stat_name_width;
+       int stat_count;
        const char *word_regex;
        enum diff_words_type word_diff;
 
@@ -198,6 +199,8 @@ extern void diff_tree_combined_merge(const unsigned char *sha1, int, struct rev_
 
 void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b);
 
+extern int diff_can_quit_early(struct diff_options *);
+
 extern void diff_addremove(struct diff_options *,
                           int addremove,
                           unsigned mode,
diff --git a/entry.c b/entry.c
index b017167f2015623fb9c721e91d0a940abd1d5196..852fea13955475c1e2fda9cfc25a63a54a1f61c7 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "blob.h"
 #include "dir.h"
+#include "streaming.h"
 
 static void create_directories(const char *path, int path_len,
                               const struct checkout *state)
@@ -91,6 +92,91 @@ static void *read_blob_entry(struct cache_entry *ce, unsigned long *size)
        return NULL;
 }
 
+static int open_output_fd(char *path, struct cache_entry *ce, int to_tempfile)
+{
+       int symlink = (ce->ce_mode & S_IFMT) != S_IFREG;
+       if (to_tempfile) {
+               strcpy(path, symlink
+                      ? ".merge_link_XXXXXX" : ".merge_file_XXXXXX");
+               return mkstemp(path);
+       } else {
+               return create_file(path, !symlink ? ce->ce_mode : 0666);
+       }
+}
+
+static int fstat_output(int fd, const struct checkout *state, struct stat *st)
+{
+       /* use fstat() only when path == ce->name */
+       if (fstat_is_reliable() &&
+           state->refresh_cache && !state->base_dir_len) {
+               fstat(fd, st);
+               return 1;
+       }
+       return 0;
+}
+
+static int streaming_write_entry(struct cache_entry *ce, char *path,
+                                struct stream_filter *filter,
+                                const struct checkout *state, int to_tempfile,
+                                int *fstat_done, struct stat *statbuf)
+{
+       struct git_istream *st;
+       enum object_type type;
+       unsigned long sz;
+       int result = -1;
+       ssize_t kept = 0;
+       int fd = -1;
+
+       st = open_istream(ce->sha1, &type, &sz, filter);
+       if (!st)
+               return -1;
+       if (type != OBJ_BLOB)
+               goto close_and_exit;
+
+       fd = open_output_fd(path, ce, to_tempfile);
+       if (fd < 0)
+               goto close_and_exit;
+
+       for (;;) {
+               char buf[1024 * 16];
+               ssize_t wrote, holeto;
+               ssize_t readlen = read_istream(st, buf, sizeof(buf));
+
+               if (!readlen)
+                       break;
+               if (sizeof(buf) == readlen) {
+                       for (holeto = 0; holeto < readlen; holeto++)
+                               if (buf[holeto])
+                                       break;
+                       if (readlen == holeto) {
+                               kept += holeto;
+                               continue;
+                       }
+               }
+
+               if (kept && lseek(fd, kept, SEEK_CUR) == (off_t) -1)
+                       goto close_and_exit;
+               else
+                       kept = 0;
+               wrote = write_in_full(fd, buf, readlen);
+
+               if (wrote != readlen)
+                       goto close_and_exit;
+       }
+       if (kept && (lseek(fd, kept - 1, SEEK_CUR) == (off_t) -1 ||
+                    write(fd, "", 1) != 1))
+               goto close_and_exit;
+       *fstat_done = fstat_output(fd, state, statbuf);
+
+close_and_exit:
+       close_istream(st);
+       if (0 <= fd)
+               result = close(fd);
+       if (result && 0 <= fd)
+               unlink(path);
+       return result;
+}
+
 static int write_entry(struct cache_entry *ce, char *path, const struct checkout *state, int to_tempfile)
 {
        unsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;
@@ -101,6 +187,15 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
        size_t wrote, newsize = 0;
        struct stat st;
 
+       if (ce_mode_s_ifmt == S_IFREG) {
+               struct stream_filter *filter = get_stream_filter(path, ce->sha1);
+               if (filter &&
+                   !streaming_write_entry(ce, path, filter,
+                                          state, to_tempfile,
+                                          &fstat_done, &st))
+                       goto finish;
+       }
+
        switch (ce_mode_s_ifmt) {
        case S_IFREG:
        case S_IFLNK:
@@ -128,17 +223,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
                        size = newsize;
                }
 
-               if (to_tempfile) {
-                       if (ce_mode_s_ifmt == S_IFREG)
-                               strcpy(path, ".merge_file_XXXXXX");
-                       else
-                               strcpy(path, ".merge_link_XXXXXX");
-                       fd = mkstemp(path);
-               } else if (ce_mode_s_ifmt == S_IFREG) {
-                       fd = create_file(path, ce->ce_mode);
-               } else {
-                       fd = create_file(path, 0666);
-               }
+               fd = open_output_fd(path, ce, to_tempfile);
                if (fd < 0) {
                        free(new);
                        return error("unable to create file %s (%s)",
@@ -146,12 +231,8 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
                }
 
                wrote = write_in_full(fd, new, size);
-               /* use fstat() only when path == ce->name */
-               if (fstat_is_reliable() &&
-                   state->refresh_cache && !to_tempfile && !state->base_dir_len) {
-                       fstat(fd, &st);
-                       fstat_done = 1;
-               }
+               if (!to_tempfile)
+                       fstat_done = fstat_output(fd, state, &st);
                close(fd);
                free(new);
                if (wrote != size)
@@ -167,6 +248,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
                return error("unknown file mode for %s in index", path);
        }
 
+finish:
        if (state->refresh_cache) {
                if (!fstat_done)
                        lstat(ce->name, &st);
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 6cdd5910db50c96df3d149fba172750cb10c09cb..463c741dfc259927b4429f19972ced21d87c215e 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -37,13 +37,14 @@ rerere-autoupdate update the index with reused conflict resolution if possible
 rebasing*       (internal use for git-rebase)"
 
 . git-sh-setup
+. git-sh-i18n
 prefix=$(git rev-parse --show-prefix)
 set_reflog_action am
 require_work_tree
 cd_to_toplevel
 
 git var GIT_COMMITTER_IDENT >/dev/null ||
-       die "You need to set your committer info first"
+       die "$(gettext "You need to set your committer info first")"
 
 if git rev-parse --verify -q HEAD >/dev/null
 then
@@ -88,8 +89,11 @@ safe_to_abort () {
        then
                return 0
        fi
-       echo >&2 "You seem to have moved HEAD since the last 'am' failure."
-       echo >&2 "Not rewinding to ORIG_HEAD"
+       (
+               gettext "You seem to have moved HEAD since the last 'am' failure.
+Not rewinding to ORIG_HEAD" &&
+               echo
+       ) >&2
        return 1
 }
 
@@ -98,9 +102,9 @@ stop_here_user_resolve () {
            printf '%s\n' "$resolvemsg"
            stop_here $1
     fi
-    echo "When you have resolved this problem run \"$cmdline --resolved\"."
-    echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
-    echo "To restore the original branch and stop patching run \"$cmdline --abort\"."
+    eval_gettext "When you have resolved this problem run \"\$cmdline --resolved\".
+If you would prefer to skip this patch, instead run \"\$cmdline --skip\".
+To restore the original branch and stop patching run \"\$cmdline --abort\"."; echo
 
     stop_here $1
 }
@@ -114,7 +118,7 @@ go_next () {
 
 cannot_fallback () {
        echo "$1"
-       echo "Cannot fall back to three-way merge."
+       gettext "Cannot fall back to three-way merge."; echo
        exit 1
 }
 
@@ -129,7 +133,7 @@ fall_back_3way () {
        "$dotest/patch" &&
     GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
     git write-tree >"$dotest/patch-merge-base+" ||
-    cannot_fallback "Repository lacks necessary blobs to fall back on 3-way merge."
+    cannot_fallback "$(gettext "Repository lacks necessary blobs to fall back on 3-way merge.")"
 
     say Using index info to reconstruct a base tree...
     if GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
@@ -138,8 +142,8 @@ fall_back_3way () {
        mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
        mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
     else
-        cannot_fallback "Did you hand edit your patch?
-It does not apply to blobs recorded in its index."
+       cannot_fallback "$(gettext "Did you hand edit your patch?
+It does not apply to blobs recorded in its index.")"
     fi
 
     test -f "$dotest/patch-merge-index" &&
@@ -147,7 +151,7 @@ It does not apply to blobs recorded in its index."
     orig_tree=$(cat "$dotest/patch-merge-base") &&
     rm -fr "$dotest"/patch-merge-* || exit 1
 
-    say Falling back to patching base and 3-way merge...
+    say "$(gettext "Falling back to patching base and 3-way merge...")"
 
     # This is not so wrong.  Depending on which base we picked,
     # orig_tree may be wildly different from ours, but his_tree
@@ -254,7 +258,7 @@ split_patches () {
        stgit-series)
                if test $# -ne 1
                then
-                       clean_abort "Only one StGIT patch series can be applied at once"
+                       clean_abort "$(gettext "Only one StGIT patch series can be applied at once")"
                fi
                series_dir=`dirname "$1"`
                series_file="$1"
@@ -306,9 +310,9 @@ split_patches () {
                ;;
        *)
                if test -n "$parse_patch" ; then
-                       clean_abort "Patch format $patch_format is not supported."
+                       clean_abort "$(eval_gettext "Patch format \$patch_format is not supported.")"
                else
-                       clean_abort "Patch format detection failed."
+                       clean_abort "$(gettext "Patch format detection failed.")"
                fi
                ;;
        esac
@@ -358,7 +362,7 @@ do
        --rebasing)
                rebasing=t threeway=t keep=t scissors=f no_inbody_headers=t ;;
        -d|--dotest)
-               die "-d option is no longer supported.  Do not use."
+               die "$(gettext "-d option is no longer supported.  Do not use.")"
                ;;
        --resolvemsg)
                shift; resolvemsg=$1 ;;
@@ -421,12 +425,12 @@ then
                false
                ;;
        esac ||
-       die "previous rebase directory $dotest still exists but mbox given."
+       die "$(eval_gettext "previous rebase directory \$dotest still exists but mbox given.")"
        resume=yes
 
        case "$skip,$abort" in
        t,t)
-               die "Please make up your mind. --skip or --abort?"
+               die "$(gettext "Please make up your mind. --skip or --abort?")"
                ;;
        t,)
                git rerere clear
@@ -453,7 +457,7 @@ then
 else
        # Make sure we are not given --skip, --resolved, nor --abort
        test "$skip$resolved$abort" = "" ||
-               die "Resolve operation not in progress, we are not resuming."
+               die "$(gettext "Resolve operation not in progress, we are not resuming.")"
 
        # Start afresh.
        mkdir -p "$dotest" || exit
@@ -518,7 +522,7 @@ case "$resolved" in
        if test "$files"
        then
                test -n "$HAS_HEAD" && : >"$dotest/dirtyindex"
-               die "Dirty index: cannot apply patches (dirty: $files)"
+               die "$(eval_gettext "Dirty index: cannot apply patches (dirty: \$files)")"
        fi
 esac
 
@@ -607,9 +611,9 @@ do
                        go_next && continue
 
                test -s "$dotest/patch" || {
-                       echo "Patch is empty.  Was it split wrong?"
-                       echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
-                       echo "To restore the original branch and stop patching run \"$cmdline --abort\"."
+                       eval_gettext "Patch is empty.  Was it split wrong?
+If you would prefer to skip this patch, instead run \"\$cmdline --skip\".
+To restore the original branch and stop patching run \"\$cmdline --abort\"."; echo
                        stop_here $this
                }
                rm -f "$dotest/original-commit" "$dotest/author-script"
@@ -644,7 +648,7 @@ do
 
        if test -z "$GIT_AUTHOR_EMAIL"
        then
-               echo "Patch does not have a valid e-mail address."
+               gettext "Patch does not have a valid e-mail address."; echo
                stop_here $this
        fi
 
@@ -691,15 +695,18 @@ do
        if test "$interactive" = t
        then
            test -t 0 ||
-           die "cannot be interactive without stdin connected to a terminal."
+           die "$(gettext "cannot be interactive without stdin connected to a terminal.")"
            action=again
            while test "$action" = again
            do
-               echo "Commit Body is:"
+               gettext "Commit Body is:"; echo
                echo "--------------------------"
                cat "$dotest/final-commit"
                echo "--------------------------"
-               printf "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+               # TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+               # in your translation. The program will only accept English
+               # input at this point.
+               gettext "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
                read reply
                case "$reply" in
                [yY]*) action=yes ;;
@@ -735,7 +742,7 @@ do
                stop_here $this
        fi
 
-       say "Applying: $FIRSTLINE"
+       say "$(eval_gettext "Applying: \$FIRSTLINE")"
 
        case "$resolved" in
        '')
@@ -756,16 +763,16 @@ do
                # working tree.
                resolved=
                git diff-index --quiet --cached HEAD -- && {
-                       echo "No changes - did you forget to use 'git add'?"
-                       echo "If there is nothing left to stage, chances are that something else"
-                       echo "already introduced the same changes; you might want to skip this patch."
+                       gettext "No changes - did you forget to use 'git add'?
+If there is nothing left to stage, chances are that something else
+already introduced the same changes; you might want to skip this patch."; echo
                        stop_here_user_resolve $this
                }
                unmerged=$(git ls-files -u)
                if test -n "$unmerged"
                then
-                       echo "You still have unmerged paths in your index"
-                       echo "did you forget to use 'git add'?"
+                       gettext "You still have unmerged paths in your index
+did you forget to use 'git add'?"; echo
                        stop_here_user_resolve $this
                fi
                apply_status=0
@@ -780,7 +787,7 @@ do
                    # Applying the patch to an earlier tree and merging the
                    # result may have produced the same tree as ours.
                    git diff-index --quiet --cached HEAD -- && {
-                       say No changes -- Patch already applied.
+                       say "$(gettext "No changes -- Patch already applied.")"
                        go_next
                        continue
                    }
@@ -790,7 +797,7 @@ do
        fi
        if test $apply_status != 0
        then
-               printf 'Patch failed at %s %s\n' "$msgnum" "$FIRSTLINE"
+               eval_gettext 'Patch failed at $msgnum $FIRSTLINE'; echo
                stop_here_user_resolve $this
        fi
 
@@ -806,7 +813,7 @@ do
                        GIT_AUTHOR_DATE=
                fi
                parent=$(git rev-parse --verify -q HEAD) ||
-               say >&2 "applying to an empty history"
+               say >&2 "$(gettext "applying to an empty history")"
 
                if test -n "$committer_date_is_author_date"
                then
index 415a8d04ccc4f313eb111a3bbfbf2af2382beebd..b2186a86279e8919214bb205400a05f32a316c3b 100755 (executable)
@@ -28,6 +28,7 @@ Please use "git help bisect" to get the full man page.'
 
 OPTIONS_SPEC=
 . git-sh-setup
+. git-sh-i18n
 require_work_tree
 
 _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
@@ -35,10 +36,16 @@ _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
 
 bisect_autostart() {
        test -s "$GIT_DIR/BISECT_START" || {
-               echo >&2 'You need to start by "git bisect start"'
+               (
+                       gettext "You need to start by \"git bisect start\"" &&
+                       echo
+               ) >&2
                if test -t 0
                then
-                       echo >&2 -n 'Do you want me to do it for you [Y/n]? '
+                       # TRANSLATORS: Make sure to include [Y] and [n] in your
+                       # translation. The program will only accept English input
+                       # at this point.
+           gettext "Do you want me to do it for you [Y/n]? " >&2
                        read yesno
                        case "$yesno" in
                        [Nn]*)
@@ -57,7 +64,7 @@ bisect_start() {
        #
        head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) ||
        head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) ||
-       die "Bad HEAD - I need a HEAD"
+       die "$(gettext "Bad HEAD - I need a HEAD")"
 
        #
        # Check if we are bisecting.
@@ -76,11 +83,11 @@ bisect_start() {
                        # cogito usage, and cogito users should understand
                        # it relates to cg-seek.
                        [ -s "$GIT_DIR/head-name" ] &&
-                               die "won't bisect on seeked tree"
+                               die "$(gettext "won't bisect on seeked tree")"
                        start_head="${head#refs/heads/}"
                        ;;
                *)
-                       die "Bad HEAD - strange symbolic ref"
+                       die "$(gettext "Bad HEAD - strange symbolic ref")"
                        ;;
                esac
        fi
@@ -110,7 +117,7 @@ bisect_start() {
            *)
                rev=$(git rev-parse -q --verify "$arg^{commit}") || {
                    test $has_double_dash -eq 1 &&
-                       die "'$arg' does not appear to be a valid revision"
+                       die "$(eval_gettext "'\$arg' does not appear to be a valid revision")"
                    break
                }
                case $bad_seen in
@@ -155,7 +162,7 @@ bisect_write() {
        case "$state" in
                bad)            tag="$state" ;;
                good|skip)      tag="$state"-"$rev" ;;
-               *)              die "Bad bisect_write argument: $state" ;;
+               *)              die "$(eval_gettext "Bad bisect_write argument: \$state")" ;;
        esac
        git update-ref "refs/bisect/$tag" "$rev" || exit
        echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
@@ -183,7 +190,7 @@ bisect_skip() {
        do
            case "$arg" in
             *..*)
-                revs=$(git rev-list "$arg") || die "Bad rev input: $arg" ;;
+               revs=$(git rev-list "$arg") || die "$(eval_gettext "Bad rev input: \$arg")" ;;
             *)
                 revs=$(git rev-parse --sq-quote "$arg") ;;
            esac
@@ -197,10 +204,10 @@ bisect_state() {
        state=$1
        case "$#,$state" in
        0,*)
-               die "Please call 'bisect_state' with at least one argument." ;;
+               die "$(gettext "Please call 'bisect_state' with at least one argument.")" ;;
        1,bad|1,good|1,skip)
                rev=$(git rev-parse --verify HEAD) ||
-                       die "Bad rev input: HEAD"
+                       die "$(gettext "Bad rev input: HEAD")"
                bisect_write "$state" "$rev"
                check_expected_revs "$rev" ;;
        2,bad|*,good|*,skip)
@@ -209,13 +216,13 @@ bisect_state() {
                for rev in "$@"
                do
                        sha=$(git rev-parse --verify "$rev^{commit}") ||
-                               die "Bad rev input: $rev"
+                               die "$(eval_gettext "Bad rev input: \$rev")"
                        eval="$eval bisect_write '$state' '$sha'; "
                done
                eval "$eval"
                check_expected_revs "$@" ;;
        *,bad)
-               die "'git bisect bad' can take only one argument." ;;
+               die "$(gettext "'git bisect bad' can take only one argument.")" ;;
        *)
                usage ;;
        esac
@@ -238,25 +245,38 @@ bisect_next_check() {
        t,,good)
                # have bad but not good.  we could bisect although
                # this is less optimum.
-               echo >&2 'Warning: bisecting only with a bad commit.'
+               (
+                       gettext "Warning: bisecting only with a bad commit." &&
+                       echo
+               ) >&2
                if test -t 0
                then
-                       printf >&2 'Are you sure [Y/n]? '
+                       # TRANSLATORS: Make sure to include [Y] and [n] in your
+                       # translation. The program will only accept English input
+                       # at this point.
+                       gettext "Are you sure [Y/n]? " >&2
                        read yesno
                        case "$yesno" in [Nn]*) exit 1 ;; esac
                fi
                : bisect without good...
                ;;
        *)
-               THEN=''
-               test -s "$GIT_DIR/BISECT_START" || {
-                       echo >&2 'You need to start by "git bisect start".'
-                       THEN='then '
-               }
-               echo >&2 'You '$THEN'need to give me at least one good' \
-                       'and one bad revisions.'
-               echo >&2 '(You can use "git bisect bad" and' \
-                       '"git bisect good" for that.)'
+
+               if test -s "$GIT_DIR/BISECT_START"
+               then
+                       (
+                               gettext "You need to give me at least one good and one bad revisions.
+(You can use \"git bisect bad\" and \"git bisect good\" for that.)" &&
+                               echo
+                       ) >&2
+               else
+                       (
+                               gettext "You need to start by \"git bisect start\".
+You then need to give me at least one good and one bad revisions.
+(You can use \"git bisect bad\" and \"git bisect good\" for that.)" &&
+                               echo
+                       ) >&2
+               fi
                exit 1 ;;
        esac
 }
@@ -307,13 +327,15 @@ bisect_visualize() {
 
 bisect_reset() {
        test -s "$GIT_DIR/BISECT_START" || {
-               echo "We are not bisecting."
+               gettext "We are not bisecting."; echo
                return
        }
        case "$#" in
        0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
-       1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null ||
-              die "'$1' is not a valid commit"
+       1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null || {
+              invalid="$1"
+              die "$(eval_gettext "'\$invalid' is not a valid commit")"
+          }
           branch="$1" ;;
        *)
            usage ;;
@@ -321,8 +343,8 @@ bisect_reset() {
        if git checkout "$branch" -- ; then
                bisect_clean_state
        else
-               die "Could not check out original HEAD '$branch'." \
-                               "Try 'git bisect reset <commit>'."
+               die "$(eval_gettext "Could not check out original HEAD '\$branch'.
+Try 'git bisect reset <commit>'.")"
        fi
 }
 
@@ -345,8 +367,9 @@ bisect_clean_state() {
 }
 
 bisect_replay () {
-       test "$#" -eq 1 || die "No logfile given"
-       test -r "$1" || die "cannot read $1 for replaying"
+       file="$1"
+       test "$#" -eq 1 || die "$(gettext "No logfile given")"
+       test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")"
        bisect_reset
        while read git bisect command rev
        do
@@ -362,9 +385,9 @@ bisect_replay () {
                good|bad|skip)
                        bisect_write "$command" "$rev" ;;
                *)
-                       die "?? what are you talking about?" ;;
+                       die "$(gettext "?? what are you talking about?")" ;;
                esac
-       done <"$1"
+       done <"$file"
        bisect_auto_next
 }
 
@@ -373,14 +396,18 @@ bisect_run () {
 
     while true
     do
-      echo "running $@"
+      command="$@"
+      eval_gettext "running \$command"; echo
       "$@"
       res=$?
 
       # Check for really bad run error.
       if [ $res -lt 0 -o $res -ge 128 ]; then
-         echo >&2 "bisect run failed:"
-         echo >&2 "exit code $res from '$@' is < 0 or >= 128"
+         (
+           eval_gettext "bisect run failed:
+exit code \$res from '\$command' is < 0 or >= 128" &&
+           echo
+         ) >&2
          exit $res
       fi
 
@@ -402,18 +429,24 @@ bisect_run () {
 
       if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
                > /dev/null; then
-         echo >&2 "bisect run cannot continue any more"
+         (
+             gettext "bisect run cannot continue any more" &&
+             echo
+         ) >&2
          exit $res
       fi
 
       if [ $res -ne 0 ]; then
-         echo >&2 "bisect run failed:"
-         echo >&2 "'bisect_state $state' exited with error code $res"
+         (
+             eval_gettext "bisect run failed:
+'bisect_state \$state' exited with error code \$res" &&
+             echo
+         ) >&2
          exit $res
       fi
 
       if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
-         echo "bisect run success"
+         gettext "bisect run success"; echo
          exit 0;
       fi
 
@@ -421,7 +454,7 @@ bisect_run () {
 }
 
 bisect_log () {
-       test -s "$GIT_DIR/BISECT_LOG" || die "We are not bisecting."
+       test -s "$GIT_DIR/BISECT_LOG" || die "$(gettext "We are not bisecting.")"
        cat "$GIT_DIR/BISECT_LOG"
 }
 
index e0bb81ed8d0bd89f18b31b1c03d3e23744aea5a1..a75530df7b7865059a51f84eb6257962c4f863cd 100644 (file)
@@ -215,6 +215,10 @@ extern char *gitbasename(char *);
 #define is_dir_sep(c) ((c) == '/')
 #endif
 
+#ifndef find_last_dir_sep
+#define find_last_dir_sep(path) strrchr(path, '/')
+#endif
+
 #if __HP_cc >= 61000
 #define NORETURN __attribute__((noreturn))
 #define NORETURN_PTR
index 8bfa8a055ccd0c344d595f112b571bb3d6e21c28..01a1b05e6bdcd12f82f70282975780d3a19d910d 100755 (executable)
@@ -27,6 +27,7 @@ httpd="$(git config --get instaweb.httpd)"
 root="$(git config --get instaweb.gitwebdir)"
 port=$(git config --get instaweb.port)
 module_path="$(git config --get instaweb.modulepath)"
+action="browse"
 
 conf="$GIT_DIR/gitweb/httpd.conf"
 
@@ -98,12 +99,18 @@ start_httpd () {
 
        # here $httpd should have a meaningful value
        resolve_full_httpd
+       mkdir -p "$fqgitdir/gitweb/$httpd_only"
+       conf="$fqgitdir/gitweb/$httpd_only.conf"
+
+       # generate correct config file if it doesn't exist
+       test -f "$conf" || configure_httpd
+       test -f "$fqgitdir/gitweb/gitweb_config.perl" || gitweb_conf
 
        # don't quote $full_httpd, there can be arguments to it (-f)
        case "$httpd" in
        *mongoose*|*plackup*)
                #These servers don't have a daemon mode so we'll have to fork it
-               $full_httpd "$fqgitdir/gitweb/httpd.conf" &
+               $full_httpd "$conf" &
                #Save the pid before doing anything else (we'll print it later)
                pid=$!
 
@@ -117,7 +124,7 @@ $pid
 EOF
                ;;
        *)
-               $full_httpd "$fqgitdir/gitweb/httpd.conf"
+               $full_httpd "$conf"
                if test $? != 0; then
                        echo "Could not execute http daemon $httpd."
                        exit 1
@@ -148,17 +155,13 @@ while test $# != 0
 do
        case "$1" in
        --stop|stop)
-               stop_httpd
-               exit 0
+               action="stop"
                ;;
        --start|start)
-               start_httpd
-               exit 0
+               action="start"
                ;;
        --restart|restart)
-               stop_httpd
-               start_httpd
-               exit 0
+               action="restart"
                ;;
        -l|--local)
                local=true
@@ -587,33 +590,54 @@ our \$projects_list = \$projectroot;
 EOF
 }
 
-gitweb_conf
-
-resolve_full_httpd
-mkdir -p "$fqgitdir/gitweb/$httpd_only"
+configure_httpd() {
+       case "$httpd" in
+       *lighttpd*)
+               lighttpd_conf
+               ;;
+       *apache2*|*httpd*)
+               apache2_conf
+               ;;
+       webrick)
+               webrick_conf
+               ;;
+       *mongoose*)
+               mongoose_conf
+               ;;
+       *plackup*)
+               plackup_conf
+               ;;
+       *)
+               echo "Unknown httpd specified: $httpd"
+               exit 1
+               ;;
+       esac
+}
 
-case "$httpd" in
-*lighttpd*)
-       lighttpd_conf
-       ;;
-*apache2*|*httpd*)
-       apache2_conf
-       ;;
-webrick)
-       webrick_conf
+case "$action" in
+stop)
+       stop_httpd
+       exit 0
        ;;
-*mongoose*)
-       mongoose_conf
+start)
+       start_httpd
+       exit 0
        ;;
-*plackup*)
-       plackup_conf
-       ;;
-*)
-       echo "Unknown httpd specified: $httpd"
-       exit 1
+restart)
+       stop_httpd
+       start_httpd
+       exit 0
        ;;
 esac
 
+gitweb_conf
+
+resolve_full_httpd
+mkdir -p "$fqgitdir/gitweb/$httpd_only"
+conf="$fqgitdir/gitweb/$httpd_only.conf"
+
+configure_httpd
+
 start_httpd
 url=http://127.0.0.1:$port
 
index 4db9212331259664732f031438b7b87b7a10244f..91f90acfba2a710644f4a076d27d50e0fbb33c2d 100644 (file)
@@ -86,6 +86,11 @@ get_merge_tool_cmd () {
 }
 
 run_merge_tool () {
+       # If GIT_PREFIX is empty then we cannot use it in tools
+       # that expect to be able to chdir() to its value.
+       GIT_PREFIX=${GIT_PREFIX:-.}
+       export GIT_PREFIX
+
        merge_tool_path="$(get_merge_tool_path "$1")" || exit
        base_present="$2"
        status=0
@@ -188,6 +193,7 @@ run_merge_tool () {
                        check_unchanged
                else
                        "$merge_tool_path" -R -f -d -c "wincmd l" \
+                               -c 'cd $GIT_PREFIX' \
                                "$LOCAL" "$REMOTE"
                fi
                ;;
@@ -199,6 +205,7 @@ run_merge_tool () {
                        check_unchanged
                else
                        "$merge_tool_path" -R -f -d -c "wincmd l" \
+                               -c 'cd $GIT_PREFIX' \
                                "$LOCAL" "$REMOTE"
                fi
                ;;
index fb9e2df9312e96ecf8ad5ab2bde4b74b979fe02e..a10b1290bc9218942fcec54b9e930f072ca11bce 100755 (executable)
@@ -9,6 +9,7 @@ LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEA
 SUBDIRECTORY_OK=Yes
 OPTIONS_SPEC=
 . git-sh-setup
+. git-sh-i18n
 set_reflog_action "pull $*"
 require_work_tree
 cd_to_toplevel
@@ -17,20 +18,20 @@ cd_to_toplevel
 die_conflict () {
     git diff-index --cached --name-status -r --ignore-submodules HEAD --
     if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
-       die "Pull is not possible because you have unmerged files.
+       die "$(gettext "Pull is not possible because you have unmerged files.
 Please, fix them up in the work tree, and then use 'git add/rm <file>'
-as appropriate to mark resolution, or use 'git commit -a'."
+as appropriate to mark resolution, or use 'git commit -a'.")"
     else
-       die "Pull is not possible because you have unmerged files."
+       die "$(gettext "Pull is not possible because you have unmerged files.")"
     fi
 }
 
 die_merge () {
     if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
-       die "You have not concluded your merge (MERGE_HEAD exists).
-Please, commit your changes before you can merge."
+       die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).
+Please, commit your changes before you can merge.")"
     else
-       die "You have not concluded your merge (MERGE_HEAD exists)."
+       die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).")"
     fi
 }
 
@@ -185,7 +186,7 @@ test true = "$rebase" && {
                # On an unborn branch
                if test -f "$GIT_DIR/index"
                then
-                       die "updating an unborn branch with changes added to the index"
+                       die "$(gettext "updating an unborn branch with changes added to the index")"
                fi
        else
                require_clean_work_tree "pull with rebase" "Please commit or stash them."
@@ -216,17 +217,20 @@ then
        # $orig_head commit, but we are merging into $curr_head.
        # First update the working tree to match $curr_head.
 
-       echo >&2 "Warning: fetch updated the current branch head."
-       echo >&2 "Warning: fast-forwarding your working tree from"
-       echo >&2 "Warning: commit $orig_head."
+       (
+               eval_gettext "Warning: fetch updated the current branch head.
+Warning: fast-forwarding your working tree from
+Warning: commit \$orig_head." &&
+               echo
+       ) >&2
        git update-index -q --refresh
        git read-tree -u -m "$orig_head" "$curr_head" ||
-               die 'Cannot fast-forward your working tree.
+               die "$(eval_gettext "Cannot fast-forward your working tree.
 After making sure that you saved anything precious from
-$ git diff '$orig_head'
+$ git diff \$orig_head
 output, run
 $ git reset --hard
-to recover.'
+to recover.")"
 
 fi
 
@@ -241,11 +245,11 @@ case "$merge_head" in
 ?*' '?*)
        if test -z "$orig_head"
        then
-               die "Cannot merge multiple branches into empty head"
+               die "$(gettext "Cannot merge multiple branches into empty head")"
        fi
        if test true = "$rebase"
        then
-               die "Cannot rebase onto multiple branches"
+               die "$(gettext "Cannot rebase onto multiple branches")"
        fi
        ;;
 esac
index 41ba96aeb7f8ee32f7c346d5d7934f4d6b305d2f..65690af89307abd72749123caddce9e6c4692383 100644 (file)
@@ -510,7 +510,9 @@ do_next () {
        refs/*)
                message="$GIT_REFLOG_ACTION: $head_name onto $shortonto" &&
                git update-ref -m "$message" $head_name $newhead $orig_head &&
-               git symbolic-ref HEAD $head_name
+               git symbolic-ref \
+                 -m "$GIT_REFLOG_ACTION: returning to $head_name" \
+                 HEAD $head_name
                ;;
        esac && {
                test ! -f "$state_dir"/verbose ||
index 7a54bfc6182a567860af023cb7720680d1806c72..d7855ea1c6dae8d8cbe35ae39f9c65e7bc9d7b11 100755 (executable)
@@ -153,7 +153,9 @@ move_to_original_branch () {
                message="rebase finished: $head_name onto $onto"
                git update-ref -m "$message" \
                        $head_name $(git rev-parse HEAD) $orig_head &&
-               git symbolic-ref HEAD $head_name ||
+               git symbolic-ref \
+                       -m "rebase finished: returning to $head_name" \
+                       HEAD $head_name ||
                die "Could not move back to $head_name"
                ;;
        esac
@@ -332,7 +334,7 @@ abort)
        read_basic_state
        case "$head_name" in
        refs/*)
-               git symbolic-ref HEAD $head_name ||
+               git symbolic-ref -m "rebase: aborting" HEAD $head_name ||
                die "Could not move back to $head_name"
                ;;
        esac
index aa16b8356507e4e669ee5c4cb4b5a667942d559e..8e427dab31c27bd78d9eac1ad15bc1f5dcc044c4 100644 (file)
@@ -39,9 +39,15 @@ git_broken_path_fix () {
 
 # @@BROKEN_PATH_FIX@@
 
-die() {
-       echo >&2 "$@"
-       exit 1
+die () {
+       die_with_status 1 "$@"
+}
+
+die_with_status () {
+       status=$1
+       shift
+       echo >&2 "$*"
+       exit "$status"
 }
 
 GIT_QUIET=
@@ -140,6 +146,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 0a9403653d7dbbb6927973dcfcc41bfd9a904e05..5619da5c10f968a7eaeeaafc497ed03e763c9439 100755 (executable)
@@ -14,6 +14,7 @@ SUBDIRECTORY_OK=Yes
 OPTIONS_SPEC=
 START_DIR=`pwd`
 . git-sh-setup
+. git-sh-i18n
 require_work_tree
 cd_to_toplevel
 
@@ -39,7 +40,7 @@ no_changes () {
 clear_stash () {
        if test $# != 0
        then
-               die "git stash clear with parameters is unimplemented"
+               die "$(gettext "git stash clear with parameters is unimplemented")"
        fi
        if current=$(git rev-parse --verify $ref_stash 2>/dev/null)
        then
@@ -61,7 +62,7 @@ create_stash () {
        then
                head=$(git rev-list --oneline -n 1 HEAD --)
        else
-               die "You do not have the initial commit yet"
+               die "$(gettext "You do not have the initial commit yet")"
        fi
 
        if branch=$(git symbolic-ref -q HEAD)
@@ -76,7 +77,7 @@ create_stash () {
        i_tree=$(git write-tree) &&
        i_commit=$(printf 'index on %s\n' "$msg" |
                git commit-tree $i_tree -p $b_commit) ||
-               die "Cannot save the current index state"
+               die "$(gettext "Cannot save the current index state")"
 
        if test -z "$patch_mode"
        then
@@ -90,7 +91,7 @@ create_stash () {
                        git write-tree &&
                        rm -f "$TMPindex"
                ) ) ||
-                       die "Cannot save the current worktree state"
+                       die "$(gettext "Cannot save the current worktree state")"
 
        else
 
@@ -103,14 +104,14 @@ create_stash () {
 
                # state of the working tree
                w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
-               die "Cannot save the current worktree state"
+               die "$(gettext "Cannot save the current worktree state")"
 
                git diff-tree -p HEAD $w_tree > "$TMP-patch" &&
                test -s "$TMP-patch" ||
-               die "No changes selected"
+               die "$(gettext "No changes selected")"
 
                rm -f "$TMP-index" ||
-               die "Cannot remove temporary index (can't happen)"
+               die "$(gettext "Cannot remove temporary index (can't happen)")"
 
        fi
 
@@ -123,7 +124,7 @@ create_stash () {
        fi
        w_commit=$(printf '%s\n' "$stash_msg" |
                git commit-tree $w_tree -p $b_commit -p $i_commit) ||
-               die "Cannot record working tree state"
+               die "$(gettext "Cannot record working tree state")"
 }
 
 save_stash () {
@@ -151,8 +152,19 @@ save_stash () {
                        break
                        ;;
                -*)
-                       echo "error: unknown option for 'stash save': $1"
-                       echo "       To provide a message, use git stash save -- '$1'"
+                       option="$1"
+                       # TRANSLATORS: $option is an invalid option, like
+                       # `--blah-blah'. The 7 spaces at the beginning of the
+                       # second line correspond to "error: ". So you should line
+                       # up the second line with however many characters the
+                       # translation of "error: " takes in your language. E.g. in
+                       # English this is:
+                       #
+                       #    $ git stash save --blah-blah 2>&1 | head -n 2
+                       #    error: unknown option for 'stash save': --blah-blah
+                       #           To provide a message, use git stash save -- '--blah-blah'
+                       eval_gettext "$("error: unknown option for 'stash save': \$option
+       To provide a message, use git stash save -- '\$option'")"; echo
                        usage
                        ;;
                *)
@@ -167,11 +179,11 @@ save_stash () {
        git update-index -q --refresh
        if no_changes
        then
-               say 'No local changes to save'
+               say "$(gettext "No local changes to save")"
                exit 0
        fi
        test -f "$GIT_DIR/logs/$ref_stash" ||
-               clear_stash || die "Cannot initialize stash"
+               clear_stash || die "$(gettext "Cannot initialize stash")"
 
        create_stash "$stash_msg"
 
@@ -179,7 +191,7 @@ save_stash () {
        : >>"$GIT_DIR/logs/$ref_stash"
 
        git update-ref -m "$stash_msg" $ref_stash $w_commit ||
-               die "Cannot save the current status"
+               die "$(gettext "Cannot save the current status")"
        say Saved working directory and index state "$stash_msg"
 
        if test -z "$patch_mode"
@@ -192,7 +204,7 @@ save_stash () {
                fi
        else
                git apply -R < "$TMP-patch" ||
-               die "Cannot remove worktree changes"
+               die "$(gettext "Cannot remove worktree changes")"
 
                if test "$keep_index" != "t"
                then
@@ -287,18 +299,21 @@ parse_flags_and_rev()
 
        case $# in
                0)
-                       have_stash || die "No stash found."
+                       have_stash || die "$(gettext "No stash found.")"
                        set -- ${ref_stash}@{0}
                ;;
                1)
                        :
                ;;
                *)
-                       die "Too many revisions specified: $REV"
+                       die "$(eval_gettext "Too many revisions specified: \$REV")"
                ;;
        esac
 
-       REV=$(git rev-parse --quiet --symbolic --verify $1 2>/dev/null) || die "$1 is not valid reference"
+       REV=$(git rev-parse --quiet --symbolic --verify $1 2>/dev/null) || {
+               reference="$1"
+               die "$(eval_gettext "\$reference is not valid reference")"
+       }
 
        i_commit=$(git rev-parse --quiet --verify $REV^2 2>/dev/null) &&
        set -- $(git rev-parse $REV $REV^1 $REV: $REV^1: $REV^2: 2>/dev/null) &&
@@ -320,7 +335,10 @@ is_stash_like()
 }
 
 assert_stash_like() {
-       is_stash_like "$@" || die "'$*' is not a stash-like commit"
+       is_stash_like "$@" || {
+               args="$*"
+               die "$(eval_gettext "'\$args' is not a stash-like commit")"
+       }
 }
 
 is_stash_ref() {
@@ -328,18 +346,21 @@ is_stash_ref() {
 }
 
 assert_stash_ref() {
-       is_stash_ref "$@" || die "'$*' is not a stash reference"
+       is_stash_ref "$@" || {
+               args="$*"
+               die "$(eval_gettext "'\$args' is not a stash reference")"
+       }
 }
 
 apply_stash () {
 
        assert_stash_like "$@"
 
-       git update-index -q --refresh || die 'unable to refresh index'
+       git update-index -q --refresh || die "$(gettext "unable to refresh index")"
 
        # current index state
        c_tree=$(git write-tree) ||
-               die 'Cannot apply a stash in the middle of a merge'
+               die "$(gettext "Cannot apply a stash in the middle of a merge")"
 
        unstashed_index_tree=
        if test -n "$INDEX_OPTION" && test "$b_tree" != "$i_tree" &&
@@ -347,9 +368,9 @@ apply_stash () {
        then
                git diff-tree --binary $s^2^..$s^2 | git apply --cached
                test $? -ne 0 &&
-                       die 'Conflicts in index. Try without --index.'
+                       die "$(gettext "Conflicts in index. Try without --index.")"
                unstashed_index_tree=$(git write-tree) ||
-                       die 'Could not save index tree'
+                       die "$(gettext "Could not save index tree")"
                git reset
        fi
 
@@ -375,7 +396,7 @@ apply_stash () {
                        git diff-index --cached --name-only --diff-filter=A $c_tree >"$a" &&
                        git read-tree --reset $c_tree &&
                        git update-index --add --stdin <"$a" ||
-                               die "Cannot unstage modified files"
+                               die "$(gettext "Cannot unstage modified files")"
                        rm -f "$a"
                fi
                squelch=
@@ -389,7 +410,10 @@ apply_stash () {
                status=$?
                if test -n "$INDEX_OPTION"
                then
-                       echo >&2 'Index was not unstashed.'
+                       (
+                               gettext "Index was not unstashed." &&
+                               echo
+                       ) >&2
                fi
                exit $status
        fi
@@ -406,14 +430,15 @@ drop_stash () {
        assert_stash_ref "$@"
 
        git reflog delete --updateref --rewrite "${REV}" &&
-               say "Dropped ${REV} ($s)" || die "${REV}: Could not drop stash entry"
+               say "$(eval_gettext "Dropped \${REV} (\$s)")" ||
+               die "$(eval_gettext "\${REV}: Could not drop stash entry")"
 
        # clear_stash if we just dropped the last stash entry
        git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash
 }
 
 apply_to_branch () {
-       test -n "$1" || die 'No branch name specified'
+       test -n "$1" || die "$(gettext "No branch name specified")"
        branch=$1
        shift 1
 
@@ -484,7 +509,7 @@ branch)
        case $# in
        0)
                save_stash &&
-               say '(To restore them type "git stash apply")'
+               say "$(gettext "(To restore them type \"git stash apply\")")"
                ;;
        *)
                usage
index bf110e9cb77a0e9930c427408e18d323db2c8916..cfd5aa60491a88534aeacb7fe70141aa391675a9 100755 (executable)
@@ -14,6 +14,7 @@ USAGE="[--quiet] add [-b branch] [-f|--force] [--reference <repository>] [--] <r
    or: $dashless [--quiet] sync [--] [<path>...]"
 OPTIONS_SPEC=
 . git-sh-setup
+. git-sh-i18n
 . git-parse-remote
 require_work_tree
 
@@ -34,7 +35,7 @@ resolve_relative_url ()
 {
        remote=$(get_default_remote)
        remoteurl=$(git config "remote.$remote.url") ||
-               die "remote ($remote) does not have a url defined in .git/config"
+               die "$(eval_gettext "remote (\$remote) does not have a url defined in .git/config")"
        url="$1"
        remoteurl=${remoteurl%/}
        sep=/
@@ -52,7 +53,7 @@ resolve_relative_url ()
                                sep=:
                                ;;
                        *)
-                               die "cannot strip one component off url '$remoteurl'"
+                               die "$(eval_gettext "cannot strip one component off url '\$remoteurl'")"
                                ;;
                        esac
                        ;;
@@ -104,7 +105,7 @@ module_name()
        name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
                sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
        test -z "$name" &&
-       die "No submodule mapping found in .gitmodules for path '$path'"
+       die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$path'")"
        echo "$name"
 }
 
@@ -128,7 +129,7 @@ module_clone()
        else
                git-clone -n "$url" "$path"
        fi ||
-       die "Clone of '$url' into submodule path '$path' failed"
+       die "$(eval_gettext "Clone of '\$url' into submodule path '\$path' failed")"
 }
 
 #
@@ -201,7 +202,7 @@ cmd_add()
                realrepo=$repo
                ;;
        *)
-               die "repo URL: '$repo' must be absolute or begin with ./|../"
+               die "$(eval_gettext "repo URL: '\$repo' must be absolute or begin with ./|../")"
        ;;
        esac
 
@@ -218,13 +219,16 @@ cmd_add()
                        s|/*$||
                ')
        git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
-       die "'$path' already exists in the index"
+       die "$(eval_gettext "'\$path' already exists in the index")"
 
        if test -z "$force" && ! git add --dry-run --ignore-missing "$path" > /dev/null 2>&1
        then
-               echo >&2 "The following path is ignored by one of your .gitignore files:" &&
-               echo >&2 $path &&
-               echo >&2 "Use -f if you really want to add it."
+               (
+                       eval_gettext "The following path is ignored by one of your .gitignore files:
+\$path
+Use -f if you really want to add it." &&
+                       echo
+               ) >&2
                exit 1
        fi
 
@@ -233,9 +237,9 @@ cmd_add()
        then
                if test -d "$path"/.git -o -f "$path"/.git
                then
-                       echo "Adding existing repo at '$path' to the index"
+                       eval_gettext "Adding existing repo at '\$path' to the index"; echo
                else
-                       die "'$path' already exists and is not a valid git repo"
+                       die "$(eval_gettext "'\$path' already exists and is not a valid git repo")"
                fi
 
                case "$repo" in
@@ -258,16 +262,16 @@ cmd_add()
                        '') git checkout -f -q ;;
                        ?*) git checkout -f -q -B "$branch" "origin/$branch" ;;
                        esac
-               ) || die "Unable to checkout submodule '$path'"
+               ) || die "$(eval_gettext "Unable to checkout submodule '\$path'")"
        fi
 
        git add $force "$path" ||
-       die "Failed to add submodule '$path'"
+       die "$(eval_gettext "Failed to add submodule '\$path'")"
 
        git config -f .gitmodules submodule."$path".path "$path" &&
        git config -f .gitmodules submodule."$path".url "$repo" &&
        git add --force .gitmodules ||
-       die "Failed to register submodule '$path'"
+       die "$(eval_gettext "Failed to register submodule '\$path'")"
 }
 
 #
@@ -305,7 +309,7 @@ cmd_foreach()
        do
                if test -e "$path"/.git
                then
-                       say "Entering '$prefix$path'"
+                       say "$(eval_gettext "Entering '\$prefix\$path'")"
                        name=$(module_name "$path")
                        (
                                prefix="$prefix$path/"
@@ -317,7 +321,7 @@ cmd_foreach()
                                        cmd_foreach "--recursive" "$@"
                                fi
                        ) ||
-                       die "Stopping at '$path'; script returned non-zero status."
+                       die "$(eval_gettext "Stopping at '\$path'; script returned non-zero status.")"
                fi
        done
 }
@@ -360,7 +364,7 @@ cmd_init()
 
                url=$(git config -f .gitmodules submodule."$name".url)
                test -z "$url" &&
-               die "No url found for submodule path '$path' in .gitmodules"
+               die "$(eval_gettext "No url found for submodule path '\$path' in .gitmodules")"
 
                # Possibly a url relative to parent
                case "$url" in
@@ -370,14 +374,14 @@ cmd_init()
                esac
 
                git config submodule."$name".url "$url" ||
-               die "Failed to register url for submodule path '$path'"
+               die "$(eval_gettext "Failed to register url for submodule path '\$path'")"
 
                upd="$(git config -f .gitmodules submodule."$name".update)"
                test -z "$upd" ||
                git config submodule."$name".update "$upd" ||
-               die "Failed to register update mode for submodule path '$path'"
+               die "$(eval_gettext "Failed to register update mode for submodule path '\$path'")"
 
-               say "Submodule '$name' ($url) registered for path '$path'"
+               say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$path'")"
        done
 }
 
@@ -444,7 +448,8 @@ cmd_update()
        fi
 
        cloned_modules=
-       module_list "$@" |
+       module_list "$@" | {
+       err=
        while read mode sha1 stage path
        do
                if test "$stage" = U
@@ -460,8 +465,8 @@ cmd_update()
                        # Only mention uninitialized submodules when its
                        # path have been specified
                        test "$#" != "0" &&
-                       say "Submodule path '$path' not initialized" &&
-                       say "Maybe you want to use 'update --init'?"
+                       say "$(eval_gettext "Submodule path '\$path' not initialized
+Maybe you want to use 'update --init'?")"
                        continue
                fi
 
@@ -473,7 +478,7 @@ cmd_update()
                else
                        subsha1=$(clear_local_git_env; cd "$path" &&
                                git rev-parse --verify HEAD) ||
-                       die "Unable to find current revision in submodule path '$path'"
+                       die "$(eval_gettext "Unable to find current revision in submodule path '\$path'")"
                fi
 
                if ! test -z "$update"
@@ -495,9 +500,9 @@ 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'"
+                               die "$(eval_gettext "Unable to fetch in submodule path '\$path'")"
                        fi
 
                        # Is this something we just cloned?
@@ -507,35 +512,72 @@ cmd_update()
                                update_module= ;;
                        esac
 
+                       must_die_on_failure=
                        case "$update_module" in
                        rebase)
                                command="git rebase"
-                               action="rebase"
-                               msg="rebased onto"
+                               die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$path'")"
+                               say_msg="$(eval_gettext "Submodule path '\$path': rebased into '\$sha1'")"
+                               must_die_on_failure=yes
                                ;;
                        merge)
                                command="git merge"
-                               action="merge"
-                               msg="merged in"
+                               die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$path'")"
+                               say_msg="$(eval_gettext "Submodule path '\$path': merged in '\$sha1'")"
+                               must_die_on_failure=yes
                                ;;
                        *)
                                command="git checkout $subforce -q"
-                               action="checkout"
-                               msg="checked out"
+                               die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$path'")"
+                               say_msg="$(eval_gettext "Submodule path '\$path': checked out '\$sha1'")"
                                ;;
                        esac
 
-                       (clear_local_git_env; cd "$path" && $command "$sha1") ||
-                       die "Unable to $action '$sha1' in submodule path '$path'"
-                       say "Submodule path '$path': $msg '$sha1'"
+                       if (clear_local_git_env; cd "$path" && $command "$sha1")
+                       then
+                               say "$say_msg"
+                       elif test -n "$must_die_on_failure"
+                       then
+                               die_with_status 2 "$die_msg"
+                       else
+                               err="${err};$die_msg"
+                               continue
+                       fi
                fi
 
                if test -n "$recursive"
                then
-                       (clear_local_git_env; cd "$path" && eval cmd_update "$orig_flags") ||
-                       die "Failed to recurse into submodule path '$path'"
+                       (clear_local_git_env; cd "$path" && eval cmd_update "$orig_flags")
+                       res=$?
+                       if test $res -gt 0
+                       then
+                               die_msg="$(eval_gettext "Failed to recurse into submodule path '\$path'")"
+                               if test $res -eq 1
+                               then
+                                       err="${err};$die_msg"
+                                       continue
+                               else
+                                       die_with_status $res "$die_msg"
+                               fi
+                       fi
                fi
        done
+
+       if test -n "$err"
+       then
+               OIFS=$IFS
+               IFS=';'
+               for e in $err
+               do
+                       if test -n "$e"
+                       then
+                               echo >&2 "$e"
+                       fi
+               done
+               IFS=$OIFS
+               exit 1
+       fi
+       }
 }
 
 set_name_rev () {
@@ -617,7 +659,7 @@ cmd_summary() {
        if [ -n "$files" ]
        then
                test -n "$cached" &&
-               die "--cached cannot be used with --files"
+               die "$(gettext -- "--cached cannot be used with --files")"
                diff_cmd=diff-files
                head=
        fi
@@ -657,7 +699,10 @@ cmd_summary() {
                                ;; # removed
                        *)
                                # unexpected type
-                               echo >&2 "unexpected mode $mod_dst"
+                               (
+                                       eval_gettext "unexpected mode \$mod_dst" &&
+                                       echo
+                               ) >&2
                                continue ;;
                        esac
                fi
@@ -675,13 +720,13 @@ cmd_summary() {
                total_commits=
                case "$missing_src,$missing_dst" in
                t,)
-                       errmsg="  Warn: $name doesn't contain commit $sha1_src"
+                       errmsg="$(eval_gettext "  Warn: \$name doesn't contain commit \$sha1_src")"
                        ;;
                ,t)
-                       errmsg="  Warn: $name doesn't contain commit $sha1_dst"
+                       errmsg="$(eval_gettext "  Warn: \$name doesn't contain commit \$sha1_dst")"
                        ;;
                t,t)
-                       errmsg="  Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
+                       errmsg="$(eval_gettext "  Warn: \$name doesn't contain commits \$sha1_src and \$sha1_dst")"
                        ;;
                *)
                        errmsg=
@@ -706,11 +751,13 @@ cmd_summary() {
                sha1_abbr_dst=$(echo $sha1_dst | cut -c1-7)
                if test $status = T
                then
+                       blob="$(gettext "blob")"
+                       submodule="$(gettext "submodule")"
                        if test $mod_dst = 160000
                        then
-                               echo "* $name $sha1_abbr_src(blob)->$sha1_abbr_dst(submodule)$total_commits:"
+                               echo "* $name $sha1_abbr_src($blob)->$sha1_abbr_dst($submodule)$total_commits:"
                        else
-                               echo "* $name $sha1_abbr_src(submodule)->$sha1_abbr_dst(blob)$total_commits:"
+                               echo "* $name $sha1_abbr_src($submodule)->$sha1_abbr_dst($blob)$total_commits:"
                        fi
                else
                        echo "* $name $sha1_abbr_src...$sha1_abbr_dst$total_commits:"
@@ -742,9 +789,9 @@ cmd_summary() {
        done |
        if test -n "$for_status"; then
                if [ -n "$files" ]; then
-                       echo "# Submodules changed but not updated:"
+                       gettext "# Submodules changed but not updated:"; echo
                else
-                       echo "# Submodule changes to be committed:"
+                       gettext "# Submodule changes to be committed:"; echo
                fi
                echo "#"
                sed -e 's|^|# |' -e 's|^# $|#|'
@@ -830,7 +877,7 @@ cmd_status()
                                cd "$path" &&
                                eval cmd_status "$orig_args"
                        ) ||
-                       die "Failed to recurse into submodule path '$path'"
+                       die "$(eval_gettext "Failed to recurse into submodule path '\$path'")"
                fi
        done
 }
@@ -874,7 +921,7 @@ cmd_sync()
                        ;;
                esac
 
-               say "Synchronizing submodule url for '$name'"
+               say "$(eval_gettext "Synchronizing submodule url for '\$name'")"
                git config submodule."$name".url "$url"
 
                if test -e "$path"/.git
index 7849cfc141d384bc28479c2f37fd128c77fe0fbe..89f83fd27abe315804173a809b3c7ef00ead6527 100755 (executable)
@@ -3124,8 +3124,12 @@ sub lookup_svn_merge {
                        next;
                }
 
-               push @merged_commit_ranges,
-                       "$bottom_commit^..$top_commit";
+               if (scalar(command('rev-parse', "$bottom_commit^@"))) {
+                       push @merged_commit_ranges,
+                            "$bottom_commit^..$top_commit";
+               } else {
+                       push @merged_commit_ranges, "$top_commit";
+               }
 
                if ( !defined $tip or $top > $tip ) {
                        $tip = $top;
@@ -3154,9 +3158,9 @@ sub check_cherry_pick {
        my $parents = shift;
        my @ranges = @_;
        my %commits = map { $_ => 1 }
-               _rev_list("--no-merges", $tip, "--not", $base, @$parents);
+               _rev_list("--no-merges", $tip, "--not", $base, @$parents, "--");
        for my $range ( @ranges ) {
-               delete @commits{_rev_list($range)};
+               delete @commits{_rev_list($range, "--")};
        }
        for my $commit (keys %commits) {
                if (has_no_changes($commit)) {
diff --git a/git.c b/git.c
index a5ef3c69ff58434e521d2c42f1b2fa275b872828..8828c18d6cce99b8becfbb510fcc9b2b752598ca 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)
@@ -185,8 +183,6 @@ static int handle_alias(int *argcp, const char ***argv)
                if (alias_string[0] == '!') {
                        const char **alias_argv;
                        int argc = *argcp, i;
-                       struct strbuf sb = STRBUF_INIT;
-                       const char *env[2];
 
                        commit_pager_choice();
 
@@ -197,13 +193,7 @@ static int handle_alias(int *argcp, const char ***argv)
                                alias_argv[i] = (*argv)[i];
                        alias_argv[argc] = NULL;
 
-                       strbuf_addstr(&sb, "GIT_PREFIX=");
-                       if (subdir)
-                               strbuf_addstr(&sb, subdir);
-                       env[0] = sb.buf;
-                       env[1] = NULL;
-                       ret = run_command_v_opt_cd_env(alias_argv, RUN_USING_SHELL, NULL, env);
-                       strbuf_release(&sb);
+                       ret = run_command_v_opt(alias_argv, RUN_USING_SHELL);
                        if (ret >= 0)   /* normal exit */
                                exit(ret);
 
index 4964a679b3013caa9bfa2f438dc110eaa0274b69..c5236fee9dced35ab35d02eb7d42109f0107a165 100644 (file)
@@ -25,11 +25,25 @@ The above example assumes that your web server is configured to run
 scripts).
 
 
+Requirements
+------------
+
+ - Core git tools
+ - Perl
+ - Perl modules: CGI, Encode, Fcntl, File::Find, File::Basename.
+ - web server
+
+The following optional Perl modules are required for extra features
+ - Digest::MD5 - for gravatar support
+ - CGI::Fast and FCGI - for running gitweb as FastCGI script
+ - HTML::TagCloud - for fancy tag cloud in project list view
+ - HTTP::Date or Time::ParseDate - to support If-Modified-Since for feeds
+
+
 Build time configuration
 ------------------------
 
-See also "How to configure gitweb for your local system" in README
-file for gitweb (in gitweb/README).
+See also "How to configure gitweb for your local system" section below.
 
 - There are many configuration variables which affect building of
   gitweb.cgi; see "default configuration for gitweb" section in main
@@ -73,6 +87,125 @@ file for gitweb (in gitweb/README).
   substitute gitweb.min.js and gitweb.min.css for all uses of gitweb.js and
   gitweb.css in the help files.
 
+
+How to configure gitweb for your local system
+---------------------------------------------
+
+You can specify the following configuration variables when building GIT:
+
+ * GIT_BINDIR
+   Points where to find the git executable.  You should set it up to
+   the place where the git binary was installed (usually /usr/bin) if you
+   don't install git from sources together with gitweb.  [Default: $(bindir)]
+ * GITWEB_SITENAME
+   Shown in the title of all generated pages, defaults to the server name
+   (SERVER_NAME CGI environment variable) if not set. [No default]
+ * GITWEB_PROJECTROOT
+   The root directory for all projects shown by gitweb. Must be set
+   correctly for gitweb to find repositories to display.  See also
+   "Gitweb repositories" in the INSTALL file for gitweb.  [Default: /pub/git]
+ * GITWEB_PROJECT_MAXDEPTH
+   The filesystem traversing limit for getting the project list; the number
+   is taken as depth relative to the projectroot.  It is used when
+   GITWEB_LIST is a directory (or is not set; then project root is used).
+   This is meant to speed up project listing on large work trees by limiting
+   search depth.  [Default: 2007]
+ * GITWEB_LIST
+   Points to a directory to scan for projects (defaults to project root
+   if not set / if empty) or to a file with explicit listing of projects
+   (together with projects' ownership). See "Generating projects list
+   using gitweb" in INSTALL file for gitweb to find out how to generate
+   such file from scan of a directory. [No default, which means use root
+   directory for projects]
+ * GITWEB_EXPORT_OK
+   Show repository only if this file exists (in repository).  Only
+   effective if this variable evaluates to true.  [No default / Not set]
+ * GITWEB_STRICT_EXPORT
+   Only allow viewing of repositories also shown on the overview page.
+   This for example makes GITWEB_EXPORT_OK to decide if repository is
+   available and not only if it is shown.  If GITWEB_LIST points to
+   file with list of project, only those repositories listed would be
+   available for gitweb.  [No default]
+ * GITWEB_HOMETEXT
+   Points to an .html file which is included on the gitweb project
+   overview page ('projects_list' view), if it exists.  Relative to
+   gitweb.cgi script.  [Default: indextext.html]
+ * GITWEB_SITE_HEADER
+   Filename of html text to include at top of each page.  Relative to
+   gitweb.cgi script.  [No default]
+ * GITWEB_SITE_FOOTER
+   Filename of html text to include at bottom of each page.  Relative to
+   gitweb.cgi script.  [No default]
+ * GITWEB_HOME_LINK_STR
+   String of the home link on top of all pages, leading to $home_link
+   (usually main gitweb page, which means projects list).  Used as first
+   part of gitweb view "breadcrumb trail": <home> / <project> / <view>.
+   [Default: projects]
+ * GITWEB_SITENAME
+   Name of your site or organization to appear in page titles.  Set it
+   to something descriptive for clearer bookmarks etc.  If not set
+   (if empty) gitweb uses "$SERVER_NAME Git", or "Untitled Git" if
+   SERVER_NAME CGI environment variable is not set (e.g. if running
+   gitweb as standalone script).  [No default]
+ * GITWEB_BASE_URL
+   Git base URLs used for URL to where fetch project from, i.e. full
+   URL is "$git_base_url/$project".  Shown on projects summary page.
+   Repository URL for project can be also configured per repository; this
+   takes precedence over URLs composed from base URL and a project name.
+   Note that you can setup multiple base URLs (for example one for
+   git:// protocol access, another for http:// access) from the gitweb
+   config file.  [No default]
+ * GITWEB_CSS
+   Points to the location where you put gitweb.css on your web server
+   (or to be more generic, the URI of gitweb stylesheet).  Relative to the
+   base URI of gitweb.  Note that you can setup multiple stylesheets from
+   the gitweb config file.  [Default: static/gitweb.css (or
+   static/gitweb.min.css if the CSSMIN variable is defined / CSS minifier
+   is used)]
+ * GITWEB_JS
+   Points to the location where you put gitweb.js on your web server
+   (or to be more generic URI of JavaScript code used by gitweb).
+   Relative to base URI of gitweb.  [Default: static/gitweb.js (or
+   static/gitweb.min.js if JSMIN build variable is defined / JavaScript
+   minifier is used)]
+ * CSSMIN, JSMIN
+   Invocation of a CSS minifier or a JavaScript minifier, respectively,
+   working as a filter (source on standard input, minified result on
+   standard output).  If set, it is used to generate a minified version of
+   'static/gitweb.css' or 'static/gitweb.js', respectively.  *Note* that
+   minified files would have *.min.css and *.min.js extension, which is
+   important if you also set GITWEB_CSS and/or GITWEB_JS.  [No default]
+ * GITWEB_LOGO
+   Points to the location where you put git-logo.png on your web server
+   (or to be more generic URI of logo, 72x27 size, displayed in top right
+   corner of each gitweb page, and used as logo for Atom feed).  Relative
+   to base URI of gitweb.  [Default: static/git-logo.png]
+ * GITWEB_FAVICON
+   Points to the location where you put git-favicon.png on your web server
+   (or to be more generic URI of favicon, assumed to be image/png type;
+   web browsers that support favicons (website icons) may display them
+   in the browser's URL bar and next to site name in bookmarks).  Relative
+   to base URI of gitweb.  [Default: static/git-favicon.png]
+ * GITWEB_CONFIG
+   This Perl file will be loaded using 'do' and can be used to override any
+   of the options above as well as some other options -- see the "Runtime
+   gitweb configuration" section below, and top of 'gitweb.cgi' for their
+   full list and description.  If the environment variable GITWEB_CONFIG
+   is set when gitweb.cgi is executed, then the file specified in the
+   environment variable will be loaded instead of the file specified
+   when gitweb.cgi was created.  [Default: gitweb_config.perl]
+ * GITWEB_CONFIG_SYSTEM
+   This Perl file will be loaded using 'do' as a fallback if GITWEB_CONFIG
+   does not exist.  If the environment variable GITWEB_CONFIG_SYSTEM is set
+   when gitweb.cgi is executed, then the file specified in the environment
+   variable will be loaded instead of the file specified when gitweb.cgi was
+   created.  [Default: /etc/gitweb.conf]
+ * HIGHLIGHT_BIN
+   Path to the highlight executable to use (must be the one from
+   http://www.andre-simon.de due to assumptions about parameters and output).
+   Useful if highlight is not installed on your webserver's PATH.
+   [Default: highlight]
+
 Build example
 ~~~~~~~~~~~~~
 
@@ -229,21 +362,6 @@ $projects_list variable in gitweb config):
        perl -- /var/www/cgi-bin/gitweb.cgi
 
 
-Requirements
-------------
-
- - Core git tools
- - Perl
- - Perl modules: CGI, Encode, Fcntl, File::Find, File::Basename.
- - web server
-
-The following optional Perl modules are required for extra features
- - Digest::MD5 - for gravatar support
- - CGI::Fast and FCGI - for running gitweb as FastCGI script
- - HTML::TagCloud - for fancy tag cloud in project list view
- - HTTP::Date or Time::ParseDate - to support If-Modified-Since for feeds
-
-
 Example web server configuration
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
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..a4cfcb42cd989c063c49b6511393ba936e93978a 100644 (file)
@@ -7,120 +7,6 @@ The one working on:
 From the git version 1.4.0 gitweb is bundled with git.
 
 
-How to configure gitweb for your local system
----------------------------------------------
-
-See also the "Build time configuration" section in the INSTALL
-file for gitweb (in gitweb/INSTALL).
-
-You can specify the following configuration variables when building GIT:
- * GIT_BINDIR
-   Points where to find the git executable.  You should set it up to
-   the place where the git binary was installed (usually /usr/bin) if you
-   don't install git from sources together with gitweb.  [Default: $(bindir)]
- * GITWEB_SITENAME
-   Shown in the title of all generated pages, defaults to the server name
-   (SERVER_NAME CGI environment variable) if not set. [No default]
- * GITWEB_PROJECTROOT
-   The root directory for all projects shown by gitweb. Must be set
-   correctly for gitweb to find repositories to display.  See also
-   "Gitweb repositories" in the INSTALL file for gitweb.  [Default: /pub/git]
- * GITWEB_PROJECT_MAXDEPTH
-   The filesystem traversing limit for getting the project list; the number
-   is taken as depth relative to the projectroot.  It is used when
-   GITWEB_LIST is a directory (or is not set; then project root is used).
-   This is meant to speed up project listing on large work trees by limiting
-   search depth.  [Default: 2007]
- * GITWEB_LIST
-   Points to a directory to scan for projects (defaults to project root
-   if not set / if empty) or to a file with explicit listing of projects
-   (together with projects' ownership). See "Generating projects list
-   using gitweb" in INSTALL file for gitweb to find out how to generate
-   such file from scan of a directory. [No default, which means use root
-   directory for projects]
- * GITWEB_EXPORT_OK
-   Show repository only if this file exists (in repository).  Only
-   effective if this variable evaluates to true.  [No default / Not set]
- * GITWEB_STRICT_EXPORT
-   Only allow viewing of repositories also shown on the overview page.
-   This for example makes GITWEB_EXPORT_OK to decide if repository is
-   available and not only if it is shown.  If GITWEB_LIST points to
-   file with list of project, only those repositories listed would be
-   available for gitweb.  [No default]
- * GITWEB_HOMETEXT
-   Points to an .html file which is included on the gitweb project
-   overview page ('projects_list' view), if it exists.  Relative to
-   gitweb.cgi script.  [Default: indextext.html]
- * GITWEB_SITE_HEADER
-   Filename of html text to include at top of each page.  Relative to
-   gitweb.cgi script.  [No default]
- * GITWEB_SITE_FOOTER
-   Filename of html text to include at bottom of each page.  Relative to
-   gitweb.cgi script.  [No default]
- * GITWEB_HOME_LINK_STR
-   String of the home link on top of all pages, leading to $home_link
-   (usually main gitweb page, which means projects list).  Used as first
-   part of gitweb view "breadcrumb trail": <home> / <project> / <view>.
-   [Default: projects]
- * GITWEB_SITENAME
-   Name of your site or organization to appear in page titles.  Set it
-   to something descriptive for clearer bookmarks etc.  If not set
-   (if empty) gitweb uses "$SERVER_NAME Git", or "Untitled Git" if
-   SERVER_NAME CGI environment variable is not set (e.g. if running
-   gitweb as standalone script).  [No default]
- * GITWEB_BASE_URL
-   Git base URLs used for URL to where fetch project from, i.e. full
-   URL is "$git_base_url/$project".  Shown on projects summary page.
-   Repository URL for project can be also configured per repository; this
-   takes precedence over URLs composed from base URL and a project name.
-   Note that you can setup multiple base URLs (for example one for
-   git:// protocol access, another for http:// access) from the gitweb
-   config file.  [No default]
- * GITWEB_CSS
-   Points to the location where you put gitweb.css on your web server
-   (or to be more generic, the URI of gitweb stylesheet).  Relative to the
-   base URI of gitweb.  Note that you can setup multiple stylesheets from
-   the gitweb config file.  [Default: static/gitweb.css (or
-   static/gitweb.min.css if the CSSMIN variable is defined / CSS minifier
-   is used)]
- * GITWEB_LOGO
-   Points to the location where you put git-logo.png on your web server
-   (or to be more generic URI of logo, 72x27 size, displayed in top right
-   corner of each gitweb page, and used as logo for Atom feed).  Relative
-   to base URI of gitweb.  [Default: static/git-logo.png]
- * GITWEB_FAVICON
-   Points to the location where you put git-favicon.png on your web server
-   (or to be more generic URI of favicon, assumed to be image/png type;
-   web browsers that support favicons (website icons) may display them
-   in the browser's URL bar and next to site name in bookmarks).  Relative
-   to base URI of gitweb.  [Default: static/git-favicon.png]
- * GITWEB_JS
-   Points to the location where you put gitweb.js on your web server
-   (or to be more generic URI of JavaScript code used by gitweb).
-   Relative to base URI of gitweb.  [Default: static/gitweb.js (or
-   static/gitweb.min.js if JSMIN build variable is defined / JavaScript
-   minifier is used)]
- * GITWEB_CONFIG
-   This Perl file will be loaded using 'do' and can be used to override any
-   of the options above as well as some other options -- see the "Runtime
-   gitweb configuration" section below, and top of 'gitweb.cgi' for their
-   full list and description.  If the environment variable GITWEB_CONFIG
-   is set when gitweb.cgi is executed, then the file specified in the
-   environment variable will be loaded instead of the file specified
-   when gitweb.cgi was created.  [Default: gitweb_config.perl]
- * GITWEB_CONFIG_SYSTEM
-   This Perl file will be loaded using 'do' as a fallback if GITWEB_CONFIG
-   does not exist.  If the environment variable GITWEB_CONFIG_SYSTEM is set
-   when gitweb.cgi is executed, then the file specified in the environment
-   variable will be loaded instead of the file specified when gitweb.cgi was
-   created.  [Default: /etc/gitweb.conf]
- * HIGHLIGHT_BIN
-   Path to the highlight executable to use (must be the one from
-   http://www.andre-simon.de due to assumptions about parameters and output).
-   Useful if highlight is not installed on your webserver's PATH.
-   [Default: highlight]
-
-
 Runtime gitweb configuration
 ----------------------------
 
@@ -207,6 +93,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 +209,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 dfac1bce848f5603ead20f8d587c7d5d3e182793..ecc9d3cca3b86131893e709ea4fc73266630dcaf 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";
@@ -320,6 +328,7 @@ sub evaluate_uri {
        # Enable grep search, which will list the files in currently selected
        # tree containing the given string. Enabled by default. This can be
        # potentially CPU-intensive, of course.
+       # Note that you need to have 'search' feature enabled too.
 
        # To enable system wide have in $GITWEB_CONFIG
        # $feature{'grep'}{'default'} = [1];
@@ -334,6 +343,7 @@ sub evaluate_uri {
        # Enable the pickaxe search, which will list the commits that modified
        # a given string in a file. This can be practical and quite faster
        # alternative to 'blame', but still potentially CPU-intensive.
+       # Note that you need to have 'search' feature enabled too.
 
        # To enable system wide have in $GITWEB_CONFIG
        # $feature{'pickaxe'}{'default'} = [1];
@@ -483,6 +493,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,
@@ -2574,20 +2596,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,
@@ -2610,7 +2646,7 @@ sub git_get_project_ctags {
                        close $ct;
 
                        (my $ctag = $tagfile) =~ s#.*/##;
-                       if ($val =~ /\d+/) {
+                       if ($val =~ /^\d+$/) {
                                $ctags->{$ctag} = $val;
                        } else {
                                $ctags->{$ctag} = 1;
@@ -3526,12 +3562,9 @@ sub mimetype_guess_file {
        open(my $mh, '<', $mimemap) or return undef;
        while (<$mh>) {
                next if m/^#/; # skip comments
-               my ($mimetype, $exts) = split(/\t+/);
-               if (defined $exts) {
-                       my @exts = split(/\s+/, $exts);
-                       foreach my $ext (@exts) {
-                               $mimemap{$ext} = $mimetype;
-                       }
+               my ($mimetype, @exts) = split(/\s+/);
+               foreach my $ext (@exts) {
+                       $mimemap{$ext} = $mimetype;
                }
        }
        close($mh);
@@ -3875,9 +3908,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!;
        }
 
@@ -4081,22 +4125,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>!;
        }
 
-       return $localtime;
+       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 $strtime;
 }
 
 # Outputs the author name and date in long form
@@ -4109,10 +4156,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,
@@ -4129,16 +4175,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";
        }
 }
@@ -4881,8 +4927,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;
@@ -4908,6 +4955,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;
        }
 
@@ -4935,6 +4988,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 {
@@ -4958,6 +5028,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) = @_;
@@ -5013,47 +5132,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) {
@@ -5573,7 +5672,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
@@ -6026,7 +6126,7 @@ sub git_blob_plain {
        # want to be sure not to break that by serving the image as an
        # attachment (though Firefox 3 doesn't seem to care).
        my $sandbox = $prevent_xss &&
-               $type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))$!;
+               $type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))(?:[ ;]|$)!;
 
        print $cgi->header(
                -type => $type,
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..db6eb50
--- /dev/null
@@ -0,0 +1,692 @@
+// 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 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();
+       }
+}
+
+// ----------------------------------------------------------------------
+
+/**#@+
+ * @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  = {};
+
+/**
+ * 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
+ * @param {Number} [xhr.pollTimer] ID of the timeout to clear
+ *
+ * @globals commits
+ */
+function handleError(xhr) {
+       errorInfo('Server error: ' +
+               xhr.status + ' - ' + (xhr.statusText || 'Error contacting server'));
+
+       if (typeof xhr.pollTimer === "number") {
+               clearTimeout(xhr.pollTimer);
+               delete xhr.pollTimer;
+       }
+       commits = {}; // free memory
+}
+
+/**
+ * Called after XMLHttpRequest finishes (loads)
+ *
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object
+ * @param {Number} [xhr.pollTimer] ID of the timeout to clear
+ *
+ * @globals commits
+ */
+function responseLoaded(xhr) {
+       if (typeof xhr.pollTimer === "number") {
+               clearTimeout(xhr.pollTimer);
+               delete xhr.pollTimer;
+       }
+
+       fixColorsAndGroups();
+       writeTimeInterval();
+       commits = {}; // free memory
+}
+
+/**
+ * handler for XMLHttpRequest onreadystatechange event
+ * @see startBlame
+ *
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object
+ * @param {Number} xhr.prevDataLength: previous value of xhr.responseText.length
+ * @param {Number} xhr.nextReadPos: start of unread part of xhr.responseText
+ * @param {Number} [xhr.pollTimer] ID of the timeout (to reset or cancel)
+ * @param {Boolean} fromTimer: if handler was called from timer
+ */
+function handleResponse(xhr, fromTimer) {
+
+       /*
+        * 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;
+       }
+
+
+       // extract new whole (complete) lines, and process them
+       if (xhr.prevDataLength !== xhr.responseText.length) {
+               xhr.prevDataLength = xhr.responseText.length;
+               var unprocessed = xhr.responseText.substring(xhr.nextReadPos);
+               xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos);
+       }
+
+       // did we finish work?
+       if (xhr.readyState === 4) {
+               responseLoaded(xhr);
+               return;
+       }
+
+       // if we get from timer, we have to restart it
+       // otherwise onreadystatechange gives us partial response, timer not needed
+       if (fromTimer) {
+               setTimeout(function () {
+                       handleResponse(xhr, true);
+               }, 1000);
+
+       } else if (typeof xhr.pollTimer === "number") {
+               clearTimeout(xhr.pollTimer);
+               delete xhr.pollTimer;
+       }
+}
+
+// ============================================================
+// ------------------------------------------------------------
+
+/**
+ * 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 t0, projectUrl, div_progress_bar, totalLines
+*/
+function startBlame(blamedataUrl, bUrl) {
+
+       var 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 = function () {
+               handleResponse(xhr, false);
+       };
+
+       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
+       xhr.pollTimer = setTimeout(function () {
+               handleResponse(xhr, true);
+       }, 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..04e9ba4ec46b9f2002135293ede1bc5570fa73dc 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) ||
@@ -631,7 +721,10 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
        int rest = eol - bol;
        char *line_color = NULL;
 
-       if (opt->pre_context || opt->post_context) {
+       if (opt->file_break && opt->last_shown == 0) {
+               if (opt->show_hunk_mark)
+                       opt->output(opt, "\n", 1);
+       } else if (opt->pre_context || opt->post_context) {
                if (opt->last_shown == 0) {
                        if (opt->show_hunk_mark) {
                                output_color(opt, "--", 2, opt->color_sep);
@@ -642,9 +735,13 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
                        opt->output(opt, "\n", 1);
                }
        }
+       if (opt->heading && opt->last_shown == 0) {
+               output_color(opt, name, strlen(name), opt->color_filename);
+               opt->output(opt, "\n", 1);
+       }
        opt->last_shown = lno;
 
-       if (opt->pathname) {
+       if (!opt->heading && opt->pathname) {
                output_color(opt, name, strlen(name), opt->color_filename);
                output_sep(opt, sign);
        }
@@ -791,10 +888,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)
@@ -854,9 +948,18 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
        if (!opt->output)
                opt->output = std_output;
 
-       if (opt->last_shown && (opt->pre_context || opt->post_context) &&
-           opt->output == std_output)
-               opt->show_hunk_mark = 1;
+       if (opt->pre_context || opt->post_context || opt->file_break) {
+               /* Show hunk marks, except for the first file. */
+               if (opt->last_shown)
+                       opt->show_hunk_mark = 1;
+               /*
+                * If we're using threads then we can't easily identify
+                * the first file.  Always put hunk marks in that case
+                * and skip the very first one later in work_done().
+                */
+               if (opt->output != std_output)
+                       opt->show_hunk_mark = 1;
+       }
        opt->last_shown = 0;
 
        switch (opt->binary) {
@@ -891,7 +994,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..c5682973eaf696099b0d0367e21df6c0a4624836 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;
@@ -101,6 +110,8 @@ struct grep_opt {
        unsigned post_context;
        unsigned last_shown;
        int show_hunk_mark;
+       int file_break;
+       int heading;
        void *priv;
 
        void (*output)(struct grep_opt *opt, const void *data, size_t size);
diff --git a/http.c b/http.c
index b2ae8de16db3abe2cad27249ae767f421aa6bb24..a1ea3db499eebc2deabf1a64e43b063cbe63ad2a 100644 (file)
--- a/http.c
+++ b/http.c
@@ -41,6 +41,7 @@ static long curl_low_speed_limit = -1;
 static long curl_low_speed_time = -1;
 static int curl_ftp_no_epsv;
 static const char *curl_http_proxy;
+static const char *curl_cookie_file;
 static char *user_name, *user_pass;
 static const char *user_agent;
 
@@ -191,6 +192,9 @@ static int http_options(const char *var, const char *value, void *cb)
        if (!strcmp("http.proxy", var))
                return git_config_string(&curl_http_proxy, var, value);
 
+       if (!strcmp("http.cookiefile", var))
+               return git_config_string(&curl_cookie_file, var, value);
+
        if (!strcmp("http.postbuffer", var)) {
                http_post_buffer = git_config_int(var, value);
                if (http_post_buffer < LARGE_PACKET_MAX)
@@ -531,6 +535,7 @@ struct active_request_slot *get_active_slot(void)
        slot->finished = NULL;
        slot->callback_data = NULL;
        slot->callback_func = NULL;
+       curl_easy_setopt(slot->curl, CURLOPT_COOKIEFILE, curl_cookie_file);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
        curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
        curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL);
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 2a1e3a94c910cd74e41303d8b7aefa539817c831..e9457019d5ac7aff1e185e195f37b851732f6210 100644 (file)
@@ -294,8 +294,9 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
        if (opt->total > 0) {
                static char buffer[64];
                snprintf(buffer, sizeof(buffer),
-                        "Subject: [%s %0*d/%d] ",
+                        "Subject: [%s%s%0*d/%d] ",
                         opt->subject_prefix,
+                        *opt->subject_prefix ? " " : "",
                         digits_in_number(opt->total),
                         opt->nr, opt->total);
                subject = buffer;
@@ -484,8 +485,10 @@ void show_log(struct rev_info *opt)
        ctx.date_mode = opt->date_mode;
        ctx.abbrev = opt->diffopt.abbrev;
        ctx.after_subject = extra_headers;
+       ctx.preserve_subject = opt->preserve_subject;
        ctx.reflog_info = opt->reflog_info;
-       pretty_print_commit(opt->commit_format, commit, &msgbuf, &ctx);
+       ctx.fmt = opt->commit_format;
+       pretty_print_commit(&ctx, commit, &msgbuf);
 
        if (opt->add_signoff)
                append_signoff(&msgbuf, opt->add_signoff);
index dff5c8d1831ca4019d1a502a393cb95f20ad18f7..f45eb54e4c99b8d67e4aa85f9a6218ea7a560592 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -318,16 +318,16 @@ static void add_rfc2047(struct strbuf *sb, const char *line, int len,
        strbuf_addstr(sb, "?=");
 }
 
-void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
-                 const char *line, enum date_mode dmode,
-                 const char *encoding)
+void pp_user_info(const struct pretty_print_context *pp,
+                 const char *what, struct strbuf *sb,
+                 const char *line, const char *encoding)
 {
        char *date;
        int namelen;
        unsigned long time;
        int tz;
 
-       if (fmt == CMIT_FMT_ONELINE)
+       if (pp->fmt == CMIT_FMT_ONELINE)
                return;
        date = strchr(line, '>');
        if (!date)
@@ -336,7 +336,7 @@ void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
        time = strtoul(date, &date, 10);
        tz = strtol(date, NULL, 10);
 
-       if (fmt == CMIT_FMT_EMAIL) {
+       if (pp->fmt == CMIT_FMT_EMAIL) {
                char *name_tail = strchr(line, '<');
                int display_name_length;
                int final_line;
@@ -366,18 +366,18 @@ void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
                strbuf_addch(sb, '\n');
        } else {
                strbuf_addf(sb, "%s: %.*s%.*s\n", what,
-                             (fmt == CMIT_FMT_FULLER) ? 4 : 0,
+                             (pp->fmt == CMIT_FMT_FULLER) ? 4 : 0,
                              "    ", namelen, line);
        }
-       switch (fmt) {
+       switch (pp->fmt) {
        case CMIT_FMT_MEDIUM:
-               strbuf_addf(sb, "Date:   %s\n", show_date(time, tz, dmode));
+               strbuf_addf(sb, "Date:   %s\n", show_date(time, tz, pp->date_mode));
                break;
        case CMIT_FMT_EMAIL:
                strbuf_addf(sb, "Date: %s\n", show_date(time, tz, DATE_RFC2822));
                break;
        case CMIT_FMT_FULLER:
-               strbuf_addf(sb, "%sDate: %s\n", what, show_date(time, tz, dmode));
+               strbuf_addf(sb, "%sDate: %s\n", what, show_date(time, tz, pp->date_mode));
                break;
        default:
                /* notin' */
@@ -408,12 +408,12 @@ static const char *skip_empty_lines(const char *msg)
        return msg;
 }
 
-static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,
-                       const struct commit *commit, int abbrev)
+static void add_merge_info(const struct pretty_print_context *pp,
+                          struct strbuf *sb, const struct commit *commit)
 {
        struct commit_list *parent = commit->parents;
 
-       if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) ||
+       if ((pp->fmt == CMIT_FMT_ONELINE) || (pp->fmt == CMIT_FMT_EMAIL) ||
            !parent || !parent->next)
                return;
 
@@ -422,8 +422,8 @@ static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,
        while (parent) {
                struct commit *p = parent->item;
                const char *hex = NULL;
-               if (abbrev)
-                       hex = find_unique_abbrev(p->object.sha1, abbrev);
+               if (pp->abbrev)
+                       hex = find_unique_abbrev(p->object.sha1, pp->abbrev);
                if (!hex)
                        hex = sha1_to_hex(p->object.sha1);
                parent = parent->next;
@@ -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);
 }
 
@@ -1116,9 +1116,7 @@ void format_commit_message(const struct commit *commit,
                free(context.message);
 }
 
-static void pp_header(enum cmit_fmt fmt,
-                     int abbrev,
-                     enum date_mode dmode,
+static void pp_header(const struct pretty_print_context *pp,
                      const char *encoding,
                      const struct commit *commit,
                      const char **msg_p,
@@ -1138,7 +1136,7 @@ static void pp_header(enum cmit_fmt fmt,
                        /* End of header */
                        return;
 
-               if (fmt == CMIT_FMT_RAW) {
+               if (pp->fmt == CMIT_FMT_RAW) {
                        strbuf_add(sb, line, linelen);
                        continue;
                }
@@ -1158,7 +1156,7 @@ static void pp_header(enum cmit_fmt fmt,
                                ;
                        /* with enough slop */
                        strbuf_grow(sb, num * 50 + 20);
-                       add_merge_info(fmt, sb, commit, abbrev);
+                       add_merge_info(pp, sb, commit);
                        parents_shown = 1;
                }
 
@@ -1169,32 +1167,31 @@ static void pp_header(enum cmit_fmt fmt,
                 */
                if (!memcmp(line, "author ", 7)) {
                        strbuf_grow(sb, linelen + 80);
-                       pp_user_info("Author", fmt, sb, line + 7, dmode, encoding);
+                       pp_user_info(pp, "Author", sb, line + 7, encoding);
                }
                if (!memcmp(line, "committer ", 10) &&
-                   (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
+                   (pp->fmt == CMIT_FMT_FULL || pp->fmt == CMIT_FMT_FULLER)) {
                        strbuf_grow(sb, linelen + 80);
-                       pp_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
+                       pp_user_info(pp, "Commit", sb, line + 10, encoding);
                }
        }
 }
 
-void pp_title_line(enum cmit_fmt fmt,
+void pp_title_line(const struct pretty_print_context *pp,
                   const char **msg_p,
                   struct strbuf *sb,
-                  const char *subject,
-                  const char *after_subject,
                   const char *encoding,
                   int need_8bit_cte)
 {
        struct strbuf title;
 
        strbuf_init(&title, 80);
-       *msg_p = format_subject(&title, *msg_p, " ");
+       *msg_p = format_subject(&title, *msg_p,
+                               pp->preserve_subject ? "\n" : " ");
 
        strbuf_grow(sb, title.len + 1024);
-       if (subject) {
-               strbuf_addstr(sb, subject);
+       if (pp->subject) {
+               strbuf_addstr(sb, pp->subject);
                add_rfc2047(sb, title.buf, title.len, encoding);
        } else {
                strbuf_addbuf(sb, &title);
@@ -1208,16 +1205,16 @@ void pp_title_line(enum cmit_fmt fmt,
                        "Content-Transfer-Encoding: 8bit\n";
                strbuf_addf(sb, header_fmt, encoding);
        }
-       if (after_subject) {
-               strbuf_addstr(sb, after_subject);
+       if (pp->after_subject) {
+               strbuf_addstr(sb, pp->after_subject);
        }
-       if (fmt == CMIT_FMT_EMAIL) {
+       if (pp->fmt == CMIT_FMT_EMAIL) {
                strbuf_addch(sb, '\n');
        }
        strbuf_release(&title);
 }
 
-void pp_remainder(enum cmit_fmt fmt,
+void pp_remainder(const struct pretty_print_context *pp,
                  const char **msg_p,
                  struct strbuf *sb,
                  int indent)
@@ -1234,7 +1231,7 @@ void pp_remainder(enum cmit_fmt fmt,
                if (is_empty_line(line, &linelen)) {
                        if (first)
                                continue;
-                       if (fmt == CMIT_FMT_SHORT)
+                       if (pp->fmt == CMIT_FMT_SHORT)
                                break;
                }
                first = 0;
@@ -1259,19 +1256,19 @@ char *reencode_commit_message(const struct commit *commit, const char **encoding
        return logmsg_reencode(commit, encoding);
 }
 
-void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
-                        struct strbuf *sb,
-                        const struct pretty_print_context *context)
+void pretty_print_commit(const struct pretty_print_context *pp,
+                        const struct commit *commit,
+                        struct strbuf *sb)
 {
        unsigned long beginning_of_body;
        int indent = 4;
        const char *msg = commit->buffer;
        char *reencoded;
        const char *encoding;
-       int need_8bit_cte = context->need_8bit_cte;
+       int need_8bit_cte = pp->need_8bit_cte;
 
-       if (fmt == CMIT_FMT_USERFORMAT) {
-               format_commit_message(commit, user_format, sb, context);
+       if (pp->fmt == CMIT_FMT_USERFORMAT) {
+               format_commit_message(commit, user_format, sb, pp);
                return;
        }
 
@@ -1280,14 +1277,14 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
                msg = reencoded;
        }
 
-       if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
+       if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL)
                indent = 0;
 
        /*
         * We need to check and emit Content-type: to mark it
         * as 8-bit if we haven't done so.
         */
-       if (fmt == CMIT_FMT_EMAIL && need_8bit_cte == 0) {
+       if (pp->fmt == CMIT_FMT_EMAIL && need_8bit_cte == 0) {
                int i, ch, in_body;
 
                for (in_body = i = 0; (ch = msg[i]); i++) {
@@ -1306,9 +1303,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
                }
        }
 
-       pp_header(fmt, context->abbrev, context->date_mode, encoding,
-                 commit, &msg, sb);
-       if (fmt != CMIT_FMT_ONELINE && !context->subject) {
+       pp_header(pp, encoding, commit, &msg, sb);
+       if (pp->fmt != CMIT_FMT_ONELINE && !pp->subject) {
                strbuf_addch(sb, '\n');
        }
 
@@ -1316,17 +1312,16 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
        msg = skip_empty_lines(msg);
 
        /* These formats treat the title line specially. */
-       if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
-               pp_title_line(fmt, &msg, sb, context->subject,
-                             context->after_subject, encoding, need_8bit_cte);
+       if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL)
+               pp_title_line(pp, &msg, sb, encoding, need_8bit_cte);
 
        beginning_of_body = sb->len;
-       if (fmt != CMIT_FMT_ONELINE)
-               pp_remainder(fmt, &msg, sb, indent);
+       if (pp->fmt != CMIT_FMT_ONELINE)
+               pp_remainder(pp, &msg, sb, indent);
        strbuf_rtrim(sb);
 
        /* Make sure there is an EOLN for the non-oneline case */
-       if (fmt != CMIT_FMT_ONELINE)
+       if (pp->fmt != CMIT_FMT_ONELINE)
                strbuf_addch(sb, '\n');
 
        /*
@@ -1334,12 +1329,20 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
         * format.  Make sure we did not strip the blank line
         * between the header and the body.
         */
-       if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
+       if (pp->fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
                strbuf_addch(sb, '\n');
 
-       if (context->show_notes)
+       if (pp->show_notes)
                format_display_notes(commit->object.sha1, sb, encoding,
                                     NOTES_SHOW_HEADER | NOTES_INDENT);
 
        free(reencoded);
 }
+
+void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
+                   struct strbuf *sb)
+{
+       struct pretty_print_context pp = {0};
+       pp.fmt = fmt;
+       pretty_print_commit(&pp, commit, sb);
+}
index 4ac9a037f478e769a69072c324a47876e298cae4..46a9e60708ad98db5299f9242db05de5915a0ebb 100644 (file)
@@ -726,11 +726,12 @@ static int verify_dotfile(const char *rest)
         * has already been discarded, we now test
         * the rest.
         */
-       switch (*rest) {
+
        /* "." is not allowed */
-       case '\0': case '/':
+       if (*rest == '\0' || is_dir_sep(*rest))
                return 0;
 
+       switch (*rest) {
        /*
         * ".git" followed by  NUL or slash is bad. This
         * shares the path end test with the ".." case.
@@ -743,7 +744,7 @@ static int verify_dotfile(const char *rest)
                rest += 2;
        /* fallthrough */
        case '.':
-               if (rest[1] == '\0' || rest[1] == '/')
+               if (rest[1] == '\0' || is_dir_sep(rest[1]))
                        return 0;
        }
        return 1;
@@ -753,23 +754,19 @@ int verify_path(const char *path)
 {
        char c;
 
+       if (has_dos_drive_prefix(path))
+               return 0;
+
        goto inside;
        for (;;) {
                if (!c)
                        return 1;
-               if (c == '/') {
+               if (is_dir_sep(c)) {
 inside:
                        c = *path++;
-                       switch (c) {
-                       default:
-                               continue;
-                       case '/': case '\0':
-                               break;
-                       case '.':
-                               if (verify_dotfile(path))
-                                       continue;
-                       }
-                       return 0;
+                       if ((c == '.' && !verify_dotfile(path)) ||
+                           is_dir_sep(c) || c == '\0')
+                               return 0;
                }
                c = *path++;
        }
diff --git a/refs.c b/refs.c
index e3c05110e58ca5684f9c43b1e1a5eb2587c96828..3a8789d3857d17a3a0a94ba2750e9f22857b8667 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -1451,7 +1451,7 @@ int write_ref_sha1(struct ref_lock *lock,
        }
        o = parse_object(sha1);
        if (!o) {
-               error("Trying to write ref %s with nonexistant object %s",
+               error("Trying to write ref %s with nonexistent object %s",
                        lock->ref_name, sha1_to_hex(sha1));
                unlock_ref(lock);
                return -1;
@@ -1826,6 +1826,12 @@ int update_ref(const char *action, const char *refname,
        return 0;
 }
 
+int ref_exists(char *refname)
+{
+       unsigned char sha1[20];
+       return !!resolve_ref(refname, sha1, 1, NULL);
+}
+
 struct ref *find_ref_by_name(const struct ref *list, const char *name)
 {
        for ( ; list; list = list->next)
diff --git a/refs.h b/refs.h
index 5e7a9a59f5f6b687d15eb37a11696433bdc8f15c..5de06e57e7a9644a7dd51832552e9d1afa53c8cd 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -54,6 +54,7 @@ extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refn
  */
 extern void add_extra_ref(const char *refname, const unsigned char *sha1, int flags);
 extern void clear_extra_refs(void);
+extern int ref_exists(char *);
 
 extern int peel_ref(const char *, unsigned char *);
 
index 17d8a9b377265aeed9765f04d505959e1f7fb9b0..b5be25ce9623936e42566ca52b9e4d1d33153111 100644 (file)
@@ -811,19 +811,21 @@ static void parse_push(struct strbuf *buf)
 
                strbuf_reset(buf);
                if (strbuf_getline(buf, stdin, '\n') == EOF)
-                       return;
+                       goto free_specs;
                if (!*buf->buf)
                        break;
        } while (1);
 
        if (push(nr_spec, specs))
                exit(128); /* error already reported */
-       for (i = 0; i < nr_spec; i++)
-               free(specs[i]);
-       free(specs);
 
        printf("\n");
        fflush(stdout);
+
+ free_specs:
+       for (i = 0; i < nr_spec; i++)
+               free(specs[i]);
+       free(specs);
 }
 
 int main(int argc, const char **argv)
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 7588a60b8e8ff55e81f29ff09bcde1750f8d0852..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);
@@ -1477,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 5e1f5c7e8f8071c6cab381306638067a1e20d71c..3d64adad18e2c889b7a7b7367f99c239f958dc70 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,
@@ -93,7 +94,8 @@ struct rev_info {
                        abbrev_commit_given:1,
                        use_terminator:1,
                        missing_newline:1,
-                       date_mode_explicit:1;
+                       date_mode_explicit:1,
+                       preserve_subject:1;
        unsigned int    disable_stdin:1;
 
        enum date_mode date_mode;
diff --git a/setup.c b/setup.c
index 013ad1127534367e64afeec3749e617ceccfb5d4..5ea5502e4881a806ebb3aa9fd38f146cf9f65cf9 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;
@@ -710,6 +710,11 @@ const char *setup_git_directory_gently(int *nongit_ok)
        const char *prefix;
 
        prefix = setup_git_directory_gently_1(nongit_ok);
+       if (prefix)
+               setenv("GIT_PREFIX", prefix, 1);
+       else
+               setenv("GIT_PREFIX", "", 1);
+
        if (startup_info) {
                startup_info->have_repository = !nongit_ok || !*nongit_ok;
                startup_info->prefix = prefix;
index 2eb0ee44b87a08750e3789ff9132d55a6abd96f9..5ddd6886c85356164e81c477d3a170343bc28049 100644 (file)
@@ -51,7 +51,6 @@
    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
 
 #include <errno.h>
-#include <getopt.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -74,6 +73,7 @@ main (int argc, char *argv[])
        {
        case 1:
          error ("we won't substitute all variables on stdin for you");
+         break;
          /*
          all_variables = 1;
       subst_from_stdin ();
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 064a33040812ba8782bf602c693abf08613d6ec7..bd6fb2f95dda17adcc56ae288af971a781cb7cdf 100644 (file)
@@ -1186,7 +1186,7 @@ static int open_sha1_file(const unsigned char *sha1)
        return -1;
 }
 
-static void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
+void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
 {
        void *map;
        int fd;
@@ -1205,20 +1205,29 @@ static void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
        return map;
 }
 
-static int legacy_loose_object(unsigned char *map)
+/*
+ * There used to be a second loose object header format which
+ * was meant to mimic the in-pack format, allowing for direct
+ * copy of the object data.  This format turned up not to be
+ * really worth it and we no longer write loose objects in that
+ * format.
+ */
+static int experimental_loose_object(unsigned char *map)
 {
        unsigned int word;
 
        /*
         * Is it a zlib-compressed buffer? If so, the first byte
         * must be 0x78 (15-bit window size, deflated), and the
-        * first 16-bit word is evenly divisible by 31
+        * first 16-bit word is evenly divisible by 31. If so,
+        * we are looking at the official format, not the experimental
+        * one.
         */
        word = (map[0] << 8) + map[1];
        if (map[0] == 0x78 && !(word % 31))
-               return 1;
-       else
                return 0;
+       else
+               return 1;
 }
 
 unsigned long unpack_object_header_buffer(const unsigned char *buf,
@@ -1245,7 +1254,7 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf,
        return used;
 }
 
-static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz)
+int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz)
 {
        unsigned long size, used;
        static const char valid_loose_object_type[8] = {
@@ -1262,34 +1271,29 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon
        stream->next_out = buffer;
        stream->avail_out = bufsiz;
 
-       if (legacy_loose_object(map)) {
-               git_inflate_init(stream);
-               return git_inflate(stream, 0);
-       }
-
+       if (experimental_loose_object(map)) {
+               /*
+                * The old experimental format we no longer produce;
+                * we can still read it.
+                */
+               used = unpack_object_header_buffer(map, mapsize, &type, &size);
+               if (!used || !valid_loose_object_type[type])
+                       return -1;
+               map += used;
+               mapsize -= used;
 
-       /*
-        * There used to be a second loose object header format which
-        * was meant to mimic the in-pack format, allowing for direct
-        * copy of the object data.  This format turned up not to be
-        * really worth it and we don't write it any longer.  But we
-        * can still read it.
-        */
-       used = unpack_object_header_buffer(map, mapsize, &type, &size);
-       if (!used || !valid_loose_object_type[type])
-               return -1;
-       map += used;
-       mapsize -= used;
+               /* Set up the stream for the rest.. */
+               stream->next_in = map;
+               stream->avail_in = mapsize;
+               git_inflate_init(stream);
 
-       /* Set up the stream for the rest.. */
-       stream->next_in = map;
-       stream->avail_in = mapsize;
+               /* And generate the fake traditional header */
+               stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu",
+                                                typename(type), size);
+               return 0;
+       }
        git_inflate_init(stream);
-
-       /* And generate the fake traditional header */
-       stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu",
-                                        typename(type), size);
-       return 0;
+       return git_inflate(stream, 0);
 }
 
 static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size, const unsigned char *sha1)
@@ -1342,7 +1346,7 @@ static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size
  * too permissive for what we want to check. So do an anal
  * object header parse by hand.
  */
-static int parse_sha1_header(const char *hdr, unsigned long *sizep)
+int parse_sha1_header(const char *hdr, unsigned long *sizep)
 {
        char type[10];
        int i;
@@ -1481,7 +1485,7 @@ static off_t get_delta_base(struct packed_git *p,
 
 /* forward declaration for a mutually recursive function */
 static int packed_object_info(struct packed_git *p, off_t offset,
-                             unsigned long *sizep);
+                             unsigned long *sizep, int *rtype);
 
 static int packed_delta_info(struct packed_git *p,
                             struct pack_window **w_curs,
@@ -1495,7 +1499,7 @@ static int packed_delta_info(struct packed_git *p,
        base_offset = get_delta_base(p, w_curs, &curpos, type, obj_offset);
        if (!base_offset)
                return OBJ_BAD;
-       type = packed_object_info(p, base_offset, NULL);
+       type = packed_object_info(p, base_offset, NULL, NULL);
        if (type <= OBJ_NONE) {
                struct revindex_entry *revidx;
                const unsigned char *base_sha1;
@@ -1523,10 +1527,10 @@ static int packed_delta_info(struct packed_git *p,
        return type;
 }
 
-static int unpack_object_header(struct packed_git *p,
-                               struct pack_window **w_curs,
-                               off_t *curpos,
-                               unsigned long *sizep)
+int unpack_object_header(struct packed_git *p,
+                        struct pack_window **w_curs,
+                        off_t *curpos,
+                        unsigned long *sizep)
 {
        unsigned char *base;
        unsigned int left;
@@ -1549,7 +1553,7 @@ static int unpack_object_header(struct packed_git *p,
        return type;
 }
 
-const char *packed_object_info_detail(struct packed_git *p,
+int packed_object_info_detail(struct packed_git *p,
                                      off_t obj_offset,
                                      unsigned long *size,
                                      unsigned long *store_size,
@@ -1580,7 +1584,7 @@ const char *packed_object_info_detail(struct packed_git *p,
                case OBJ_BLOB:
                case OBJ_TAG:
                        unuse_pack(&w_curs);
-                       return typename(type);
+                       return type;
                case OBJ_OFS_DELTA:
                        obj_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset);
                        if (!obj_offset)
@@ -1605,7 +1609,7 @@ const char *packed_object_info_detail(struct packed_git *p,
 }
 
 static int packed_object_info(struct packed_git *p, off_t obj_offset,
-                             unsigned long *sizep)
+                             unsigned long *sizep, int *rtype)
 {
        struct pack_window *w_curs = NULL;
        unsigned long size;
@@ -1613,6 +1617,8 @@ static int packed_object_info(struct packed_git *p, off_t obj_offset,
        enum object_type type;
 
        type = unpack_object_header(p, &w_curs, &curpos, &size);
+       if (rtype)
+               *rtype = type; /* representation type */
 
        switch (type) {
        case OBJ_OFS_DELTA:
@@ -1695,6 +1701,13 @@ static unsigned long pack_entry_hash(struct packed_git *p, off_t base_offset)
        return hash % MAX_DELTA_CACHE;
 }
 
+static int in_delta_base_cache(struct packed_git *p, off_t base_offset)
+{
+       unsigned long hash = pack_entry_hash(p, base_offset);
+       struct delta_base_cache_entry *ent = delta_base_cache + hash;
+       return (ent->data && ent->p == p && ent->base_offset == base_offset);
+}
+
 static void *cache_or_unpack_entry(struct packed_git *p, off_t base_offset,
        unsigned long *base_size, enum object_type *type, int keep_cache)
 {
@@ -2093,24 +2106,28 @@ static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *size
        return status;
 }
 
-int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
+/* returns enum object_type or negative */
+int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi)
 {
        struct cached_object *co;
        struct pack_entry e;
-       int status;
+       int status, rtype;
 
        co = find_cached_object(sha1);
        if (co) {
-               if (sizep)
-                       *sizep = co->size;
+               if (oi->sizep)
+                       *(oi->sizep) = co->size;
+               oi->whence = OI_CACHED;
                return co->type;
        }
 
        if (!find_pack_entry(sha1, &e)) {
                /* Most likely it's a loose object. */
-               status = sha1_loose_object_info(sha1, sizep);
-               if (status >= 0)
+               status = sha1_loose_object_info(sha1, oi->sizep);
+               if (status >= 0) {
+                       oi->whence = OI_LOOSE;
                        return status;
+               }
 
                /* Not a loose object; someone else may have just packed it. */
                reprepare_packed_git();
@@ -2118,15 +2135,31 @@ int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
                        return status;
        }
 
-       status = packed_object_info(e.p, e.offset, sizep);
+       status = packed_object_info(e.p, e.offset, oi->sizep, &rtype);
        if (status < 0) {
                mark_bad_packed_object(e.p, sha1);
-               status = sha1_object_info(sha1, sizep);
+               status = sha1_object_info_extended(sha1, oi);
+       } else if (in_delta_base_cache(e.p, e.offset)) {
+               oi->whence = OI_DBCACHED;
+       } else {
+               oi->whence = OI_PACKED;
+               oi->u.packed.offset = e.offset;
+               oi->u.packed.pack = e.p;
+               oi->u.packed.is_delta = (rtype == OBJ_REF_DELTA ||
+                                        rtype == OBJ_OFS_DELTA);
        }
 
        return status;
 }
 
+int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
+{
+       struct object_info oi;
+
+       oi.sizep = sizep;
+       return sha1_object_info_extended(sha1, &oi);
+}
+
 static void *read_packed_sha1(const unsigned char *sha1,
                              enum object_type *type, unsigned long *size)
 {
@@ -2704,7 +2737,7 @@ static int index_stream(unsigned char *sha1, int fd, size_t size,
        while (size) {
                char buf[10240];
                size_t sz = size < sizeof(buf) ? size : sizeof(buf);
-               size_t actual;
+               ssize_t actual;
 
                actual = read_in_full(fd, buf, sz);
                if (actual < 0)
diff --git a/streaming.c b/streaming.c
new file mode 100644 (file)
index 0000000..91414f4
--- /dev/null
@@ -0,0 +1,489 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#include "cache.h"
+#include "streaming.h"
+
+enum input_source {
+       stream_error = -1,
+       incore = 0,
+       loose = 1,
+       pack_non_delta = 2
+};
+
+typedef int (*open_istream_fn)(struct git_istream *,
+                              struct object_info *,
+                              const unsigned char *,
+                              enum object_type *);
+typedef int (*close_istream_fn)(struct git_istream *);
+typedef ssize_t (*read_istream_fn)(struct git_istream *, char *, size_t);
+
+struct stream_vtbl {
+       close_istream_fn close;
+       read_istream_fn read;
+};
+
+#define open_method_decl(name) \
+       int open_istream_ ##name \
+       (struct git_istream *st, struct object_info *oi, \
+        const unsigned char *sha1, \
+        enum object_type *type)
+
+#define close_method_decl(name) \
+       int close_istream_ ##name \
+       (struct git_istream *st)
+
+#define read_method_decl(name) \
+       ssize_t read_istream_ ##name \
+       (struct git_istream *st, char *buf, size_t sz)
+
+/* forward declaration */
+static open_method_decl(incore);
+static open_method_decl(loose);
+static open_method_decl(pack_non_delta);
+static struct git_istream *attach_stream_filter(struct git_istream *st,
+                                               struct stream_filter *filter);
+
+
+static open_istream_fn open_istream_tbl[] = {
+       open_istream_incore,
+       open_istream_loose,
+       open_istream_pack_non_delta,
+};
+
+#define FILTER_BUFFER (1024*16)
+
+struct filtered_istream {
+       struct git_istream *upstream;
+       struct stream_filter *filter;
+       char ibuf[FILTER_BUFFER];
+       char obuf[FILTER_BUFFER];
+       int i_end, i_ptr;
+       int o_end, o_ptr;
+       int input_finished;
+};
+
+struct git_istream {
+       const struct stream_vtbl *vtbl;
+       unsigned long size; /* inflated size of full object */
+       z_stream z;
+       enum { z_unused, z_used, z_done, z_error } z_state;
+
+       union {
+               struct {
+                       char *buf; /* from read_object() */
+                       unsigned long read_ptr;
+               } incore;
+
+               struct {
+                       void *mapped;
+                       unsigned long mapsize;
+                       char hdr[32];
+                       int hdr_avail;
+                       int hdr_used;
+               } loose;
+
+               struct {
+                       struct packed_git *pack;
+                       off_t pos;
+               } in_pack;
+
+               struct filtered_istream filtered;
+       } u;
+};
+
+int close_istream(struct git_istream *st)
+{
+       return st->vtbl->close(st);
+}
+
+ssize_t read_istream(struct git_istream *st, char *buf, size_t sz)
+{
+       return st->vtbl->read(st, buf, sz);
+}
+
+static enum input_source istream_source(const unsigned char *sha1,
+                                       enum object_type *type,
+                                       struct object_info *oi)
+{
+       unsigned long size;
+       int status;
+
+       oi->sizep = &size;
+       status = sha1_object_info_extended(sha1, oi);
+       if (status < 0)
+               return stream_error;
+       *type = status;
+
+       switch (oi->whence) {
+       case OI_LOOSE:
+               return loose;
+       case OI_PACKED:
+               if (!oi->u.packed.is_delta && big_file_threshold <= size)
+                       return pack_non_delta;
+               /* fallthru */
+       default:
+               return incore;
+       }
+}
+
+struct git_istream *open_istream(const unsigned char *sha1,
+                                enum object_type *type,
+                                unsigned long *size,
+                                struct stream_filter *filter)
+{
+       struct git_istream *st;
+       struct object_info oi;
+       const unsigned char *real = lookup_replace_object(sha1);
+       enum input_source src = istream_source(real, type, &oi);
+
+       if (src < 0)
+               return NULL;
+
+       st = xmalloc(sizeof(*st));
+       if (open_istream_tbl[src](st, &oi, real, type)) {
+               if (open_istream_incore(st, &oi, real, type)) {
+                       free(st);
+                       return NULL;
+               }
+       }
+       if (st && filter) {
+               /* Add "&& !is_null_stream_filter(filter)" for performance */
+               struct git_istream *nst = attach_stream_filter(st, filter);
+               if (!nst)
+                       close_istream(st);
+               st = nst;
+       }
+
+       *size = st->size;
+       return st;
+}
+
+
+/*****************************************************************
+ *
+ * Common helpers
+ *
+ *****************************************************************/
+
+static void close_deflated_stream(struct git_istream *st)
+{
+       if (st->z_state == z_used)
+               git_inflate_end(&st->z);
+}
+
+
+/*****************************************************************
+ *
+ * Filtered stream
+ *
+ *****************************************************************/
+
+static close_method_decl(filtered)
+{
+       free_stream_filter(st->u.filtered.filter);
+       return close_istream(st->u.filtered.upstream);
+}
+
+static read_method_decl(filtered)
+{
+       struct filtered_istream *fs = &(st->u.filtered);
+       size_t filled = 0;
+
+       while (sz) {
+               /* do we already have filtered output? */
+               if (fs->o_ptr < fs->o_end) {
+                       size_t to_move = fs->o_end - fs->o_ptr;
+                       if (sz < to_move)
+                               to_move = sz;
+                       memcpy(buf + filled, fs->obuf + fs->o_ptr, to_move);
+                       fs->o_ptr += to_move;
+                       sz -= to_move;
+                       filled += to_move;
+                       continue;
+               }
+               fs->o_end = fs->o_ptr = 0;
+
+               /* do we have anything to feed the filter with? */
+               if (fs->i_ptr < fs->i_end) {
+                       size_t to_feed = fs->i_end - fs->i_ptr;
+                       size_t to_receive = FILTER_BUFFER;
+                       if (stream_filter(fs->filter,
+                                         fs->ibuf + fs->i_ptr, &to_feed,
+                                         fs->obuf, &to_receive))
+                               return -1;
+                       fs->i_ptr = fs->i_end - to_feed;
+                       fs->o_end = FILTER_BUFFER - to_receive;
+                       continue;
+               }
+
+               /* tell the filter to drain upon no more input */
+               if (fs->input_finished) {
+                       size_t to_receive = FILTER_BUFFER;
+                       if (stream_filter(fs->filter,
+                                         NULL, NULL,
+                                         fs->obuf, &to_receive))
+                               return -1;
+                       fs->o_end = FILTER_BUFFER - to_receive;
+                       if (!fs->o_end)
+                               break;
+                       continue;
+               }
+               fs->i_end = fs->i_ptr = 0;
+
+               /* refill the input from the upstream */
+               if (!fs->input_finished) {
+                       fs->i_end = read_istream(fs->upstream, fs->ibuf, FILTER_BUFFER);
+                       if (fs->i_end < 0)
+                               break;
+                       if (fs->i_end)
+                               continue;
+               }
+               fs->input_finished = 1;
+       }
+       return filled;
+}
+
+static struct stream_vtbl filtered_vtbl = {
+       close_istream_filtered,
+       read_istream_filtered,
+};
+
+static struct git_istream *attach_stream_filter(struct git_istream *st,
+                                               struct stream_filter *filter)
+{
+       struct git_istream *ifs = xmalloc(sizeof(*ifs));
+       struct filtered_istream *fs = &(ifs->u.filtered);
+
+       ifs->vtbl = &filtered_vtbl;
+       fs->upstream = st;
+       fs->filter = filter;
+       fs->i_end = fs->i_ptr = 0;
+       fs->o_end = fs->o_ptr = 0;
+       fs->input_finished = 0;
+       ifs->size = -1; /* unknown */
+       return ifs;
+}
+
+/*****************************************************************
+ *
+ * Loose object stream
+ *
+ *****************************************************************/
+
+static read_method_decl(loose)
+{
+       size_t total_read = 0;
+
+       switch (st->z_state) {
+       case z_done:
+               return 0;
+       case z_error:
+               return -1;
+       default:
+               break;
+       }
+
+       if (st->u.loose.hdr_used < st->u.loose.hdr_avail) {
+               size_t to_copy = st->u.loose.hdr_avail - st->u.loose.hdr_used;
+               if (sz < to_copy)
+                       to_copy = sz;
+               memcpy(buf, st->u.loose.hdr + st->u.loose.hdr_used, to_copy);
+               st->u.loose.hdr_used += to_copy;
+               total_read += to_copy;
+       }
+
+       while (total_read < sz) {
+               int status;
+
+               st->z.next_out = (unsigned char *)buf + total_read;
+               st->z.avail_out = sz - total_read;
+               status = git_inflate(&st->z, Z_FINISH);
+
+               total_read = st->z.next_out - (unsigned char *)buf;
+
+               if (status == Z_STREAM_END) {
+                       git_inflate_end(&st->z);
+                       st->z_state = z_done;
+                       break;
+               }
+               if (status != Z_OK && status != Z_BUF_ERROR) {
+                       git_inflate_end(&st->z);
+                       st->z_state = z_error;
+                       return -1;
+               }
+       }
+       return total_read;
+}
+
+static close_method_decl(loose)
+{
+       close_deflated_stream(st);
+       munmap(st->u.loose.mapped, st->u.loose.mapsize);
+       return 0;
+}
+
+static struct stream_vtbl loose_vtbl = {
+       close_istream_loose,
+       read_istream_loose,
+};
+
+static open_method_decl(loose)
+{
+       st->u.loose.mapped = map_sha1_file(sha1, &st->u.loose.mapsize);
+       if (!st->u.loose.mapped)
+               return -1;
+       if (unpack_sha1_header(&st->z,
+                              st->u.loose.mapped,
+                              st->u.loose.mapsize,
+                              st->u.loose.hdr,
+                              sizeof(st->u.loose.hdr)) < 0) {
+               git_inflate_end(&st->z);
+               munmap(st->u.loose.mapped, st->u.loose.mapsize);
+               return -1;
+       }
+
+       parse_sha1_header(st->u.loose.hdr, &st->size);
+       st->u.loose.hdr_used = strlen(st->u.loose.hdr) + 1;
+       st->u.loose.hdr_avail = st->z.total_out;
+       st->z_state = z_used;
+
+       st->vtbl = &loose_vtbl;
+       return 0;
+}
+
+
+/*****************************************************************
+ *
+ * Non-delta packed object stream
+ *
+ *****************************************************************/
+
+static read_method_decl(pack_non_delta)
+{
+       size_t total_read = 0;
+
+       switch (st->z_state) {
+       case z_unused:
+               memset(&st->z, 0, sizeof(st->z));
+               git_inflate_init(&st->z);
+               st->z_state = z_used;
+               break;
+       case z_done:
+               return 0;
+       case z_error:
+               return -1;
+       case z_used:
+               break;
+       }
+
+       while (total_read < sz) {
+               int status;
+               struct pack_window *window = NULL;
+               unsigned char *mapped;
+
+               mapped = use_pack(st->u.in_pack.pack, &window,
+                                 st->u.in_pack.pos, &st->z.avail_in);
+
+               st->z.next_out = (unsigned char *)buf + total_read;
+               st->z.avail_out = sz - total_read;
+               st->z.next_in = mapped;
+               status = git_inflate(&st->z, Z_FINISH);
+
+               st->u.in_pack.pos += st->z.next_in - mapped;
+               total_read = st->z.next_out - (unsigned char *)buf;
+               unuse_pack(&window);
+
+               if (status == Z_STREAM_END) {
+                       git_inflate_end(&st->z);
+                       st->z_state = z_done;
+                       break;
+               }
+               if (status != Z_OK && status != Z_BUF_ERROR) {
+                       git_inflate_end(&st->z);
+                       st->z_state = z_error;
+                       return -1;
+               }
+       }
+       return total_read;
+}
+
+static close_method_decl(pack_non_delta)
+{
+       close_deflated_stream(st);
+       return 0;
+}
+
+static struct stream_vtbl pack_non_delta_vtbl = {
+       close_istream_pack_non_delta,
+       read_istream_pack_non_delta,
+};
+
+static open_method_decl(pack_non_delta)
+{
+       struct pack_window *window;
+       enum object_type in_pack_type;
+
+       st->u.in_pack.pack = oi->u.packed.pack;
+       st->u.in_pack.pos = oi->u.packed.offset;
+       window = NULL;
+
+       in_pack_type = unpack_object_header(st->u.in_pack.pack,
+                                           &window,
+                                           &st->u.in_pack.pos,
+                                           &st->size);
+       unuse_pack(&window);
+       switch (in_pack_type) {
+       default:
+               return -1; /* we do not do deltas for now */
+       case OBJ_COMMIT:
+       case OBJ_TREE:
+       case OBJ_BLOB:
+       case OBJ_TAG:
+               break;
+       }
+       st->z_state = z_unused;
+       st->vtbl = &pack_non_delta_vtbl;
+       return 0;
+}
+
+
+/*****************************************************************
+ *
+ * In-core stream
+ *
+ *****************************************************************/
+
+static close_method_decl(incore)
+{
+       free(st->u.incore.buf);
+       return 0;
+}
+
+static read_method_decl(incore)
+{
+       size_t read_size = sz;
+       size_t remainder = st->size - st->u.incore.read_ptr;
+
+       if (remainder <= read_size)
+               read_size = remainder;
+       if (read_size) {
+               memcpy(buf, st->u.incore.buf + st->u.incore.read_ptr, read_size);
+               st->u.incore.read_ptr += read_size;
+       }
+       return read_size;
+}
+
+static struct stream_vtbl incore_vtbl = {
+       close_istream_incore,
+       read_istream_incore,
+};
+
+static open_method_decl(incore)
+{
+       st->u.incore.buf = read_sha1_file_extended(sha1, type, &st->size, 0);
+       st->u.incore.read_ptr = 0;
+       st->vtbl = &incore_vtbl;
+
+       return st->u.incore.buf ? 0 : -1;
+}
diff --git a/streaming.h b/streaming.h
new file mode 100644 (file)
index 0000000..589e857
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#ifndef STREAMING_H
+#define STREAMING_H 1
+#include "cache.h"
+
+/* opaque */
+struct git_istream;
+
+extern struct git_istream *open_istream(const unsigned char *, enum object_type *, unsigned long *, struct stream_filter *);
+extern int close_istream(struct git_istream *);
+extern ssize_t read_istream(struct git_istream *, char *, size_t);
+
+#endif /* STREAMING_H */
index 47cbeb6e68ec910a691f2feca18b7b160efd0ced..9046ec98164f44811b67124e869353db4050ec06 100644 (file)
@@ -71,7 +71,7 @@ gitweb-test:
        $(MAKE) $(TGITWEB)
 
 valgrind:
-       GIT_TEST_OPTS=--valgrind $(MAKE)
+       $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind"
 
 # Smoke testing targets
 -include ../GIT-VERSION-FILE
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 143eb1f24092df070c5ddf75340bebb319a0c746..292753f77c4daf5f3cb55de1c28269b13455544f 100644 (file)
@@ -107,4 +107,9 @@ perl -MEncode -e '$e="";decode_utf8($e, Encode::FB_CROAK)' >/dev/null 2>&1 || {
        test_done
 }
 
+perl -MCGI -MCGI::Util -MCGI::Carp -e 0 >/dev/null 2>&1 || {
+       skip_all='skipping gitweb tests, CGI module unusable'
+       test_done
+}
+
 gitweb_init
diff --git a/t/lib-read-tree.sh b/t/lib-read-tree.sh
new file mode 100644 (file)
index 0000000..abc2c6f
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+#
+# Helper functions to check if read-tree would succeed/fail as expected with
+# and without the dry-run option. They also test that the dry-run does not
+# write the index and that together with -u it doesn't touch the work tree.
+#
+read_tree_must_succeed () {
+    git ls-files -s >pre-dry-run &&
+    git read-tree -n "$@" &&
+    git ls-files -s >post-dry-run &&
+    test_cmp pre-dry-run post-dry-run &&
+    git read-tree "$@"
+}
+
+read_tree_must_fail () {
+    git ls-files -s >pre-dry-run &&
+    test_must_fail git read-tree -n "$@" &&
+    git ls-files -s >post-dry-run &&
+    test_cmp pre-dry-run post-dry-run &&
+    test_must_fail git read-tree "$@"
+}
+
+read_tree_u_must_succeed () {
+    git ls-files -s >pre-dry-run &&
+    git diff-files -p >pre-dry-run-wt &&
+    git read-tree -n "$@" &&
+    git ls-files -s >post-dry-run &&
+    git diff-files -p >post-dry-run-wt &&
+    test_cmp pre-dry-run post-dry-run &&
+    test_cmp pre-dry-run-wt post-dry-run-wt &&
+    git read-tree "$@"
+}
+
+read_tree_u_must_fail () {
+    git ls-files -s >pre-dry-run &&
+    git diff-files -p >pre-dry-run-wt &&
+    test_must_fail git read-tree -n "$@" &&
+    git ls-files -s >post-dry-run &&
+    git diff-files -p >post-dry-run-wt &&
+    test_cmp pre-dry-run post-dry-run &&
+    test_cmp pre-dry-run-wt post-dry-run-wt &&
+    test_must_fail git read-tree "$@"
+}
index 9078b84ae68b430285312749f62daee103dcc428..f19e6510d04583866e39cbdea545a0d1323b7f76 100755 (executable)
@@ -66,31 +66,48 @@ test_expect_success expanded_in_repo '
                echo "\$Id:NoSpaceAtEitherEnd\$"
                echo "\$Id: NoTerminatingSymbol"
                echo "\$Id: Foreign Commit With Spaces \$"
-               echo "\$Id: NoTerminatingSymbolAtEOF"
-       } > expanded-keywords &&
+       } >expanded-keywords.0 &&
+
+       {
+               cat expanded-keywords.0 &&
+               printf "\$Id: NoTerminatingSymbolAtEOF"
+       } >expanded-keywords &&
+       cat expanded-keywords >expanded-keywords-crlf &&
+       git add expanded-keywords expanded-keywords-crlf &&
+       git commit -m "File with keywords expanded" &&
+       id=$(git rev-parse --verify :expanded-keywords) &&
 
        {
                echo "File with expanded keywords"
-               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
-               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
-               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
-               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
-               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
-               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
+               echo "\$Id: $id \$"
+               echo "\$Id: $id \$"
+               echo "\$Id: $id \$"
+               echo "\$Id: $id \$"
+               echo "\$Id: $id \$"
+               echo "\$Id: $id \$"
                echo "\$Id: NoTerminatingSymbol"
                echo "\$Id: Foreign Commit With Spaces \$"
-               echo "\$Id: NoTerminatingSymbolAtEOF"
-       } > expected-output &&
-
-       git add expanded-keywords &&
-       git commit -m "File with keywords expanded" &&
+       } >expected-output.0 &&
+       {
+               cat expected-output.0 &&
+               printf "\$Id: NoTerminatingSymbolAtEOF"
+       } >expected-output &&
+       {
+               append_cr <expected-output.0 &&
+               printf "\$Id: NoTerminatingSymbolAtEOF"
+       } >expected-output-crlf &&
+       {
+               echo "expanded-keywords ident"
+               echo "expanded-keywords-crlf ident text eol=crlf"
+       } >>.gitattributes &&
 
-       echo "expanded-keywords ident" >> .gitattributes &&
+       rm -f expanded-keywords expanded-keywords-crlf &&
 
-       rm -f expanded-keywords &&
        git checkout -- expanded-keywords &&
-       cat expanded-keywords &&
-       cmp expanded-keywords expected-output
+       test_cmp expanded-keywords expected-output &&
+
+       git checkout -- expanded-keywords-crlf &&
+       test_cmp expanded-keywords-crlf expected-output-crlf
 '
 
 # The use of %f in a filter definition is expanded to the path to
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 &&
index ca8a4098fa06afbdbe5c427983d3953550457bc2..babcdd23433348cd0e2397b2fb6968f7cfae4b0a 100755 (executable)
@@ -72,6 +72,7 @@ In addition:
 
 '
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
 . "$TEST_DIRECTORY"/lib-read-tree-m-3way.sh
 
 ################################################################
@@ -137,7 +138,7 @@ test_expect_success \
     '3-way merge with git read-tree -m, empty cache' \
     "rm -fr [NDMALTS][NDMALTSF] Z &&
      rm .git/index &&
-     git read-tree -m $tree_O $tree_A $tree_B &&
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
      check_result"
 
 # This starts out with the first head, which is the normal
@@ -146,9 +147,9 @@ test_expect_success \
     '3-way merge with git read-tree -m, match H' \
     "rm -fr [NDMALTS][NDMALTSF] Z &&
      rm .git/index &&
-     git read-tree $tree_A &&
+     read_tree_must_succeed $tree_A &&
      git checkout-index -f -u -a &&
-     git read-tree -m $tree_O $tree_A $tree_B &&
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
      check_result"
 
 : <<\END_OF_CASE_TABLE
@@ -211,7 +212,7 @@ test_expect_success '1 - must not have an entry not in A.' "
      rm -f .git/index XX &&
      echo XX >XX &&
      git update-index --add XX &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 test_expect_success \
@@ -219,7 +220,7 @@ test_expect_success \
     "rm -f .git/index NA &&
      cp .orig-B/NA NA &&
      git update-index --add NA &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B"
 
 test_expect_success \
     '2 - matching B alone is OK in !O && !A && B case.' \
@@ -227,14 +228,14 @@ test_expect_success \
      cp .orig-B/NA NA &&
      git update-index --add NA &&
      echo extra >>NA &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B"
 
 test_expect_success \
     '3 - must match A in !O && A && !B case.' \
     "rm -f .git/index AN &&
      cp .orig-A/AN AN &&
      git update-index --add AN &&
-     git read-tree -m $tree_O $tree_A $tree_B &&
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
@@ -243,7 +244,7 @@ test_expect_success \
      cp .orig-A/AN AN &&
      git update-index --add AN &&
      echo extra >>AN &&
-     git read-tree -m $tree_O $tree_A $tree_B"
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B"
 
 test_expect_success \
     '3 (fail) - must match A in !O && A && !B case.' "
@@ -251,7 +252,7 @@ test_expect_success \
      cp .orig-A/AN AN &&
      echo extra >>AN &&
      git update-index --add AN &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 test_expect_success \
@@ -259,7 +260,7 @@ test_expect_success \
     "rm -f .git/index AA &&
      cp .orig-A/AA AA &&
      git update-index --add AA &&
-     git read-tree -m $tree_O $tree_A $tree_B &&
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
@@ -268,7 +269,7 @@ test_expect_success \
      cp .orig-A/AA AA &&
      git update-index --add AA &&
      echo extra >>AA &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 test_expect_success \
@@ -277,7 +278,7 @@ test_expect_success \
      cp .orig-A/AA AA &&
      echo extra >>AA &&
      git update-index --add AA &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 test_expect_success \
@@ -285,7 +286,7 @@ test_expect_success \
     "rm -f .git/index LL &&
      cp .orig-A/LL LL &&
      git update-index --add LL &&
-     git read-tree -m $tree_O $tree_A $tree_B &&
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
@@ -294,7 +295,7 @@ test_expect_success \
      cp .orig-A/LL LL &&
      git update-index --add LL &&
      echo extra >>LL &&
-     git read-tree -m $tree_O $tree_A $tree_B &&
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
@@ -303,7 +304,7 @@ test_expect_success \
      cp .orig-A/LL LL &&
      echo extra >>LL &&
      git update-index --add LL &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 test_expect_success \
@@ -311,7 +312,7 @@ test_expect_success \
      rm -f .git/index DD &&
      echo DD >DD &&
      git update-index --add DD &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 test_expect_success \
@@ -319,7 +320,7 @@ test_expect_success \
      rm -f .git/index DM &&
      cp .orig-B/DM DM &&
      git update-index --add DM &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 test_expect_success \
@@ -327,7 +328,7 @@ test_expect_success \
      rm -f .git/index DN &&
      cp .orig-B/DN DN &&
      git update-index --add DN &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 test_expect_success \
@@ -335,7 +336,7 @@ test_expect_success \
     "rm -f .git/index MD &&
      cp .orig-A/MD MD &&
      git update-index --add MD &&
-     git read-tree -m $tree_O $tree_A $tree_B &&
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
@@ -344,7 +345,7 @@ test_expect_success \
      cp .orig-A/MD MD &&
      git update-index --add MD &&
      echo extra >>MD &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 test_expect_success \
@@ -353,7 +354,7 @@ test_expect_success \
      cp .orig-A/MD MD &&
      echo extra >>MD &&
      git update-index --add MD &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 test_expect_success \
@@ -361,7 +362,7 @@ test_expect_success \
     "rm -f .git/index ND &&
      cp .orig-A/ND ND &&
      git update-index --add ND &&
-     git read-tree -m $tree_O $tree_A $tree_B &&
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
@@ -370,7 +371,7 @@ test_expect_success \
      cp .orig-A/ND ND &&
      git update-index --add ND &&
      echo extra >>ND &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 test_expect_success \
@@ -379,7 +380,7 @@ test_expect_success \
      cp .orig-A/ND ND &&
      echo extra >>ND &&
      git update-index --add ND &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 test_expect_success \
@@ -387,7 +388,7 @@ test_expect_success \
     "rm -f .git/index MM &&
      cp .orig-A/MM MM &&
      git update-index --add MM &&
-     git read-tree -m $tree_O $tree_A $tree_B &&
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
@@ -396,7 +397,7 @@ test_expect_success \
      cp .orig-A/MM MM &&
      git update-index --add MM &&
      echo extra >>MM &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 test_expect_success \
@@ -405,7 +406,7 @@ test_expect_success \
      cp .orig-A/MM MM &&
      echo extra >>MM &&
      git update-index --add MM &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 test_expect_success \
@@ -413,7 +414,7 @@ test_expect_success \
     "rm -f .git/index SS &&
      cp .orig-A/SS SS &&
      git update-index --add SS &&
-     git read-tree -m $tree_O $tree_A $tree_B &&
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
@@ -422,7 +423,7 @@ test_expect_success \
      cp .orig-A/SS SS &&
      git update-index --add SS &&
      echo extra >>SS &&
-     git read-tree -m $tree_O $tree_A $tree_B &&
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
@@ -431,7 +432,7 @@ test_expect_success \
      cp .orig-A/SS SS &&
      echo extra >>SS &&
      git update-index --add SS &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 test_expect_success \
@@ -439,7 +440,7 @@ test_expect_success \
     "rm -f .git/index MN &&
      cp .orig-A/MN MN &&
      git update-index --add MN &&
-     git read-tree -m $tree_O $tree_A $tree_B &&
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
@@ -448,7 +449,7 @@ test_expect_success \
      cp .orig-A/MN MN &&
      git update-index --add MN &&
      echo extra >>MN &&
-     git read-tree -m $tree_O $tree_A $tree_B &&
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
@@ -456,7 +457,7 @@ test_expect_success \
     "rm -f .git/index NM &&
      cp .orig-A/NM NM &&
      git update-index --add NM &&
-     git read-tree -m $tree_O $tree_A $tree_B &&
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
@@ -465,7 +466,7 @@ test_expect_success \
      cp .orig-B/NM NM &&
      git update-index --add NM &&
      echo extra >>NM &&
-     git read-tree -m $tree_O $tree_A $tree_B &&
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
@@ -474,7 +475,7 @@ test_expect_success \
      cp .orig-A/NM NM &&
      git update-index --add NM &&
      echo extra >>NM &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 test_expect_success \
@@ -483,7 +484,7 @@ test_expect_success \
      cp .orig-A/NM NM &&
      echo extra >>NM &&
      git update-index --add NM &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 test_expect_success \
@@ -491,7 +492,7 @@ test_expect_success \
     "rm -f .git/index NN &&
      cp .orig-A/NN NN &&
      git update-index --add NN &&
-     git read-tree -m $tree_O $tree_A $tree_B &&
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
@@ -500,7 +501,7 @@ test_expect_success \
      cp .orig-A/NN NN &&
      git update-index --add NN &&
      echo extra >>NN &&
-     git read-tree -m $tree_O $tree_A $tree_B &&
+     read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
@@ -509,7 +510,7 @@ test_expect_success \
      cp .orig-A/NN NN &&
      echo extra >>NN &&
      git update-index --add NN &&
-     test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+     read_tree_must_fail -m $tree_O $tree_A $tree_B
 "
 
 # #16
@@ -522,7 +523,7 @@ test_expect_success \
     echo E16 >F16 &&
     git update-index F16 &&
     tree1=`git write-tree` &&
-    git read-tree -m $tree0 $tree1 $tree1 $tree0 &&
+    read_tree_must_succeed -m $tree0 $tree1 $tree1 $tree0 &&
     git ls-files --stage'
 
 test_done
index 680d992f229cda9f1904240f593d66dc1cf3e32d..acaab07fac672e4b1b74161b58baf1f5e153840e 100755 (executable)
@@ -21,6 +21,7 @@ In the test, these paths are used:
         yomin   - not in H nor M
 '
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
 
 read_tree_twoway () {
     git read-tree -m "$1" "$2" && git ls-files --stage
@@ -94,7 +95,7 @@ echo '+100644 X 0     yomin' >expected
 test_expect_success \
     '4 - carry forward local addition.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      git update-index --add yomin &&
      read_tree_twoway $treeH $treeM &&
@@ -106,7 +107,7 @@ test_expect_success \
 test_expect_success \
     '5 - carry forward local addition.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      echo yomin >yomin &&
      git update-index --add yomin &&
@@ -120,7 +121,7 @@ test_expect_success \
 test_expect_success \
     '6 - local addition already has the same.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      git update-index --add frotz &&
      read_tree_twoway $treeH $treeM &&
@@ -131,7 +132,7 @@ test_expect_success \
 test_expect_success \
     '7 - local addition already has the same.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      echo frotz >frotz &&
      git update-index --add frotz &&
@@ -144,7 +145,7 @@ test_expect_success \
 test_expect_success \
     '8 - conflicting addition.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      echo frotz frotz >frotz &&
      git update-index --add frotz &&
@@ -153,7 +154,7 @@ test_expect_success \
 test_expect_success \
     '9 - conflicting addition.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      echo frotz frotz >frotz &&
      git update-index --add frotz &&
@@ -163,7 +164,7 @@ test_expect_success \
 test_expect_success \
     '10 - path removed.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      echo rezrov >rezrov &&
      git update-index --add rezrov &&
@@ -174,7 +175,7 @@ test_expect_success \
 test_expect_success \
     '11 - dirty path removed.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      echo rezrov >rezrov &&
      git update-index --add rezrov &&
@@ -184,7 +185,7 @@ test_expect_success \
 test_expect_success \
     '12 - unmatching local changes being removed.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      echo rezrov rezrov >rezrov &&
      git update-index --add rezrov &&
@@ -193,7 +194,7 @@ test_expect_success \
 test_expect_success \
     '13 - unmatching local changes being removed.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      echo rezrov rezrov >rezrov &&
      git update-index --add rezrov &&
@@ -208,7 +209,7 @@ EOF
 test_expect_success \
     '14 - unchanged in two heads.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      echo nitfol nitfol >nitfol &&
      git update-index --add nitfol &&
@@ -221,7 +222,7 @@ test_expect_success \
 test_expect_success \
     '15 - unchanged in two heads.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      echo nitfol nitfol >nitfol &&
      git update-index --add nitfol &&
@@ -235,7 +236,7 @@ test_expect_success \
 test_expect_success \
     '16 - conflicting local change.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      echo bozbar bozbar >bozbar &&
      git update-index --add bozbar &&
@@ -244,7 +245,7 @@ test_expect_success \
 test_expect_success \
     '17 - conflicting local change.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      echo bozbar bozbar >bozbar &&
      git update-index --add bozbar &&
@@ -254,7 +255,7 @@ test_expect_success \
 test_expect_success \
     '18 - local change already having a good result.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      cat bozbar-new >bozbar &&
      git update-index --add bozbar &&
@@ -266,7 +267,7 @@ test_expect_success \
 test_expect_success \
     '19 - local change already having a good result, further modified.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      cat bozbar-new >bozbar &&
      git update-index --add bozbar &&
@@ -279,7 +280,7 @@ test_expect_success \
 test_expect_success \
     '20 - no local change, use new tree.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      cat bozbar-old >bozbar &&
      git update-index --add bozbar &&
@@ -291,7 +292,7 @@ test_expect_success \
 test_expect_success \
     '21 - no local change, dirty cache.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      cat bozbar-old >bozbar &&
      git update-index --add bozbar &&
@@ -302,7 +303,7 @@ test_expect_success \
 test_expect_success \
     '22 - local change cache updated.' \
     'rm -f .git/index &&
-     git read-tree $treeH &&
+     read_tree_must_succeed $treeH &&
      git checkout-index -u -f -q -a &&
      sed -e "s/such as/SUCH AS/" bozbar-old >bozbar &&
      git update-index --add bozbar &&
@@ -359,7 +360,7 @@ test_expect_success \
 
 test_expect_success \
     'a/b (untracked) vs a, plus c/d case test.' \
-    'test_must_fail git read-tree -u -m "$treeH" "$treeM" &&
+    'read_tree_u_must_fail -u -m "$treeH" "$treeM" &&
      git ls-files --stage &&
      test -f a/b'
 
@@ -386,7 +387,7 @@ test_expect_success \
 
 test_expect_success \
     'a/b vs a, plus c/d case test.' \
-    'git read-tree -u -m "$treeH" "$treeM" &&
+    'read_tree_u_must_succeed -u -m "$treeH" "$treeM" &&
      git ls-files --stage | tee >treeMcheck.out &&
      test_cmp treeM.out treeMcheck.out'
 
@@ -401,7 +402,7 @@ test_expect_success '-m references the correct modified tree' '
        echo a >file-a &&
        git add file-a &&
        git ls-tree $(git write-tree) file-a >expect &&
-       git read-tree -m HEAD initial-mod &&
+       read_tree_must_succeed -m HEAD initial-mod &&
        git ls-tree $(git write-tree) file-a >actual &&
        test_cmp expect actual
 '
index a4a17e001739a45fc7d53e76bd8bbb14426492f9..a847709a1354a824c90a13d2c9e9ab6d6bc69b6c 100755 (executable)
@@ -9,6 +9,7 @@ This is identical to t1001, but uses -u to update the work tree as well.
 
 '
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
 
 compare_change () {
        sed >current \
@@ -56,8 +57,8 @@ test_expect_success \
 test_expect_success \
     '1, 2, 3 - no carry forward' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
-     git read-tree -m -u $treeH $treeM &&
+     read_tree_u_must_succeed --reset -u $treeH &&
+     read_tree_u_must_succeed -m -u $treeH $treeM &&
      git ls-files --stage >1-3.out &&
      cmp M.out 1-3.out &&
      sum bozbar frotz nitfol >actual3.sum &&
@@ -69,11 +70,11 @@ test_expect_success \
 test_expect_success \
     '4 - carry forward local addition.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
      echo "+100644 X 0 yomin" >expected &&
      echo yomin >yomin &&
      git update-index --add yomin &&
-     git read-tree -m -u $treeH $treeM &&
+     read_tree_u_must_succeed -m -u $treeH $treeM &&
      git ls-files --stage >4.out || return 1
      git diff -U0 --no-index M.out 4.out >4diff.out
      compare_change 4diff.out expected &&
@@ -87,12 +88,12 @@ test_expect_success \
 test_expect_success \
     '5 - carry forward local addition.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
-     git read-tree -m -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
+     read_tree_u_must_succeed -m -u $treeH &&
      echo yomin >yomin &&
      git update-index --add yomin &&
      echo yomin yomin >yomin &&
-     git read-tree -m -u $treeH $treeM &&
+     read_tree_u_must_succeed -m -u $treeH $treeM &&
      git ls-files --stage >5.out || return 1
      git diff -U0 --no-index M.out 5.out >5diff.out
      compare_change 5diff.out expected &&
@@ -107,10 +108,10 @@ test_expect_success \
 test_expect_success \
     '6 - local addition already has the same.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
      echo frotz >frotz &&
      git update-index --add frotz &&
-     git read-tree -m -u $treeH $treeM &&
+     read_tree_u_must_succeed -m -u $treeH $treeM &&
      git ls-files --stage >6.out &&
      test_cmp M.out 6.out &&
      check_cache_at frotz clean &&
@@ -123,11 +124,11 @@ test_expect_success \
 test_expect_success \
     '7 - local addition already has the same.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
      echo frotz >frotz &&
      git update-index --add frotz &&
      echo frotz frotz >frotz &&
-     git read-tree -m -u $treeH $treeM &&
+     read_tree_u_must_succeed -m -u $treeH $treeM &&
      git ls-files --stage >7.out &&
      test_cmp M.out 7.out &&
      check_cache_at frotz dirty &&
@@ -141,27 +142,27 @@ test_expect_success \
 test_expect_success \
     '8 - conflicting addition.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
      echo frotz frotz >frotz &&
      git update-index --add frotz &&
-     if git read-tree -m -u $treeH $treeM; then false; else :; fi'
+     if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '9 - conflicting addition.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
      echo frotz frotz >frotz &&
      git update-index --add frotz &&
      echo frotz >frotz &&
-     if git read-tree -m -u $treeH $treeM; then false; else :; fi'
+     if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '10 - path removed.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
      echo rezrov >rezrov &&
      git update-index --add rezrov &&
-     git read-tree -m -u $treeH $treeM &&
+     read_tree_u_must_succeed -m -u $treeH $treeM &&
      git ls-files --stage >10.out &&
      cmp M.out 10.out &&
      sum bozbar frotz nitfol >actual10.sum &&
@@ -170,28 +171,28 @@ test_expect_success \
 test_expect_success \
     '11 - dirty path removed.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
      echo rezrov >rezrov &&
      git update-index --add rezrov &&
      echo rezrov rezrov >rezrov &&
-     if git read-tree -m -u $treeH $treeM; then false; else :; fi'
+     if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '12 - unmatching local changes being removed.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
      echo rezrov rezrov >rezrov &&
      git update-index --add rezrov &&
-     if git read-tree -m -u $treeH $treeM; then false; else :; fi'
+     if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '13 - unmatching local changes being removed.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
      echo rezrov rezrov >rezrov &&
      git update-index --add rezrov &&
      echo rezrov >rezrov &&
-     if git read-tree -m -u $treeH $treeM; then false; else :; fi'
+     if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi'
 
 cat >expected <<EOF
 -100644 X 0    nitfol
@@ -201,10 +202,10 @@ EOF
 test_expect_success \
     '14 - unchanged in two heads.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
      echo nitfol nitfol >nitfol &&
      git update-index --add nitfol &&
-     git read-tree -m -u $treeH $treeM &&
+     read_tree_u_must_succeed -m -u $treeH $treeM &&
      git ls-files --stage >14.out &&
      test_must_fail git diff -U0 --no-index M.out 14.out >14diff.out &&
      compare_change 14diff.out expected &&
@@ -221,11 +222,11 @@ test_expect_success \
 test_expect_success \
     '15 - unchanged in two heads.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
      echo nitfol nitfol >nitfol &&
      git update-index --add nitfol &&
      echo nitfol nitfol nitfol >nitfol &&
-     git read-tree -m -u $treeH $treeM &&
+     read_tree_u_must_succeed -m -u $treeH $treeM &&
      git ls-files --stage >15.out &&
      test_must_fail git diff -U0 --no-index M.out 15.out >15diff.out &&
      compare_change 15diff.out expected &&
@@ -242,27 +243,27 @@ test_expect_success \
 test_expect_success \
     '16 - conflicting local change.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
      echo bozbar bozbar >bozbar &&
      git update-index --add bozbar &&
-     if git read-tree -m -u $treeH $treeM; then false; else :; fi'
+     if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '17 - conflicting local change.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
      echo bozbar bozbar >bozbar &&
      git update-index --add bozbar &&
      echo bozbar bozbar bozbar >bozbar &&
-     if git read-tree -m -u $treeH $treeM; then false; else :; fi'
+     if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '18 - local change already having a good result.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
      echo gnusto >bozbar &&
      git update-index --add bozbar &&
-     git read-tree -m -u $treeH $treeM &&
+     read_tree_u_must_succeed -m -u $treeH $treeM &&
      git ls-files --stage >18.out &&
      test_cmp M.out 18.out &&
      check_cache_at bozbar clean &&
@@ -272,11 +273,11 @@ test_expect_success \
 test_expect_success \
     '19 - local change already having a good result, further modified.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
      echo gnusto >bozbar &&
      git update-index --add bozbar &&
      echo gnusto gnusto >bozbar &&
-     git read-tree -m -u $treeH $treeM &&
+     read_tree_u_must_succeed -m -u $treeH $treeM &&
      git ls-files --stage >19.out &&
      test_cmp M.out 19.out &&
      check_cache_at bozbar dirty &&
@@ -292,10 +293,10 @@ test_expect_success \
 test_expect_success \
     '20 - no local change, use new tree.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
      echo bozbar >bozbar &&
      git update-index --add bozbar &&
-     git read-tree -m -u $treeH $treeM &&
+     read_tree_u_must_succeed -m -u $treeH $treeM &&
      git ls-files --stage >20.out &&
      test_cmp M.out 20.out &&
      check_cache_at bozbar clean &&
@@ -305,11 +306,11 @@ test_expect_success \
 test_expect_success \
     '21 - no local change, dirty cache.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git read-tree --reset -u $treeH &&
+     read_tree_u_must_succeed --reset -u $treeH &&
      echo bozbar >bozbar &&
      git update-index --add bozbar &&
      echo gnusto gnusto >bozbar &&
-     if git read-tree -m -u $treeH $treeM; then false; else :; fi'
+     if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi'
 
 # Also make sure we did not break DF vs DF/DF case.
 test_expect_success \
@@ -336,7 +337,7 @@ test_expect_success \
      rm -fr DF &&
      echo DF >DF &&
      git update-index --add DF &&
-     git read-tree -m -u $treeDF $treeDFDF &&
+     read_tree_u_must_succeed -m -u $treeDF $treeDFDF &&
      git ls-files --stage >DFDFcheck.out &&
      test_cmp DFDF.out DFDFcheck.out &&
      check_cache_at DF/DF clean'
index eb8e3d447613ce7383f53a7a09039c01d72f35ef..b3ae7d52c6c76895434c1d2e8dfb9a31b12ebc62 100755 (executable)
@@ -3,6 +3,7 @@
 test_description='read-tree -m -u checks working tree files'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
 
 # two-tree test
 
@@ -29,7 +30,7 @@ test_expect_success 'two-way not clobbering' '
 
        echo >file2 master creates untracked file2 &&
        echo >subdir/file2 master creates untracked subdir/file2 &&
-       if err=`git read-tree -m -u master side 2>&1`
+       if err=`read_tree_u_must_succeed -m -u master side 2>&1`
        then
                echo should have complained
                false
@@ -42,7 +43,7 @@ echo file2 >.gitignore
 
 test_expect_success 'two-way with incorrect --exclude-per-directory (1)' '
 
-       if err=`git read-tree -m --exclude-per-directory=.gitignore master side 2>&1`
+       if err=`read_tree_u_must_succeed -m --exclude-per-directory=.gitignore master side 2>&1`
        then
                echo should have complained
                false
@@ -53,7 +54,7 @@ test_expect_success 'two-way with incorrect --exclude-per-directory (1)' '
 
 test_expect_success 'two-way with incorrect --exclude-per-directory (2)' '
 
-       if err=`git read-tree -m -u --exclude-per-directory=foo --exclude-per-directory=.gitignore master side 2>&1`
+       if err=`read_tree_u_must_succeed -m -u --exclude-per-directory=foo --exclude-per-directory=.gitignore master side 2>&1`
        then
                echo should have complained
                false
@@ -64,7 +65,7 @@ test_expect_success 'two-way with incorrect --exclude-per-directory (2)' '
 
 test_expect_success 'two-way clobbering a ignored file' '
 
-       git read-tree -m -u --exclude-per-directory=.gitignore master side
+       read_tree_u_must_succeed -m -u --exclude-per-directory=.gitignore master side
 '
 
 rm -f .gitignore
@@ -84,7 +85,7 @@ test_expect_success 'three-way not complaining on an untracked path in both' '
        echo >file2 file two is untracked on the master side &&
        echo >subdir/file2 file two is untracked on the master side &&
 
-       git read-tree -m -u branch-point master side
+       read_tree_u_must_succeed -m -u branch-point master side
 '
 
 test_expect_success 'three-way not clobbering a working tree file' '
@@ -94,7 +95,7 @@ test_expect_success 'three-way not clobbering a working tree file' '
        git checkout master &&
        echo >file3 file three created in master, untracked &&
        echo >subdir/file3 file three created in master, untracked &&
-       if err=`git read-tree -m -u branch-point master side 2>&1`
+       if err=`read_tree_u_must_succeed -m -u branch-point master side 2>&1`
        then
                echo should have complained
                false
@@ -113,7 +114,7 @@ test_expect_success 'three-way not complaining on an untracked file' '
        echo >file3 file three created in master, untracked &&
        echo >subdir/file3 file three created in master, untracked &&
 
-       git read-tree -m -u --exclude-per-directory=.gitignore branch-point master side
+       read_tree_u_must_succeed -m -u --exclude-per-directory=.gitignore branch-point master side
 '
 
 test_expect_success '3-way not overwriting local changes (setup)' '
@@ -137,7 +138,7 @@ test_expect_success '3-way not overwriting local changes (our side)' '
        git reset --hard &&
 
        echo >>file1 "local changes" &&
-       git read-tree -m -u branch-point side-a side-b &&
+       read_tree_u_must_succeed -m -u branch-point side-a side-b &&
        grep "new line to be kept" file1 &&
        grep "local changes" file1
 
@@ -151,7 +152,7 @@ test_expect_success '3-way not overwriting local changes (their side)' '
        git reset --hard &&
 
        echo >>file2 "local changes" &&
-       test_must_fail git read-tree -m -u branch-point side-a side-b &&
+       read_tree_u_must_fail -m -u branch-point side-a side-b &&
        ! grep "new line to be kept" file2 &&
        grep "local changes" file2
 
@@ -173,7 +174,7 @@ test_expect_success SYMLINKS 'funny symlink in work tree' '
        git add a/b &&
        git commit -m "we add a/b" &&
 
-       git read-tree -m -u sym-a sym-a sym-b
+       read_tree_u_must_succeed -m -u sym-a sym-a sym-b
 
 '
 
@@ -209,7 +210,7 @@ test_expect_success 'D/F setup' '
 test_expect_success 'D/F' '
 
        git checkout side-b &&
-       git read-tree -m -u branch-point side-b side-a &&
+       read_tree_u_must_succeed -m -u branch-point side-b side-a &&
        git ls-files -u >actual &&
        (
                a=$(git rev-parse branch-point:subdir/file2)
index 849911683a799981a565416a88fe9092b3727853..f53de79e565afcbe53a016a56a59b27831a3ce42 100755 (executable)
@@ -3,6 +3,7 @@
 test_description='read-tree -u --reset'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
 
 # two-tree test
 
@@ -22,13 +23,13 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'reset should work' '
-  git read-tree -u --reset HEAD^ &&
+  read_tree_u_must_succeed -u --reset HEAD^ &&
   git ls-files >actual &&
   test_cmp expect actual
 '
 
 test_expect_success 'reset should remove remnants from a failed merge' '
-  git read-tree --reset -u HEAD &&
+  read_tree_u_must_succeed --reset -u HEAD &&
   git ls-files -s >expect &&
   sha1=$(git rev-parse :new) &&
   (
@@ -37,13 +38,13 @@ test_expect_success 'reset should remove remnants from a failed merge' '
   ) | git update-index --index-info &&
   >old &&
   git ls-files -s &&
-  git read-tree --reset -u HEAD &&
+  read_tree_u_must_succeed --reset -u HEAD &&
   git ls-files -s >actual &&
   ! test -f old
 '
 
 test_expect_success 'Porcelain reset should remove remnants too' '
-  git read-tree --reset -u HEAD &&
+  read_tree_u_must_succeed --reset -u HEAD &&
   git ls-files -s >expect &&
   sha1=$(git rev-parse :new) &&
   (
@@ -58,7 +59,7 @@ test_expect_success 'Porcelain reset should remove remnants too' '
 '
 
 test_expect_success 'Porcelain checkout -f should remove remnants too' '
-  git read-tree --reset -u HEAD &&
+  read_tree_u_must_succeed --reset -u HEAD &&
   git ls-files -s >expect &&
   sha1=$(git rev-parse :new) &&
   (
@@ -73,7 +74,7 @@ test_expect_success 'Porcelain checkout -f should remove remnants too' '
 '
 
 test_expect_success 'Porcelain checkout -f HEAD should remove remnants too' '
-  git read-tree --reset -u HEAD &&
+  read_tree_u_must_succeed --reset -u HEAD &&
   git ls-files -s >expect &&
   sha1=$(git rev-parse :new) &&
   (
index f9e00285db662504b200fb25f0ed06c796346c52..4c50ed955eb18da1a60dd9d635f3c32d081cfb76 100755 (executable)
@@ -3,6 +3,7 @@
 test_description='test multi-tree read-tree without merging'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
 
 test_expect_success setup '
        echo one >a &&
@@ -21,7 +22,7 @@ test_expect_success setup '
 '
 
 test_expect_success 'multi-read' '
-       git read-tree initial master side &&
+       read_tree_must_succeed initial master side &&
        (echo a; echo b/c) >expect &&
        git ls-files >actual &&
        test_cmp expect actual
index 20a50eba5b77e23b6e540402b4eabb311feabd0d..018c3546b6f05228a20dfb5ec9364d0a1575e4ff 100755 (executable)
@@ -12,6 +12,7 @@ test_description='sparse checkout tests
 '
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
 
 test_expect_success 'setup' '
        cat >expected <<-\EOF &&
@@ -43,7 +44,7 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'read-tree without .git/info/sparse-checkout' '
-       git read-tree -m -u HEAD &&
+       read_tree_u_must_succeed -m -u HEAD &&
        git ls-files --stage >result &&
        test_cmp expected result &&
        git ls-files -t >result &&
@@ -52,7 +53,7 @@ test_expect_success 'read-tree without .git/info/sparse-checkout' '
 
 test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' '
        echo >.git/info/sparse-checkout &&
-       git read-tree -m -u HEAD &&
+       read_tree_u_must_succeed -m -u HEAD &&
        git ls-files -t >result &&
        test_cmp expected.swt result &&
        test -f init.t &&
@@ -62,7 +63,7 @@ test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' '
 test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-checkout and enabled' '
        git config core.sparsecheckout true &&
        echo >.git/info/sparse-checkout &&
-       git read-tree --no-sparse-checkout -m -u HEAD &&
+       read_tree_u_must_succeed --no-sparse-checkout -m -u HEAD &&
        git ls-files -t >result &&
        test_cmp expected.swt result &&
        test -f init.t &&
@@ -72,7 +73,7 @@ test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-
 test_expect_success 'read-tree with empty .git/info/sparse-checkout' '
        git config core.sparsecheckout true &&
        echo >.git/info/sparse-checkout &&
-       test_must_fail git read-tree -m -u HEAD &&
+       read_tree_u_must_fail -m -u HEAD &&
        git ls-files --stage >result &&
        test_cmp expected result &&
        git ls-files -t >result &&
@@ -90,7 +91,7 @@ test_expect_success 'match directories with trailing slash' '
        EOF
 
        echo sub/ > .git/info/sparse-checkout &&
-       git read-tree -m -u HEAD &&
+       read_tree_u_must_succeed -m -u HEAD &&
        git ls-files -t > result &&
        test_cmp expected.swt-noinit result &&
        test ! -f init.t &&
@@ -99,7 +100,7 @@ test_expect_success 'match directories with trailing slash' '
 
 test_expect_success 'match directories without trailing slash' '
        echo sub >.git/info/sparse-checkout &&
-       git read-tree -m -u HEAD &&
+       read_tree_u_must_succeed -m -u HEAD &&
        git ls-files -t >result &&
        test_cmp expected.swt-noinit result &&
        test ! -f init.t &&
@@ -149,7 +150,7 @@ EOF
 
 test_expect_success 'match directory pattern' '
        echo "s?b" >.git/info/sparse-checkout &&
-       git read-tree -m -u HEAD &&
+       read_tree_u_must_succeed -m -u HEAD &&
        git ls-files -t >result &&
        test_cmp expected.swt-noinit result &&
        test ! -f init.t &&
@@ -165,7 +166,7 @@ test_expect_success 'checkout area changes' '
        EOF
 
        echo init.t >.git/info/sparse-checkout &&
-       git read-tree -m -u HEAD &&
+       read_tree_u_must_succeed -m -u HEAD &&
        git ls-files -t >result &&
        test_cmp expected.swt-nosub result &&
        test -f init.t &&
@@ -175,7 +176,7 @@ test_expect_success 'checkout area changes' '
 test_expect_success 'read-tree updates worktree, absent case' '
        echo sub/added >.git/info/sparse-checkout &&
        git checkout -f top &&
-       git read-tree -m -u HEAD^ &&
+       read_tree_u_must_succeed -m -u HEAD^ &&
        test ! -f init.t
 '
 
@@ -183,7 +184,7 @@ test_expect_success 'read-tree updates worktree, dirty case' '
        echo sub/added >.git/info/sparse-checkout &&
        git checkout -f top &&
        echo dirty >init.t &&
-       git read-tree -m -u HEAD^ &&
+       read_tree_u_must_succeed -m -u HEAD^ &&
        grep -q dirty init.t &&
        rm init.t
 '
@@ -192,14 +193,14 @@ test_expect_success 'read-tree removes worktree, dirty case' '
        echo init.t >.git/info/sparse-checkout &&
        git checkout -f top &&
        echo dirty >added &&
-       git read-tree -m -u HEAD^ &&
+       read_tree_u_must_succeed -m -u HEAD^ &&
        grep -q dirty added
 '
 
 test_expect_success 'read-tree adds to worktree, absent case' '
        echo init.t >.git/info/sparse-checkout &&
        git checkout -f removed &&
-       git read-tree -u -m HEAD^ &&
+       read_tree_u_must_succeed -u -m HEAD^ &&
        test ! -f sub/added
 '
 
@@ -208,7 +209,7 @@ test_expect_success 'read-tree adds to worktree, dirty case' '
        git checkout -f removed &&
        mkdir sub &&
        echo dirty >sub/added &&
-       git read-tree -u -m HEAD^ &&
+       read_tree_u_must_succeed -u -m HEAD^ &&
        grep -q dirty sub/added
 '
 
index 9811d467da5a54eefd68ecfad4c68b67e5b3734f..a6a04b6b90d290a445ed7c33e5928a9a168ed28b 100755 (executable)
@@ -3,6 +3,7 @@
 test_description='read-tree D/F conflict corner cases'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
 
 maketree () {
        (
@@ -53,7 +54,7 @@ test_expect_success setup '
 
 test_expect_success '3-way (1)' '
        settree A-000 &&
-       git read-tree -m -u O-000 A-000 B-000 &&
+       read_tree_u_must_succeed -m -u O-000 A-000 B-000 &&
        checkindex <<-EOF
        3 a/b
        0 a/b-2/c/d
@@ -65,7 +66,7 @@ test_expect_success '3-way (1)' '
 
 test_expect_success '3-way (2)' '
        settree A-001 &&
-       git read-tree -m -u O-000 A-001 B-000 &&
+       read_tree_u_must_succeed -m -u O-000 A-001 B-000 &&
        checkindex <<-EOF
        3 a/b
        0 a/b-2/c/d
@@ -78,7 +79,7 @@ test_expect_success '3-way (2)' '
 
 test_expect_success '3-way (3)' '
        settree A-010 &&
-       git read-tree -m -u O-010 A-010 B-010 &&
+       read_tree_u_must_succeed -m -u O-010 A-010 B-010 &&
        checkindex <<-EOF
        2 t
        1 t-0
@@ -92,7 +93,7 @@ test_expect_success '3-way (3)' '
 
 test_expect_success '2-way (1)' '
        settree O-020 &&
-       git read-tree -m -u O-020 A-020 &&
+       read_tree_u_must_succeed -m -u O-020 A-020 &&
        checkindex <<-EOF
        0 ds/dma/ioat/Makefile
        0 ds/dma/ioat/registers.h
index ddc3921ac6a009dfc706cd19ad94f2b29af4b1cc..865b8ed26d577e154276887f88c8af9d13e62170 100755 (executable)
@@ -7,6 +7,7 @@ test_description='Try various core-level commands in subdirectory.
 '
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
 
 test_expect_success setup '
        long="a b c d e f g h i j k l m n o p q r s t u v w x y z" &&
@@ -98,13 +99,13 @@ test_expect_success 'checkout-index' '
 test_expect_success 'read-tree' '
        rm -f one dir/two &&
        tree=`git write-tree` &&
-       git read-tree --reset -u "$tree" &&
+       read_tree_u_must_succeed --reset -u "$tree" &&
        cmp one original.one &&
        cmp dir/two original.two &&
        (
                cd dir &&
                rm -f two &&
-               git read-tree --reset -u "$tree" &&
+               read_tree_u_must_succeed --reset -u "$tree" &&
                cmp two ../original.two &&
                cmp ../one ../original.one
        )
@@ -139,6 +140,22 @@ test_expect_success 'GIT_PREFIX for !alias' '
        test_cmp expect actual
 '
 
+test_expect_success 'GIT_PREFIX for built-ins' '
+       # Use GIT_EXTERNAL_DIFF to test that the "diff" built-in
+       # receives the GIT_PREFIX variable.
+       printf "dir/" >expect &&
+       printf "#!/bin/sh\n" >diff &&
+       printf "printf \"\$GIT_PREFIX\"" >>diff &&
+       chmod +x diff &&
+       (
+               cd dir &&
+               printf "change" >two &&
+               env GIT_EXTERNAL_DIFF=./diff git diff >../actual
+               git checkout -- two
+       ) &&
+       test_cmp expect actual
+'
+
 test_expect_success 'no file/rev ambiguity check inside .git' '
        git commit -a -m 1 &&
        (
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 fa69016381b0196c49472af51e36948ec7c5a2a9..a42e03967b1df3001df24089f2c50008c092ac51 100755 (executable)
@@ -169,4 +169,15 @@ test_expect_success 'checkout -f -B to an existing branch with mergeable changes
        test_must_fail test_dirty_mergeable
 '
 
+test_expect_success 'checkout -b <describe>' '
+       git tag -f -m "First commit" initial initial &&
+       git checkout -f change1 &&
+       name=$(git describe) &&
+       git checkout -b $name &&
+       git diff --exit-code change1 &&
+       echo "refs/heads/$name" >expect &&
+       git symbolic-ref HEAD >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 7d8147bb93df482cf7e11338ac96acdf43cf551e..47c8371c7edfe7945ded380bb6f96a00d26c04eb 100755 (executable)
@@ -317,7 +317,7 @@ test_expect_success '--continue tries to commit' '
 '
 
 test_expect_success 'verbose flag is heeded, even after --continue' '
-       git reset --hard HEAD@{1} &&
+       git reset --hard master@{1} &&
        test_tick &&
        test_must_fail git rebase -v -i --onto new-branch1 HEAD^ &&
        echo resolved > file1 &&
index e508246c0c904ec01775040d9fb0cc32b4b33dc2..5115de7036c38a9464dfdab0e13519a872902991 100755 (executable)
@@ -38,17 +38,17 @@ cat >expected <<EOF
 add 'sub/foo'
 EOF
 
-test_expect_success 'a file with the same (long) magic name exists' '
-       : >":(icase)ha" &&
-       test_must_fail git add -n ":(icase)ha" &&
-       git add -n "./:(icase)ha"
-'
-
 if mkdir ":" 2>/dev/null
 then
        test_set_prereq COLON_DIR
 fi
 
+test_expect_success COLON_DIR 'a file with the same (long) magic name exists' '
+       : >":(icase)ha" &&
+       test_must_fail git add -n ":(icase)ha" &&
+       git add -n "./:(icase)ha"
+'
+
 test_expect_success COLON_DIR 'a file with the same (short) magic name exists' '
        : >":/bar" &&
        test_must_fail git add -n :/bar &&
index 5c725406422dcedb6c6d5e5d61cfc80f87d3595c..7197aae1ed10add49aa72748188fecb4c2ef90a4 100755 (executable)
@@ -536,7 +536,7 @@ test_expect_success 'stash pop - fail early if specified stash is not a stash re
        git reset --hard HEAD
 '
 
-test_expect_success 'ref with non-existant reflog' '
+test_expect_success 'ref with non-existent reflog' '
        git stash clear &&
        echo bar5 > file &&
        echo bar6 > file2 &&
index 045cee312cdeb515869eeeda3f7b167fb5b91504..92248d24c48a65ab89a5b0ad255357f6034e6ad4 100755 (executable)
@@ -851,4 +851,22 @@ test_expect_success 'subject lines do not have 822 atom-quoting' '
        test_cmp expect actual
 '
 
+cat >expect <<'EOF'
+Subject: [PREFIX 1/1] header with . in it
+EOF
+test_expect_success 'subject prefixes have space prepended' '
+       git format-patch -n -1 --stdout --subject-prefix=PREFIX >patch &&
+       grep ^Subject: patch >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+Subject: [1/1] header with . in it
+EOF
+test_expect_success 'empty subject prefix does not have extra space' '
+       git format-patch -n -1 --stdout --subject-prefix= >patch &&
+       grep ^Subject: patch >actual &&
+       test_cmp expect actual
+'
+
 test_done
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 abc49348b196cf0fec232b6f2399356e4fe324d5..3c728a3ebf9ce52e5c24c81525d5cb749cfb2957 100755 (executable)
@@ -67,4 +67,9 @@ test_expect_success 'diff-files --diff-filter --quiet' '
        test_must_fail git diff-files --diff-filter=M --quiet
 '
 
+test_expect_success 'diff-tree --diff-filter --quiet' '
+       git commit -a -m "worktree state" &&
+       test_must_fail git diff-tree --diff-filter=M --quiet HEAD^ HEAD
+'
+
 test_done
diff --git a/t/t4048-diff-combined-binary.sh b/t/t4048-diff-combined-binary.sh
new file mode 100755 (executable)
index 0000000..87a8949
--- /dev/null
@@ -0,0 +1,212 @@
+#!/bin/sh
+
+test_description='combined and merge diff handle binary files and textconv'
+. ./test-lib.sh
+
+test_expect_success 'setup binary merge conflict' '
+       echo oneQ1 | q_to_nul >binary &&
+       git add binary &&
+       git commit -m one &&
+       echo twoQ2 | q_to_nul >binary &&
+       git commit -a -m two &&
+       git checkout -b branch-binary HEAD^ &&
+       echo threeQ3 | q_to_nul >binary &&
+       git commit -a -m three &&
+       test_must_fail git merge master &&
+       echo resolvedQhooray | q_to_nul >binary &&
+       git commit -a -m resolved
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --git a/binary b/binary
+index 7ea6ded..9563691 100644
+Binary files a/binary and b/binary differ
+resolved
+
+diff --git a/binary b/binary
+index 6197570..9563691 100644
+Binary files a/binary and b/binary differ
+EOF
+test_expect_success 'diff -m indicates binary-ness' '
+       git show --format=%s -m >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --combined binary
+index 7ea6ded,6197570..9563691
+Binary files differ
+EOF
+test_expect_success 'diff -c indicates binary-ness' '
+       git show --format=%s -c >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --cc binary
+index 7ea6ded,6197570..9563691
+Binary files differ
+EOF
+test_expect_success 'diff --cc indicates binary-ness' '
+       git show --format=%s --cc >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'setup non-binary with binary attribute' '
+       git checkout master &&
+       test_commit one text &&
+       test_commit two text &&
+       git checkout -b branch-text HEAD^ &&
+       test_commit three text &&
+       test_must_fail git merge master &&
+       test_commit resolved text &&
+       echo text -diff >.gitattributes
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --git a/text b/text
+index 2bdf67a..2ab19ae 100644
+Binary files a/text and b/text differ
+resolved
+
+diff --git a/text b/text
+index f719efd..2ab19ae 100644
+Binary files a/text and b/text differ
+EOF
+test_expect_success 'diff -m respects binary attribute' '
+       git show --format=%s -m >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --combined text
+index 2bdf67a,f719efd..2ab19ae
+Binary files differ
+EOF
+test_expect_success 'diff -c respects binary attribute' '
+       git show --format=%s -c >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --cc text
+index 2bdf67a,f719efd..2ab19ae
+Binary files differ
+EOF
+test_expect_success 'diff --cc respects binary attribute' '
+       git show --format=%s --cc >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'setup textconv attribute' '
+       echo "text diff=upcase" >.gitattributes &&
+       git config diff.upcase.textconv "tr a-z A-Z <"
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --git a/text b/text
+index 2bdf67a..2ab19ae 100644
+--- a/text
++++ b/text
+@@ -1 +1 @@
+-THREE
++RESOLVED
+resolved
+
+diff --git a/text b/text
+index f719efd..2ab19ae 100644
+--- a/text
++++ b/text
+@@ -1 +1 @@
+-TWO
++RESOLVED
+EOF
+test_expect_success 'diff -m respects textconv attribute' '
+       git show --format=%s -m >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --combined text
+index 2bdf67a,f719efd..2ab19ae
+--- a/text
++++ b/text
+@@@ -1,1 -1,1 +1,1 @@@
+- THREE
+ -TWO
+++RESOLVED
+EOF
+test_expect_success 'diff -c respects textconv attribute' '
+       git show --format=%s -c >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --cc text
+index 2bdf67a,f719efd..2ab19ae
+--- a/text
++++ b/text
+@@@ -1,1 -1,1 +1,1 @@@
+- THREE
+ -TWO
+++RESOLVED
+EOF
+test_expect_success 'diff --cc respects textconv attribute' '
+       git show --format=%s --cc >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+diff --combined text
+index 2bdf67a,f719efd..2ab19ae
+--- a/text
++++ b/text
+@@@ -1,1 -1,1 +1,1 @@@
+- three
+ -two
+++resolved
+EOF
+test_expect_success 'diff-tree plumbing does not respect textconv' '
+       git diff-tree HEAD -c -p >full &&
+       tail -n +2 full >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+diff --cc text
+index 2bdf67a,f719efd..0000000
+--- a/text
++++ b/text
+@@@ -1,1 -1,1 +1,5 @@@
+++<<<<<<< HEAD
+ +THREE
+++=======
++ TWO
+++>>>>>>> MASTER
+EOF
+test_expect_success 'diff --cc respects textconv on worktree file' '
+       git reset --hard HEAD^ &&
+       test_must_fail git merge master &&
+       git diff >actual &&
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t4049-diff-stat-count.sh b/t/t4049-diff-stat-count.sh
new file mode 100755 (executable)
index 0000000..641e70d
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh
+# Copyright (c) 2011, Google Inc.
+
+test_description='diff --stat-count'
+. ./test-lib.sh
+
+test_expect_success setup '
+       >a &&
+       >b &&
+       >c &&
+       >d &&
+       git add a b c d &&
+       chmod +x c d &&
+       echo a >a &&
+       echo b >b &&
+       cat >expect <<-\EOF
+        a |    1 +
+        b |    1 +
+        2 files changed, 2 insertions(+), 0 deletions(-)
+       EOF
+       git diff --stat --stat-count=2 >actual &&
+       test_cmp expect actual
+'
+
+test_done
index 850fc96d1f07b19310cb4672ab44374b37b82d67..151404e0908c55f3c0d399d03225b077dc226562 100755 (executable)
@@ -465,7 +465,7 @@ test_expect_success 'am newline in subject' '
        test_tick &&
        sed -e "s/second/second \\\n foo/" patch1 >patchnl &&
        git am <patchnl >output.out 2>&1 &&
-       grep "^Applying: second \\\n foo$" output.out
+       test_i18ngrep "^Applying: second \\\n foo$" output.out
 '
 
 test_expect_success 'am -q is quiet' '
index c95c4ccc393d0863ad53b6a2a684893282d7d9e6..1176bcccf3b3f3708df04f49bfd084190cd27600 100755 (executable)
@@ -45,8 +45,9 @@ do
 
        test_expect_success "am$with3 --skip continue after failed am$with3" '
                test_must_fail git am$with3 --skip >output &&
-               test "$(grep "^Applying" output)" = "Applying: 6" &&
-               test_cmp file-2-expect file-2 &&
+               test_i18ngrep "^Applying" output >output.applying &&
+               test_i18ngrep "^Applying: 6$" output.applying &&
+               test_i18ncmp file-2-expect file-2 &&
                test ! -f .git/MERGE_RR
        '
 
diff --git a/t/t4152-am-subjects.sh b/t/t4152-am-subjects.sh
new file mode 100755 (executable)
index 0000000..4c68245
--- /dev/null
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+test_description='test subject preservation with format-patch | am'
+. ./test-lib.sh
+
+make_patches() {
+       type=$1
+       subject=$2
+       test_expect_success "create patches with $type subject" '
+               git reset --hard baseline &&
+               echo $type >file &&
+               git commit -a -m "$subject" &&
+               git format-patch -1 --stdout >$type.patch &&
+               git format-patch -1 --stdout -k >$type-k.patch
+       '
+}
+
+check_subject() {
+       git reset --hard baseline &&
+       git am $2 $1.patch &&
+       git log -1 --pretty=format:%B >actual &&
+       test_cmp expect actual
+}
+
+test_expect_success 'setup baseline commit' '
+       test_commit baseline file
+'
+
+SHORT_SUBJECT='short subject'
+make_patches short "$SHORT_SUBJECT"
+
+LONG_SUBJECT1='this is a long subject that is virtually guaranteed'
+LONG_SUBJECT2='to require wrapping via format-patch if it is all'
+LONG_SUBJECT3='going to appear on a single line'
+LONG_SUBJECT="$LONG_SUBJECT1 $LONG_SUBJECT2 $LONG_SUBJECT3"
+make_patches long "$LONG_SUBJECT"
+
+MULTILINE_SUBJECT="$LONG_SUBJECT1
+$LONG_SUBJECT2
+$LONG_SUBJECT3"
+make_patches multiline "$MULTILINE_SUBJECT"
+
+echo "$SHORT_SUBJECT" >expect
+test_expect_success 'short subject preserved (format-patch | am)' '
+       check_subject short
+'
+test_expect_success 'short subject preserved (format-patch -k | am)' '
+       check_subject short-k
+'
+test_expect_success 'short subject preserved (format-patch -k | am -k)' '
+       check_subject short-k -k
+'
+
+echo "$LONG_SUBJECT" >expect
+test_expect_success 'long subject preserved (format-patch | am)' '
+       check_subject long
+'
+test_expect_success 'long subject preserved (format-patch -k | am)' '
+       check_subject long-k
+'
+test_expect_success 'long subject preserved (format-patch -k | am -k)' '
+       check_subject long-k -k
+'
+
+echo "$LONG_SUBJECT" >expect
+test_expect_success 'multiline subject unwrapped (format-patch | am)' '
+       check_subject multiline
+'
+test_expect_success 'multiline subject unwrapped (format-patch -k | am)' '
+       check_subject multiline-k
+'
+echo "$MULTILINE_SUBJECT" >expect
+test_expect_success 'multiline subject preserved (format-patch -k | am -k)' '
+       check_subject multiline-k -k
+'
+
+test_done
index e818de6ddd904378265cb11f2d48075cda474f5f..1f182f612c7e2376b503cf0b9cf7389e37903239 100755 (executable)
@@ -94,7 +94,7 @@ nick1 (1):
 
 EOF
 
-test_expect_success 'mailmap.file non-existant' '
+test_expect_success 'mailmap.file non-existent' '
        rm internal_mailmap/.mailmap &&
        rmdir internal_mailmap &&
        git shortlog HEAD >actual &&
index cb9f2bdd2956244955d56ec8a0cd2320726e1030..2ae9faa8b37821db6e7c28ae3d98e53cb25264b1 100755 (executable)
@@ -45,7 +45,7 @@ test_expect_success 'alias user-defined tformat' '
        test_cmp expected actual
 '
 
-test_expect_success 'alias non-existant format' '
+test_expect_success 'alias non-existent format' '
        git config pretty.test-alias format-that-will-never-exist &&
        test_must_fail git log --pretty=test-alias
 '
index 4e69c907d810c5a82892f94f05374b824a003771..0d0222ea2ace733ffe3fbfbdee58520abcd40e92 100755 (executable)
@@ -347,6 +347,21 @@ test_expect_success 'fetch mirrors do not act as mirrors during push' '
        )
 '
 
+test_expect_success 'add fetch mirror with specific branches' '
+       git init --bare mirror-fetch/track &&
+       (cd mirror-fetch/track &&
+        git remote add --mirror=fetch -t heads/new parent ../parent
+       )
+'
+
+test_expect_success 'fetch mirror respects specific branches' '
+       (cd mirror-fetch/track &&
+        git fetch parent &&
+        git rev-parse --verify refs/heads/new &&
+        test_must_fail git rev-parse --verify refs/heads/renamed
+       )
+'
+
 test_expect_success 'add --mirror=push' '
        mkdir mirror-push &&
        git init --bare mirror-push/public &&
@@ -382,6 +397,13 @@ test_expect_success 'push mirrors do not act as mirrors during fetch' '
        )
 '
 
+test_expect_success 'push mirrors do not allow you to specify refs' '
+       git init mirror-push/track &&
+       (cd mirror-push/track &&
+        test_must_fail git remote add --mirror=push -t new public ../public
+       )
+'
+
 test_expect_success 'add alt && prune' '
        (mkdir alttst &&
         cd alttst &&
index b7b7ddaa403d7a06c0203b89e484d56d20863f4a..530b01678e5c9cfc1aed2fe2c6fcd96b9a09a90d 100755 (executable)
@@ -43,10 +43,10 @@ test_expect_success 'no group updates all' '
        repo_fetched two
 '
 
-test_expect_success 'nonexistant group produces error' '
-       mark nonexistant &&
+test_expect_success 'nonexistent group produces error' '
+       mark nonexistent &&
        update_repos &&
-       test_must_fail git remote update nonexistant &&
+       test_must_fail git remote update nonexistent &&
        ! repo_fetched one &&
        ! repo_fetched two
 '
index d73731e6446f71480db1ec7cceb73f27ad51ecd3..3abb2907ea91c5ef6298ca7ee64b7a8156d65d96 100755 (executable)
@@ -367,7 +367,7 @@ test_expect_success 'push with colon-less refspec (4)' '
 
 '
 
-test_expect_success 'push head with non-existant, incomplete dest' '
+test_expect_success 'push head with non-existent, incomplete dest' '
 
        mk_test &&
        git push testrepo master:branch &&
@@ -375,7 +375,7 @@ test_expect_success 'push head with non-existant, incomplete dest' '
 
 '
 
-test_expect_success 'push tag with non-existant, incomplete dest' '
+test_expect_success 'push tag with non-existent, incomplete dest' '
 
        mk_test &&
        git tag -f v1.0 &&
@@ -384,14 +384,14 @@ test_expect_success 'push tag with non-existant, incomplete dest' '
 
 '
 
-test_expect_success 'push sha1 with non-existant, incomplete dest' '
+test_expect_success 'push sha1 with non-existent, incomplete dest' '
 
        mk_test &&
        test_must_fail git push testrepo `git rev-parse master`:foo
 
 '
 
-test_expect_success 'push ref expression with non-existant, incomplete dest' '
+test_expect_success 'push ref expression with non-existent, incomplete dest' '
 
        mk_test &&
        test_must_fail git push testrepo master^:branch
@@ -436,7 +436,7 @@ test_expect_success 'push with +HEAD' '
 
 '
 
-test_expect_success 'push HEAD with non-existant, incomplete dest' '
+test_expect_success 'push HEAD with non-existent, incomplete dest' '
 
        mk_test &&
        git checkout master &&
index 874279e32da98ac2c20137a207a0d115f073e444..b2b26b72d05a392d8adb83be50c1bb9e73c4eb2d 100755 (executable)
@@ -99,7 +99,7 @@ test_expect_success 'submodule add to .gitignored path fails' '
                git add --force .gitignore &&
                git commit -m"Ignore everything" &&
                ! git submodule add "$submodurl" submod >actual 2>&1 &&
-               test_cmp expect actual
+               test_i18ncmp expect actual
        )
 '
 
@@ -357,7 +357,7 @@ test_expect_success 'update --init' '
 
        git submodule update init > update.out &&
        cat update.out &&
-       grep "not initialized" update.out &&
+       test_i18ngrep "not initialized" update.out &&
        ! test -d init/.git &&
 
        git submodule update --init init &&
index 7d7fde057b04e4615e32b0ddc71fd3b07968a129..30b429e7dcbcd56af21ded67128aa18901133275 100755 (executable)
@@ -128,7 +128,7 @@ test_expect_success 'typechanged submodule(submodule->blob), --cached' "
   < Add foo5
 
 EOF
-       test_cmp actual expected
+       test_i18ncmp actual expected
 "
 
 test_expect_success 'typechanged submodule(submodule->blob), --files' "
@@ -138,7 +138,7 @@ test_expect_success 'typechanged submodule(submodule->blob), --files' "
   > Add foo5
 
 EOF
-    test_cmp actual expected
+    test_i18ncmp actual expected
 "
 
 rm -rf sm1 &&
@@ -149,7 +149,7 @@ test_expect_success 'typechanged submodule(submodule->blob)' "
 * sm1 $head4(submodule)->$head5(blob):
 
 EOF
-    test_cmp actual expected
+    test_i18ncmp actual expected
 "
 
 rm -f sm1 &&
@@ -162,7 +162,7 @@ test_expect_success 'nonexistent commit' "
   Warn: sm1 doesn't contain commit $head4_full
 
 EOF
-    test_cmp actual expected
+    test_i18ncmp actual expected
 "
 
 commit_file
@@ -173,7 +173,7 @@ test_expect_success 'typechanged submodule(blob->submodule)' "
   > Add foo7
 
 EOF
-    test_cmp expected actual
+    test_i18ncmp expected actual
 "
 
 commit_file sm1 &&
@@ -228,7 +228,7 @@ EOF
 
 test_expect_success '--for-status' "
     git submodule summary --for-status HEAD^ >actual &&
-    test_cmp actual - <<EOF
+    test_i18ncmp actual - <<EOF
 # Submodule changes to be committed:
 #
 # * sm1 $head6...0000000:
index 4f16fcce2bfcb63f437fa6b495fdb5c4370fccc1..c679f36a4daeab3f114c9cf0c84c93549d28c680 100755 (executable)
@@ -90,7 +90,7 @@ test_expect_success 'submodule update does not fetch already present commits' '
        (cd super &&
          git submodule update > ../actual 2> ../actual.err
        ) &&
-       test_cmp expected actual &&
+       test_i18ncmp expected actual &&
        ! test -s actual.err
 '
 
@@ -298,4 +298,148 @@ test_expect_success 'submodule update ignores update=rebase config for new submo
        )
 '
 
+test_expect_success 'submodule update continues after checkout error' '
+       (cd super &&
+        git reset --hard HEAD &&
+        git submodule add ../submodule submodule2 &&
+        git submodule init &&
+        git commit -am "new_submodule" &&
+        (cd submodule2 &&
+         git rev-parse --max-count=1 HEAD > ../expect
+        ) &&
+        (cd submodule &&
+         test_commit "update_submodule" file
+        ) &&
+        (cd submodule2 &&
+         test_commit "update_submodule2" file
+        ) &&
+        git add submodule &&
+        git add submodule2 &&
+        git commit -m "two_new_submodule_commits" &&
+        (cd submodule &&
+         echo "" > file
+        ) &&
+        git checkout HEAD^ &&
+        test_must_fail git submodule update &&
+        (cd submodule2 &&
+         git rev-parse --max-count=1 HEAD > ../actual
+        ) &&
+        test_cmp expect actual
+       )
+'
+test_expect_success 'submodule update continues after recursive checkout error' '
+       (cd super &&
+        git reset --hard HEAD &&
+        git checkout master &&
+        git submodule update &&
+        (cd submodule &&
+         git submodule add ../submodule subsubmodule &&
+         git submodule init &&
+         git commit -m "new_subsubmodule"
+        ) &&
+        git add submodule &&
+        git commit -m "update_submodule" &&
+        (cd submodule &&
+         (cd subsubmodule &&
+          test_commit "update_subsubmodule" file
+         ) &&
+         git add subsubmodule &&
+         test_commit "update_submodule_again" file &&
+         (cd subsubmodule &&
+          test_commit "update_subsubmodule_again" file
+         ) &&
+         test_commit "update_submodule_again_again" file
+        ) &&
+        (cd submodule2 &&
+         git rev-parse --max-count=1 HEAD > ../expect &&
+         test_commit "update_submodule2_again" file
+        ) &&
+        git add submodule &&
+        git add submodule2 &&
+        git commit -m "new_commits" &&
+        git checkout HEAD^ &&
+        (cd submodule &&
+         git checkout HEAD^ &&
+         (cd subsubmodule &&
+          echo "" > file
+         )
+        ) &&
+        test_must_fail git submodule update --recursive &&
+        (cd submodule2 &&
+         git rev-parse --max-count=1 HEAD > ../actual
+        ) &&
+        test_cmp expect actual
+       )
+'
+
+test_expect_success 'submodule update exit immediately in case of merge conflict' '
+       (cd super &&
+        git checkout master &&
+        git reset --hard HEAD &&
+        (cd submodule &&
+         (cd subsubmodule &&
+          git reset --hard HEAD
+         )
+        ) &&
+        git submodule update --recursive &&
+        (cd submodule &&
+         test_commit "update_submodule_2" file
+        ) &&
+        (cd submodule2 &&
+         test_commit "update_submodule2_2" file
+        ) &&
+        git add submodule &&
+        git add submodule2 &&
+        git commit -m "two_new_submodule_commits" &&
+        (cd submodule &&
+         git checkout master &&
+         test_commit "conflict" file &&
+         echo "conflict" > file
+        ) &&
+        git checkout HEAD^ &&
+        (cd submodule2 &&
+         git rev-parse --max-count=1 HEAD > ../expect
+        ) &&
+        git config submodule.submodule.update merge &&
+        test_must_fail git submodule update &&
+        (cd submodule2 &&
+         git rev-parse --max-count=1 HEAD > ../actual
+        ) &&
+        test_cmp expect actual
+       )
+'
+test_expect_success 'submodule update exit immediately after recursive rebase error' '
+       (cd super &&
+        git checkout master &&
+        git reset --hard HEAD &&
+        (cd submodule &&
+         git reset --hard HEAD &&
+         git submodule update --recursive
+        ) &&
+        (cd submodule &&
+         test_commit "update_submodule_3" file
+        ) &&
+        (cd submodule2 &&
+         test_commit "update_submodule2_3" file
+        ) &&
+        git add submodule &&
+        git add submodule2 &&
+        git commit -m "two_new_submodule_commits" &&
+        (cd submodule &&
+         git checkout master &&
+         test_commit "conflict2" file &&
+         echo "conflict" > file
+        ) &&
+        git checkout HEAD^ &&
+        (cd submodule2 &&
+         git rev-parse --max-count=1 HEAD > ../expect
+        ) &&
+        git config submodule.submodule.update rebase &&
+        test_must_fail git submodule update &&
+        (cd submodule2 &&
+         git rev-parse --max-count=1 HEAD > ../actual
+        ) &&
+        test_cmp expect actual
+       )
+'
 test_done
index e5be13c271c92ce7c51601ee1eaf966d849d8ae4..ae3bd18a5ed167caa461f269cd77b6f34a6ae7b0 100755 (executable)
@@ -77,7 +77,7 @@ test_expect_success 'test basic "submodule foreach" usage' '
                git config foo.bar zar &&
                git submodule foreach "git config --file \"\$toplevel/.git/config\" foo.bar"
        ) &&
-       test_cmp expect actual
+       test_i18ncmp expect actual
 '
 
 test_expect_success 'setup nested submodules' '
@@ -158,7 +158,7 @@ test_expect_success 'test messages from "foreach --recursive"' '
                cd clone2 &&
                git submodule foreach --recursive "true" > ../actual
        ) &&
-       test_cmp expect actual
+       test_i18ncmp expect actual
 '
 
 cat > expect <<EOF
index 8528f64c8d1491fd3c279f030b6f8aee2050cdf7..ee7f0cd4596f982f16cbf3859675e6faba424faa 100755 (executable)
@@ -84,5 +84,38 @@ test_expect_success POSIXPERM '--no-verify with non-executable hook' '
        git commit --no-verify -m "more content"
 
 '
+chmod +x "$HOOK"
+
+# a hook that checks $GIT_PREFIX and succeeds inside the
+# success/ subdirectory only
+cat > "$HOOK" <<EOF
+#!/bin/sh
+test \$GIT_PREFIX = success/
+EOF
+
+test_expect_success 'with hook requiring GIT_PREFIX' '
+
+       echo "more content" >> file &&
+       git add file &&
+       mkdir success &&
+       (
+               cd success &&
+               git commit -m "hook requires GIT_PREFIX = success/"
+       ) &&
+       rmdir success
+'
+
+test_expect_success 'with failing hook requiring GIT_PREFIX' '
+
+       echo "more content" >> file &&
+       git add file &&
+       mkdir fail &&
+       (
+               cd fail &&
+               test_must_fail git commit -m "hook must fail"
+       ) &&
+       rmdir fail &&
+       git checkout -- file
+'
 
 test_done
index cd6e2c5e871230a43f504e165498139d8ec7a3d2..905255adf0ca5b15d9befa772cda4a650bd15f34 100755 (executable)
@@ -131,6 +131,127 @@ test_expect_success 'status -s' '
 
 '
 
+test_expect_success 'status with gitignore' '
+       {
+               echo ".gitignore" &&
+               echo "expect" &&
+               echo "output" &&
+               echo "untracked"
+       } >.gitignore &&
+
+       cat >expect <<-\EOF &&
+        M dir1/modified
+       A  dir2/added
+       ?? dir2/modified
+       EOF
+       git status -s >output &&
+       test_cmp expect output &&
+
+       cat >expect <<-\EOF &&
+        M dir1/modified
+       A  dir2/added
+       ?? dir2/modified
+       !! .gitignore
+       !! dir1/untracked
+       !! dir2/untracked
+       !! expect
+       !! output
+       !! untracked
+       EOF
+       git status -s --ignored >output &&
+       test_cmp expect output &&
+
+       cat >expect <<-\EOF &&
+       # On branch master
+       # Changes to be committed:
+       #   (use "git reset HEAD <file>..." to unstage)
+       #
+       #       new file:   dir2/added
+       #
+       # Changes not staged for commit:
+       #   (use "git add <file>..." to update what will be committed)
+       #   (use "git checkout -- <file>..." to discard changes in working directory)
+       #
+       #       modified:   dir1/modified
+       #
+       # Untracked files:
+       #   (use "git add <file>..." to include in what will be committed)
+       #
+       #       dir2/modified
+       # Ignored files:
+       #   (use "git add -f <file>..." to include in what will be committed)
+       #
+       #       .gitignore
+       #       dir1/untracked
+       #       dir2/untracked
+       #       expect
+       #       output
+       #       untracked
+       EOF
+       git status --ignored >output &&
+       test_cmp expect output
+'
+
+test_expect_success 'status with gitignore (nothing untracked)' '
+       {
+               echo ".gitignore" &&
+               echo "expect" &&
+               echo "dir2/modified" &&
+               echo "output" &&
+               echo "untracked"
+       } >.gitignore &&
+
+       cat >expect <<-\EOF &&
+        M dir1/modified
+       A  dir2/added
+       EOF
+       git status -s >output &&
+       test_cmp expect output &&
+
+       cat >expect <<-\EOF &&
+        M dir1/modified
+       A  dir2/added
+       !! .gitignore
+       !! dir1/untracked
+       !! dir2/modified
+       !! dir2/untracked
+       !! expect
+       !! output
+       !! untracked
+       EOF
+       git status -s --ignored >output &&
+       test_cmp expect output &&
+
+       cat >expect <<-\EOF &&
+       # On branch master
+       # Changes to be committed:
+       #   (use "git reset HEAD <file>..." to unstage)
+       #
+       #       new file:   dir2/added
+       #
+       # Changes not staged for commit:
+       #   (use "git add <file>..." to update what will be committed)
+       #   (use "git checkout -- <file>..." to discard changes in working directory)
+       #
+       #       modified:   dir1/modified
+       #
+       # Ignored files:
+       #   (use "git add -f <file>..." to include in what will be committed)
+       #
+       #       .gitignore
+       #       dir1/untracked
+       #       dir2/modified
+       #       dir2/untracked
+       #       expect
+       #       output
+       #       untracked
+       EOF
+       git status --ignored >output &&
+       test_cmp expect output
+'
+
+rm -f .gitignore
+
 cat >expect <<\EOF
 ## master
  M dir1/modified
@@ -780,6 +901,13 @@ test_expect_success 'status -s submodule summary (clean submodule)' '
        test_cmp expect output
 '
 
+test_expect_success 'status -z implies porcelain' '
+       git status --porcelain |
+       perl -pe "s/\012/\000/g" >expect &&
+       git status -z >output &&
+       test_cmp expect output
+'
+
 cat >expect <<EOF
 # On branch master
 # Changes to be committed:
index 8184c264cf942bcd8e6c2f75b5526ddfdc5a11e9..a29ae45b399f89123f886f5b35580bcdc0e803f1 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,195 @@ 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' '
+       cat >expected <<-EOF &&
+       hello.c:        printf("Hello world.\n");
+       EOF
+       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_config() {
+       git config "$1" "$2" &&
+       test_when_finished "git config --unset $1"
+}
+
+cat >expected <<EOF
+hello.c<RED>:<RESET>int main(int argc, const char **argv)
+hello.c<RED>-<RESET>{
+<RED>--<RESET>
+hello.c<RED>:<RESET>   /* char ?? */
+hello.c<RED>-<RESET>}
+<RED>--<RESET>
+hello_world<RED>:<RESET>Hello_world
+hello_world<RED>-<RESET>HeLLo_world
+EOF
+
+test_expect_success 'grep --color, separator' '
+       test_config color.grep.context          normal &&
+       test_config color.grep.filename         normal &&
+       test_config color.grep.function         normal &&
+       test_config color.grep.linenumber       normal &&
+       test_config color.grep.match            normal &&
+       test_config color.grep.selected         normal &&
+       test_config color.grep.separator        red &&
+
+       git grep --color=always -A1 -e char -e lo_w hello.c hello_world |
+       test_decode_color >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c:int main(int argc, const char **argv)
+hello.c:       /* char ?? */
+
+hello_world:Hello_world
+EOF
+
+test_expect_success 'grep --break' '
+       git grep --break -e char -e lo_w hello.c hello_world >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c:int main(int argc, const char **argv)
+hello.c-{
+--
+hello.c:       /* char ?? */
+hello.c-}
+
+hello_world:Hello_world
+hello_world-HeLLo_world
+EOF
+
+test_expect_success 'grep --break with context' '
+       git grep --break -A1 -e char -e lo_w hello.c hello_world >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c
+int main(int argc, const char **argv)
+       /* char ?? */
+hello_world
+Hello_world
+EOF
+
+test_expect_success 'grep --heading' '
+       git grep --heading -e char -e lo_w hello.c hello_world >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+<BOLD;GREEN>hello.c<RESET>
+2:int main(int argc, const <BLACK;BYELLOW>char<RESET> **argv)
+6:     /* <BLACK;BYELLOW>char<RESET> ?? */
+
+<BOLD;GREEN>hello_world<RESET>
+3:Hel<BLACK;BYELLOW>lo_w<RESET>orld
+EOF
+
+test_expect_success 'mimic ack-grep --group' '
+       test_config color.grep.context          normal &&
+       test_config color.grep.filename         "bold green" &&
+       test_config color.grep.function         normal &&
+       test_config color.grep.linenumber       normal &&
+       test_config color.grep.match            "black yellow" &&
+       test_config color.grep.selected         normal &&
+       test_config color.grep.separator        normal &&
+
+       git grep --break --heading -n --color \
+               -e char -e lo_w hello.c hello_world |
+       test_decode_color >actual &&
+       test_cmp expected actual
+'
+
 test_done
diff --git a/t/t9159-git-svn-no-parent-mergeinfo.sh b/t/t9159-git-svn-no-parent-mergeinfo.sh
new file mode 100755 (executable)
index 0000000..85120b7
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+test_description='git svn handling of root commits in merge ranges'
+. ./lib-git-svn.sh
+
+test_expect_success 'test handling of root commits in merge ranges' '
+       mkdir -p init/trunk init/branches init/tags &&
+       echo "r1" > init/trunk/file.txt &&
+       svn_cmd import -m "initial import" init "$svnrepo" &&
+       svn_cmd co "$svnrepo" tmp &&
+       (
+               cd tmp &&
+               echo "r2" > trunk/file.txt &&
+               svn_cmd commit -m "Modify file.txt on trunk" &&
+               svn_cmd cp trunk@1 branches/a &&
+               svn_cmd commit -m "Create branch a from trunk r1" &&
+               svn_cmd propset svn:mergeinfo /trunk:1-2 branches/a &&
+               svn_cmd commit -m "Fake merge of trunk r2 into branch a" &&
+               mkdir branches/b &&
+               echo "r5" > branches/b/file2.txt &&
+               svn_cmd add branches/b &&
+               svn_cmd commit -m "Create branch b from thin air" &&
+               echo "r6" > branches/b/file2.txt &&
+               svn_cmd commit -m "Modify file2.txt on branch b" &&
+               svn_cmd cp branches/b@5 branches/c &&
+               svn_cmd commit -m "Create branch c from branch b r5" &&
+               svn_cmd propset svn:mergeinfo /branches/b:5-6 branches/c &&
+               svn_cmd commit -m "Fake merge of branch b r6 into branch c"
+       ) &&
+       git svn init -s "$svnrepo" &&
+       git svn fetch
+       '
+
+test_done
index 6b1ba6c858562b7c085951fb1d69d4d2371e866c..2a53640c5b29c19a2327570148638dbf94bc2afa 100755 (executable)
@@ -1893,7 +1893,7 @@ test_expect_success \
     test_cmp marks.out marks.new'
 
 cat >input <<EOF
-feature import-marks=nonexistant.marks
+feature import-marks=nonexistent.marks
 feature export-marks=marks.new
 EOF
 
@@ -1904,7 +1904,7 @@ test_expect_success \
 
 
 cat >input <<EOF
-feature import-marks=nonexistant.marks
+feature import-marks=nonexistent.marks
 feature export-marks=combined.marks
 EOF
 
index 71ef0acb1ba64d36a7909b5308ab6b6e89c31466..53297156a314a5b5e03385a3ad887eff959359c4 100755 (executable)
@@ -644,4 +644,20 @@ test_expect_success \
        'ctags: search projects by non existent tag' \
        'gitweb_run "by_tag=non-existent"'
 
+test_expect_success \
+       'ctags: malformed tag weights' \
+       'mkdir -p .git/ctags &&
+        echo "not-a-number" > .git/ctags/nan &&
+        echo "not-a-number-2" > .git/ctags/nan2 &&
+        echo "0.1" >.git/ctags/floating-point &&
+        gitweb_run'
+
+# ----------------------------------------------------------------------
+# 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..df25f1792923a65aab2c4ce88e7b528effb381f5 100644 (file)
@@ -446,9 +446,14 @@ test_debug () {
 
 test_run_ () {
        test_cleanup=:
+       expecting_failure=$2
        eval >&3 2>&4 "$1"
        eval_ret=$?
-       eval >&3 2>&4 "$test_cleanup"
+
+       if test -z "$immediate" || test $eval_ret = 0 || test -n "$expecting_failure"
+       then
+               eval >&3 2>&4 "$test_cleanup"
+       fi
        if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then
                echo ""
        fi
@@ -497,7 +502,7 @@ test_expect_failure () {
        if ! test_skip "$@"
        then
                say >&3 "checking known breakage: $2"
-               test_run_ "$2"
+               test_run_ "$2" expecting_failure
                if [ "$?" = 0 -a "$eval_ret" = 0 ]
                then
                        test_known_broken_ok_ "$1"
@@ -731,12 +736,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.
@@ -775,6 +779,9 @@ test_cmp() {
 #
 # except that the greeting and config --unset must both succeed for
 # the test to pass.
+#
+# Note that under --immediate mode, no clean-up is done to help diagnose
+# what went wrong.
 
 test_when_finished () {
        test_cleanup="{ $*
@@ -885,8 +892,13 @@ then
        }
 
        make_valgrind_symlink () {
-               # handle only executables
-               test -x "$1" || return
+               # handle only executables, unless they are shell libraries that
+               # need to be in the exec-path.  We will just use "#!" as a
+               # guess for a shell-script, since we have no idea what the user
+               # may have configured as the shell path.
+               test -x "$1" ||
+               test "#!" = "$(head -c 2 <"$1")" ||
+               return;
 
                base=$(basename "$1")
                symlink_target=$GIT_BUILD_DIR/$base
@@ -1072,6 +1084,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 3f4072525b9489a86b2231748abe13c611e05274..b3cc2e4753447d4734ed08a70e3396771257dced 100644 (file)
@@ -142,8 +142,7 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
        strbuf_add(&base, base_str, baselen);
 
        for (;;) {
-               if (DIFF_OPT_TST(opt, QUICK) &&
-                   DIFF_OPT_TST(opt, HAS_CHANGES))
+               if (diff_can_quit_early(opt))
                        break;
                if (opt->pathspec.nr) {
                        skip_uninteresting(t1, &base, opt, &t1_match);
index 0bc4b2ddca2d96e16c3f7b32e8e922f426706be6..3a61d821ee85a9cfb026909c6ae90def360a7681 100644 (file)
@@ -203,7 +203,7 @@ static int check_updates(struct unpack_trees_options *o)
 
                if (ce->ce_flags & CE_WT_REMOVE) {
                        display_progress(progress, ++cnt);
-                       if (o->update)
+                       if (o->update && !o->dry_run)
                                unlink_entry(ce);
                        continue;
                }
@@ -217,7 +217,7 @@ static int check_updates(struct unpack_trees_options *o)
                if (ce->ce_flags & CE_UPDATE) {
                        display_progress(progress, ++cnt);
                        ce->ce_flags &= ~CE_UPDATE;
-                       if (o->update) {
+                       if (o->update && !o->dry_run) {
                                errs |= checkout_entry(ce, &state, NULL);
                        }
                }
@@ -593,7 +593,7 @@ static int unpack_nondirectories(int n, unsigned long mask,
 static int unpack_failed(struct unpack_trees_options *o, const char *message)
 {
        discard_index(&o->result);
-       if (!o->gently) {
+       if (!o->gently && !o->exiting_early) {
                if (message)
                        return error("%s", message);
                return -1;
@@ -1133,6 +1133,8 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
                display_error_msgs(o);
        mark_all_ce_unused(o->src_index);
        ret = unpack_failed(o, NULL);
+       if (o->exiting_early)
+               ret = 0;
        goto done;
 }
 
index cd11a08365ab3e27b1321b3df87bcab6b9278f90..79989483079970e9dad42512e522eef8ea2a75a4 100644 (file)
@@ -46,7 +46,9 @@ struct unpack_trees_options {
                     debug_unpack,
                     skip_sparse_checkout,
                     gently,
-                    show_all_errors;
+                    exiting_early,
+                    show_all_errors,
+                    dry_run;
        const char *prefix;
        int cache_bottom;
        struct dir_struct *dir;
index 1ff47977d549992ada8c1236187d4516b648ee7d..01d3a8b81e628b0a088e3e9fc0ca59acf7d9c58d 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_]*"
@@ -267,3 +281,20 @@ struct userdiff_driver *userdiff_find_by_path(const char *path)
                return NULL;
        return userdiff_find_by_name(check.value);
 }
+
+struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver)
+{
+       if (!driver->textconv)
+               return NULL;
+
+       if (driver->textconv_want_cache && !driver->textconv_cache) {
+               struct notes_cache *c = xmalloc(sizeof(*c));
+               struct strbuf name = STRBUF_INIT;
+
+               strbuf_addf(&name, "textconv/%s", driver->name);
+               notes_cache_init(c, name.buf, driver->textconv);
+               driver->textconv_cache = c;
+       }
+
+       return driver;
+}
index 942d5949501027fb5d30e0d59629aab38fd2669a..4a7e78ffbcc6d552a39dcccd9008d6c11919e432 100644 (file)
@@ -23,4 +23,6 @@ int userdiff_config(const char *k, const char *v);
 struct userdiff_driver *userdiff_find_by_name(const char *name);
 struct userdiff_driver *userdiff_find_by_path(const char *path);
 
+struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver);
+
 #endif /* USERDIFF */
index 28290002b9f6434d716a39612f3afc9958c292af..85f09df747637b94e0488ad65984c3f97c732034 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -148,8 +148,10 @@ ssize_t read_in_full(int fd, void *buf, size_t count)
 
        while (count > 0) {
                ssize_t loaded = xread(fd, p, count);
-               if (loaded <= 0)
-                       return total ? total : loaded;
+               if (loaded < 0)
+                       return -1;
+               if (loaded == 0)
+                       return total;
                count -= loaded;
                p += loaded;
                total += loaded;
index 9f4e0ba9c17120ca2903b30b95b6d8fbcc62cd9c..02377729c401fa6e041a713a89fe986fa0f562a0 100644 (file)
@@ -642,7 +642,7 @@ static void wt_status_print_other(struct wt_status *s,
        int i;
        struct strbuf buf = STRBUF_INIT;
 
-       if (!s->untracked.nr)
+       if (!l->nr)
                return;
 
        wt_status_print_other_header(s, what, how);