diff options
78 files changed, 2413 insertions, 707 deletions
diff --git a/.gitignore b/.gitignore index 4bdb83975c..acffdfaae6 100644 --- a/.gitignore +++ b/.gitignore @@ -163,6 +163,7 @@ /gitk-git/gitk-wish /gitweb/GITWEB-BUILD-OPTIONS /gitweb/gitweb.cgi +/gitweb/static/gitweb.js /gitweb/static/gitweb.min.* /test-chmtime /test-ctype diff --git a/Documentation/RelNotes/1.7.5.3.txt b/Documentation/RelNotes/1.7.5.3.txt new file mode 100644 index 0000000000..9c03353af2 --- /dev/null +++ b/Documentation/RelNotes/1.7.5.3.txt @@ -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 index 0000000000..f3412ced33 --- /dev/null +++ b/Documentation/RelNotes/1.7.5.4.txt @@ -0,0 +1,18 @@ +Git v1.7.5.4 Release Notes +========================== + +Fixes since v1.7.5.3 +-------------------- + + * The single-key mode of "git add -p" was easily fooled into thinking + that it was told to add everthing ('a') when up-arrow was pressed by + mistake. + + * "git diff -C -C" used to disable the rename detection entirely when + there are too many copy candidate paths in the tree; now it falls + back to "-C" when doing so would keep the copy candidate paths + under the rename detection limit. + + * "git rerere" did not diagnose a corrupt MERGE_RR file in some cases. + +And other minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.6.txt b/Documentation/RelNotes/1.7.6.txt index 3d5ff4d9b2..7e1c7f1773 100644 --- a/Documentation/RelNotes/1.7.6.txt +++ b/Documentation/RelNotes/1.7.6.txt @@ -6,7 +6,8 @@ Updates since v1.7.5 * Various git-svn updates. - * Updates the way content tags are handled in gitweb. + * Updates the way content tags are handled in gitweb. Also adds + a UI to choose common timezone for displaying the dates. * Similar to branch names, tagnames that begin with "-" are now disallowed. @@ -16,6 +17,15 @@ Updates since v1.7.5 * The scripting part of the codebase is getting prepared for i18n/l10n. + * Pushing and pulling from a repository with large number of refs that + point to identical commits are optimized by not listing the same commit + during the common ancestor negotiation exchange with the other side. + + * Adding a file larger than core.bigfilethreshold (defaults to 1/2 Gig) + using "git add" will send the contents straight to a packfile without + having to hold it and its compressed representation both at the same + time in memory. + * Processes spawned by "[alias] <name> = !process" in the configuration can inspect GIT_PREFIX environment variable to learn where in the working tree the original command was invoked. @@ -66,6 +76,9 @@ Updates since v1.7.5 "--show-notes" option. Unlike "--show-notes", "--notes=<ref>" does not imply showing the default notes. + * They also learned a log.abbrevCommit configuration variable to augment + the --abbrev-commit command line option. + * "git ls-remote" learned "--exit-code" option to consider it a different kind of error when no remote ref to be shown. @@ -93,10 +106,6 @@ Updates since v1.7.5 still in a conflicted state during a merge, to avoid using information that is not final and possibly corrupt with conflict markers. - * Compressed tarball gitweb generates is made without the timestamp of - the tarball generation; snapshot from the same tree should result in - a same tarball. - Also contains various documentation updates and minor miscellaneous changes. @@ -107,45 +116,16 @@ Fixes since v1.7.5 Unless otherwise noted, all the fixes in 1.7.5.X maintenance track are included in this release. - * Setting $(prefix) in config.mak did not affect where etc/gitconfig - file is read from, even though passing it from the command line of - $(MAKE) did. - (merge kk/maint-prefix-in-config-mak later) - - * The bash completion scripts should correctly work using zsh's bash - completion emulation layer now. - (merge fc/completion-zsh later) - - * The logic to handle "&" (expand to UNIX username) in GECOS field - miscounted the length of the name it formatted. - (merge rg/copy-gecos-username later) - * The single-key mode of "git add -p" was easily fooled into thinking that it was told to add everthing ('a') when up-arrow was pressed by mistake. (merge tr/add-i-no-escape later) - * "git cherry-pick -s resolve" failed to cherry-pick a root commit. - (merge jk/cherry-pick-root-with-resolve later) - * "git config" used to choke with an insanely long line. (merge ef/maint-strbuf-init later) - * "git diff --word-diff" misbehaved when diff.suppress-blank-empty was - in effect. - (merge jm/maint-diff-words-with-sbe later) - - * "git log --stdin path" with an input that has additional pathspec - used to corrupt memory. - (merge jc/maint-pathspec-stdin-and-cmdline later) - - * "git send-pack" (hence "git push") over smalt-HTTP protocol could - deadlock when the client side pack-object died early. - (merge js/maint-send-pack-stateless-rpc-deadlock-fix later) - (merge jk/git-connection-deadlock-fix later) - --- exec >/var/tmp/1 echo O=$(git describe master) -O=v1.7.5.2-352-g4961210 +O=v1.7.5.3-365-g7eacc2b git shortlog --no-merges ^maint ^$O master diff --git a/Documentation/config.txt b/Documentation/config.txt index 1a060ecbc8..6b93777199 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1315,6 +1315,11 @@ interactive.singlekey:: setting is silently ignored if portable keystroke input is not available. +log.abbrevCommit:: + If true, makes linkgit:git-log[1], linkgit:git-show[1], and + linkgit:git-whatchanged[1] assume `\--abbrev-commit`. You may + override this option with `\--no-abbrev-commit`. + log.date:: Set the default date-time mode for the 'log' command. Setting a value for log.date is similar to using 'git log''s diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 86eb4c9368..b093e45497 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -12,7 +12,7 @@ SYNOPSIS 'git clone' [--template=<template_directory>] [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror] [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>] - [--separate-git-dir|-L <git dir>] + [--separate-git-dir <git dir>] [--depth <depth>] [--recursive|--recurse-submodules] [--] <repository> [<directory>] @@ -177,7 +177,6 @@ objects from the source repository into a pack in the cloned repository. repository does not have a worktree/checkout (i.e. if any of `--no-checkout`/`-n`, `--bare`, or `--mirror` is given) --L=<git dir>:: --separate-git-dir=<git dir>:: Instead of placing the cloned repository where it is supposed to be, place the cloned repository at the specified directory, diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index d7523b3e45..e150c77cff 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -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 diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt index 2c4c716f33..9f97f5a915 100644 --- a/Documentation/git-init-db.txt +++ b/Documentation/git-init-db.txt @@ -8,7 +8,7 @@ git-init-db - Creates an empty git repository SYNOPSIS -------- -'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--shared[=<permissions>]] +'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--separate-git-dir <git dir>] [--shared[=<permissions>]] DESCRIPTION diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt index 58cd01145a..f2777a7786 100644 --- a/Documentation/git-init.txt +++ b/Documentation/git-init.txt @@ -9,7 +9,7 @@ git-init - Create an empty git repository or reinitialize an existing one SYNOPSIS -------- 'git init' [-q | --quiet] [--bare] [--template=<template_directory>] - [--separate-git-dir|-L <git dir>] + [--separate-git-dir <git dir>] [--shared[=<permissions>]] [directory] @@ -54,7 +54,6 @@ current working directory. Specify the directory from which templates will be used. (See the "TEMPLATE DIRECTORY" section below.) --L=<git dir>:: --separate-git-dir=<git dir>:: Instead of initializing the repository where it is supposed to be, diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 913ecd8c43..6a187f2e23 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -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 diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 415f4f0b30..38fafcaa6b 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -29,6 +29,7 @@ SYNOPSIS [ \--tags[=<pattern>] ] [ \--remotes[=<pattern>] ] [ \--glob=<glob-pattern> ] + [ \--ignore-missing ] [ \--stdin ] [ \--quiet ] [ \--topo-order ] diff --git a/Documentation/git-sh-setup.txt b/Documentation/git-sh-setup.txt index 053df505bc..27fd8ba854 100644 --- a/Documentation/git-sh-setup.txt +++ b/Documentation/git-sh-setup.txt @@ -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, diff --git a/Documentation/git.txt b/Documentation/git.txt index 19d4deff70..65317cce18 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -44,9 +44,10 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.7.5.2/git.html[documentation for release 1.7.5.2] +* link:v1.7.5.3/git.html[documentation for release 1.7.5.3] * release notes for + link:RelNotes/1.7.5.3.txt[1.7.5.3], link:RelNotes/1.7.5.2.txt[1.7.5.2], link:RelNotes/1.7.5.1.txt[1.7.5.1], link:RelNotes/1.7.5.txt[1.7.5]. diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 15aebc6062..412c55b549 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -593,6 +593,37 @@ and now produces better output), you can remove the cache manually with `git update-ref -d refs/notes/textconv/jpg` (where "jpg" is the name of the diff driver, as in the example above). +Choosing textconv versus external diff +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you want to show differences between binary or specially-formatted +blobs in your repository, you can choose to use either an external diff +command, or to use textconv to convert them to a diff-able text format. +Which method you choose depends on your exact situation. + +The advantage of using an external diff command is flexibility. You are +not bound to find line-oriented changes, nor is it necessary for the +output to resemble unified diff. You are free to locate and report +changes in the most appropriate way for your data format. + +A textconv, by comparison, is much more limiting. You provide a +transformation of the data into a line-oriented text format, and git +uses its regular diff tools to generate the output. There are several +advantages to choosing this method: + +1. Ease of use. It is often much simpler to write a binary to text + transformation than it is to perform your own diff. In many cases, + existing programs can be used as textconv filters (e.g., exif, + odt2txt). + +2. Git diff features. By performing only the transformation step + yourself, you can still utilize many of git's diff features, + including colorization, word-diff, and combined diffs for merges. + +3. Caching. Textconv caching can speed up repeated diffs, such as those + you might trigger by running `git log -p`. + + Marking files as binary ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt index d5c977262a..2a3dc8664f 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -19,6 +19,11 @@ configuration (see linkgit:git-config[1]). This should make "--pretty=oneline" a whole lot more readable for people using 80-column terminals. +--no-abbrev-commit:: + Show the full 40-byte hexadecimal commit object name. This negates + `--abbrev-commit` and those options which imply it such as + "--oneline". It also overrides the 'log.abbrevCommit' variable. + --oneline:: This is a shorthand for "--pretty=oneline --abbrev-commit" used together. diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 52bae31fcb..7e7ba68781 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -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:: diff --git a/Documentation/technical/api-diff.txt b/Documentation/technical/api-diff.txt index 20b0241d30..2d2ebc04b7 100644 --- a/Documentation/technical/api-diff.txt +++ b/Documentation/technical/api-diff.txt @@ -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` @@ -24,6 +24,12 @@ all:: # Define NO_OPENSSL environment variable if you do not have OpenSSL. # This also implies BLK_SHA1. # +# Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be +# able to use Perl-compatible regular expressions. +# +# Define LIBPCREDIR=/foo/bar if your libpcre header and library files are in +# /foo/bar/include and /foo/bar/lib directories. +# # Define NO_CURL if you do not have libcurl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// # transports. @@ -70,6 +76,9 @@ all:: # Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the # FNM_CASEFOLD GNU extension. # +# Define NO_GECOS_IN_PWENT if you don't have pw_gecos in struct passwd +# in the C library. +# # Define NO_LIBGEN_H if you don't have libgen.h. # # Define NEEDS_LIBGEN if your libgen needs -lgen when linking @@ -542,6 +551,7 @@ LIB_H += rerere.h LIB_H += resolve-undo.h LIB_H += revision.h LIB_H += run-command.h +LIB_H += sha1-array.h LIB_H += sha1-lookup.h LIB_H += sideband.h LIB_H += sigchain.h @@ -644,6 +654,7 @@ LIB_OBJS += revision.o LIB_OBJS += run-command.o LIB_OBJS += server-info.o LIB_OBJS += setup.o +LIB_OBJS += sha1-array.o LIB_OBJS += sha1-lookup.o LIB_OBJS += sha1_file.o LIB_OBJS += sha1_name.o @@ -1253,6 +1264,15 @@ ifdef NO_LIBGEN_H COMPAT_OBJS += compat/basename.o endif +ifdef USE_LIBPCRE + BASIC_CFLAGS += -DUSE_LIBPCRE + ifdef LIBPCREDIR + BASIC_CFLAGS += -I$(LIBPCREDIR)/include + EXTLIBS += -L$(LIBPCREDIR)/$(lib) $(CC_LD_DYNPATH)$(LIBPCREDIR)/$(lib) + endif + EXTLIBS += -lpcre +endif + ifdef NO_CURL BASIC_CFLAGS += -DNO_CURL REMOTE_CURL_PRIMARY = @@ -2084,6 +2104,7 @@ GIT-BUILD-OPTIONS: FORCE @echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@ @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@ @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@ + @echo USE_LIBPCRE=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@ @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@ @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@ ifdef GIT_TEST_CMP @@ -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) { diff --git a/builtin/clone.c b/builtin/clone.c index 49c838fd3f..f579794d9a 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -81,7 +81,7 @@ static struct option builtin_clone_options[] = { "path to git-upload-pack on the remote"), OPT_STRING(0, "depth", &option_depth, "depth", "create a shallow clone of that depth"), - OPT_STRING('L', "separate-git-dir", &real_git_dir, "gitdir", + OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir", "separate git dir from working tree"), OPT_END() diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 85aff029b2..4367984102 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -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; } diff --git a/builtin/grep.c b/builtin/grep.c index 931eee0d75..871afaa3c7 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -753,6 +753,15 @@ int cmd_grep(int argc, const char **argv, const char *prefix) int i; int dummy; int use_index = 1; + enum { + pattern_type_unspecified = 0, + pattern_type_bre, + pattern_type_ere, + pattern_type_fixed, + pattern_type_pcre, + }; + int pattern_type = pattern_type_unspecified; + struct option options[] = { OPT_BOOLEAN(0, "cached", &cached, "search in index instead of in the work tree"), @@ -774,13 +783,18 @@ int cmd_grep(int argc, const char **argv, const char *prefix) "descend at most <depth> levels", PARSE_OPT_NONEG, NULL, 1 }, OPT_GROUP(""), - OPT_BIT('E', "extended-regexp", &opt.regflags, - "use extended POSIX regular expressions", REG_EXTENDED), - OPT_NEGBIT('G', "basic-regexp", &opt.regflags, - "use basic POSIX regular expressions (default)", - REG_EXTENDED), - OPT_BOOLEAN('F', "fixed-strings", &opt.fixed, - "interpret patterns as fixed strings"), + OPT_SET_INT('E', "extended-regexp", &pattern_type, + "use extended POSIX regular expressions", + pattern_type_ere), + OPT_SET_INT('G', "basic-regexp", &pattern_type, + "use basic POSIX regular expressions (default)", + pattern_type_bre), + OPT_SET_INT('F', "fixed-strings", &pattern_type, + "interpret patterns as fixed strings", + pattern_type_fixed), + OPT_SET_INT('P', "perl-regexp", &pattern_type, + "use Perl-compatible regular expressions", + pattern_type_pcre), OPT_GROUP(""), OPT_BOOLEAN('n', "line-number", &opt.linenum, "show line numbers"), OPT_NEGBIT('h', NULL, &opt.pathname, "don't show filenames", 1), @@ -886,6 +900,28 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_STOP_AT_NON_OPTION | PARSE_OPT_NO_INTERNAL_HELP); + switch (pattern_type) { + case pattern_type_fixed: + opt.fixed = 1; + opt.pcre = 0; + break; + case pattern_type_bre: + opt.fixed = 0; + opt.pcre = 0; + opt.regflags &= ~REG_EXTENDED; + break; + case pattern_type_ere: + opt.fixed = 0; + opt.pcre = 0; + opt.regflags |= REG_EXTENDED; + break; + case pattern_type_pcre: + opt.fixed = 0; + opt.pcre = 1; + break; + default: + break; /* nothing */ + } if (use_index && !startup_info->have_repository) /* die the same way as if we did it at the beginning */ @@ -925,8 +961,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) die(_("no pattern given.")); if (!opt.fixed && opt.ignore_case) opt.regflags |= REG_ICASE; - if ((opt.regflags != REG_NEWLINE) && opt.fixed) - die(_("cannot mix --fixed-strings and regexp")); #ifndef NO_PTHREADS if (online_cpus() == 1 || !grep_threads_ok(&opt)) diff --git a/builtin/hash-object.c b/builtin/hash-object.c index b96f46acf5..33911fd5e9 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -14,8 +14,11 @@ static void hash_fd(int fd, const char *type, int write_object, const char *path { struct stat st; unsigned char sha1[20]; + unsigned flags = (HASH_FORMAT_CHECK | + (write_object ? HASH_WRITE_OBJECT : 0)); + if (fstat(fd, &st) < 0 || - index_fd(sha1, fd, &st, write_object, type_from_string(type), path, 1)) + index_fd(sha1, fd, &st, type_from_string(type), path, flags)) die(write_object ? "Unable to add %s to database" : "Unable to hash %s", path); diff --git a/builtin/init-db.c b/builtin/init-db.c index ba13a54793..025aa47c80 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -490,7 +490,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) "specify that the git repository is to be shared amongst several users", PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0}, OPT_BIT('q', "quiet", &flags, "be quiet", INIT_DB_QUIET), - OPT_STRING('L', "separate-git-dir", &real_git_dir, "gitdir", + OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir", "separate git dir from working tree"), OPT_END() }; diff --git a/builtin/log.c b/builtin/log.c index f6219909a7..27849dc91d 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -23,6 +23,7 @@ /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; +static int default_abbrev_commit; static int default_show_root = 1; static int decoration_style; static int decoration_given; @@ -77,6 +78,7 @@ static void cmd_log_init_defaults(struct rev_info *rev) get_commit_format(fmt_pretty, rev); rev->verbose_header = 1; DIFF_OPT_SET(&rev->diffopt, RECURSIVE); + rev->abbrev_commit = default_abbrev_commit; rev->show_root_diff = default_show_root; rev->subject_prefix = fmt_patch_subject_prefix; DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV); @@ -92,7 +94,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, int quiet = 0, source = 0; const struct option builtin_log_options[] = { - OPT_BOOLEAN(0, "quiet", &quiet, "supress diff output"), + OPT_BOOLEAN(0, "quiet", &quiet, "suppress diff output"), OPT_BOOLEAN(0, "source", &source, "show source"), { OPTION_CALLBACK, 0, "decorate", NULL, NULL, "decorate options", PARSE_OPT_OPTARG, decorate_callback}, @@ -129,13 +131,16 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, if (source) rev->show_source = 1; - /* - * defeat log.decorate configuration interacting with --pretty=raw - * from the command line. - */ - if (!decoration_given && rev->pretty_given - && rev->commit_format == CMIT_FMT_RAW) - decoration_style = 0; + if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) { + /* + * "log --pretty=raw" is special; ignore UI oriented + * configuration variables such as decoration. + */ + if (!decoration_given) + decoration_style = 0; + if (!rev->abbrev_commit_given) + rev->abbrev_commit = 0; + } if (decoration_style) { rev->show_decorations = 1; @@ -323,6 +328,10 @@ static int git_log_config(const char *var, const char *value, void *cb) return git_config_string(&fmt_pretty, var, value); if (!strcmp(var, "format.subjectprefix")) return git_config_string(&fmt_patch_subject_prefix, var, value); + if (!strcmp(var, "log.abbrevcommit")) { + default_abbrev_commit = git_config_bool(var, value); + return 0; + } if (!strcmp(var, "log.date")) return git_config_string(&default_date_mode, var, value); if (!strcmp(var, "log.decorate")) { @@ -516,11 +525,11 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) init_revisions(&rev, prefix); init_reflog_walk(&rev.reflog_info); - rev.abbrev_commit = 1; rev.verbose_header = 1; memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; cmd_log_init_defaults(&rev); + rev.abbrev_commit = 1; rev.commit_format = CMIT_FMT_ONELINE; rev.use_terminator = 1; rev.always_show_header = 1; diff --git a/builtin/notes.c b/builtin/notes.c index 1fb1f73439..f8e437db01 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -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; } diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index e1ba4dc697..e1a687ad07 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -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) diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index adb1cae4f2..4c19f844a9 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -44,6 +44,7 @@ static int is_rev_argument(const char *arg) "--branches=", "--branches", "--header", + "--ignore-missing", "--max-age=", "--max-count=", "--min-age=", diff --git a/builtin/update-index.c b/builtin/update-index.c index d7850c6309..f14bc90830 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -99,7 +99,8 @@ static int add_one_path(struct cache_entry *old, const char *path, int len, stru fill_stat_cache_info(ce, st); ce->ce_mode = ce_mode_from_stat(old, st->st_mode); - if (index_path(ce->sha1, path, st, !info_only)) + if (index_path(ce->sha1, path, st, + info_only ? 0 : HASH_WRITE_OBJECT)) return -1; option = allow_add ? ADD_CACHE_OK_TO_ADD : 0; option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0; @@ -518,8 +518,11 @@ struct pathspec { extern int init_pathspec(struct pathspec *, const char **); extern void free_pathspec(struct pathspec *); extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec); -extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path, int format_check); -extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object); + +#define HASH_WRITE_OBJECT 1 +#define HASH_FORMAT_CHECK 2 +extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags); +extern int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags); extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); #define REFRESH_REALLY 0x0001 /* ignore_valid */ @@ -1037,8 +1040,6 @@ typedef int (*config_fn_t)(const char *, const char *, void *); extern int git_default_config(const char *, const char *, void *); extern int git_config_from_file(config_fn_t fn, const char *, void *); extern void git_config_push_parameter(const char *text); -extern int git_config_parse_parameter(const char *text); -extern int git_config_parse_environment(void); extern int git_config_from_parameters(config_fn_t fn, void *data); extern int git_config(config_fn_t fn, void *); extern int git_config_early(config_fn_t fn, void *, const char *repo_config); diff --git a/compat/fnmatch/fnmatch.c b/compat/fnmatch/fnmatch.c index 14feac7fe1..9473aed2bb 100644 --- a/compat/fnmatch/fnmatch.c +++ b/compat/fnmatch/fnmatch.c @@ -127,6 +127,10 @@ extern char *getenv (); extern int errno; # endif +# ifndef NULL +# define NULL 0 +# endif + /* This function doesn't exist on most systems. */ # if !defined HAVE___STRCHRNUL && !defined _LIBC @@ -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; } diff --git a/config.mak.in b/config.mak.in index e378534cbd..f30130b17a 100644 --- a/config.mak.in +++ b/config.mak.in @@ -61,6 +61,7 @@ NO_INET_PTON=@NO_INET_PTON@ NO_ICONV=@NO_ICONV@ OLD_ICONV=@OLD_ICONV@ NO_REGEX=@NO_REGEX@ +USE_LIBPCRE=@USE_LIBPCRE@ NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@ INLINE=@INLINE@ SOCKLEN_T=@SOCKLEN_T@ diff --git a/configure.ac b/configure.ac index fafd81557c..048a1d4972 100644 --- a/configure.ac +++ b/configure.ac @@ -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. @@ -435,6 +456,25 @@ 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:// # transports. diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index bb8d7d0878..b36290fa60 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -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 @@ -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); diff --git a/git-sh-setup.sh b/git-sh-setup.sh index aa16b83565..94e26ed5e8 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -140,6 +140,13 @@ cd_to_toplevel () { } } +require_work_tree_exists () { + if test "z$(git rev-parse --is-bare-repository)" != zfalse + then + die "fatal: $0 cannot be used without a working tree." + fi +} + require_work_tree () { test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true || die "fatal: $0 cannot be used without a working tree." diff --git a/git-submodule.sh b/git-submodule.sh index bf110e9cb7..d189a24c71 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -495,7 +495,7 @@ cmd_update() # Run fetch only if $sha1 isn't present or it # is not reachable from a ref. (clear_local_git_env; cd "$path" && - ((rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) && + ( (rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) && test -z "$rev") || git-fetch)) || die "Unable to fetch in submodule path '$path'" fi @@ -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) diff --git a/gitweb/Makefile b/gitweb/Makefile index 0a6ac00631..5d20515fba 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -86,7 +86,7 @@ ifndef V endif endif -all:: gitweb.cgi +all:: gitweb.cgi static/gitweb.js GITWEB_PROGRAMS = gitweb.cgi @@ -112,6 +112,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: diff --git a/gitweb/README b/gitweb/README index a92bde7f14..a3a697bf55 100644 --- a/gitweb/README +++ b/gitweb/README @@ -207,6 +207,15 @@ not include variables usually directly set during build): full description is available as 'title' attribute (usually shown on mouseover). By default set to 25, which might be too small if you use long project descriptions. + * $projects_list_group_categories + Enables the grouping of projects by category on the project list page. + The category of a project is determined by the $GIT_DIR/category + file or the 'gitweb.category' variable in its repository configuration. + Disabled by default. + * $project_list_default_category + Default category for projects for which none is specified. If set + to the empty string, such projects will remain uncategorized and + listed at the top, above categorized projects. * @git_base_url_list List of git base URLs used for URL to where fetch project from, shown in project summary page. Full URL is "$git_base_url/$project". @@ -314,6 +323,13 @@ You can use the following files in repository: from the template during repository creation. You can use the gitweb.description repo configuration variable, but the file takes precedence. + * category (or gitweb.category) + Singe line category of a project, used to group projects if + $projects_list_group_categories is enabled. By default (file and + configuration variable absent), uncategorized projects are put in + the $project_list_default_category category. You can use the + gitweb.category repo configuration variable, but the file takes + precedence. * cloneurl (or multiple-valued gitweb.url) File with repository URL (used for clone and fetch), one per line. Displayed in the project summary page. You can use multiple-valued diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 66eadb4ab2..240dd4701c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -115,6 +115,14 @@ our $projects_list = "++GITWEB_LIST++"; # the width (in characters) of the projects list "Description" column our $projects_list_description_width = 25; +# group projects by category on the projects list +# (enabled if this variable evaluates to true) +our $projects_list_group_categories = 0; + +# default category if none specified +# (leave the empty string for no category) +our $project_list_default_category = ""; + # default order of projects list # valid values are none, project, descr, owner, and age our $default_projects_order = "project"; @@ -483,6 +491,18 @@ our %feature = ( 'override' => 0, 'default' => [0]}, + # Enable and configure ability to change common timezone for dates + # in gitweb output via JavaScript. Enabled by default. + # Project specific override is not supported. + 'javascript-timezone' => { + 'override' => 0, + 'default' => [ + 'local', # default timezone: 'utc', 'local', or '(-|+)HHMM' format, + # or undef to turn off this feature + 'gitweb_tz', # name of cookie where to store selected timezone + 'datetime', # CSS class used to mark up dates for manipulation + ]}, + # Syntax highlighting support. This is based on Daniel Svensson's # and Sham Chukoury's work in gitweb-xmms2.git. # It requires the 'highlight' program present in $PATH, @@ -623,18 +643,30 @@ sub filter_snapshot_fmts { # if it is true then gitweb config would be run for each request. our $per_request_config = 1; +# read and parse gitweb config file given by its parameter. +# returns true on success, false on recoverable error, allowing +# to chain this subroutine, using first file that exists. +# dies on errors during parsing config file, as it is unrecoverable. +sub read_config_file { + my $filename = shift; + return unless defined $filename; + # die if there are errors parsing config file + if (-e $filename) { + do $filename; + die $@ if $@; + return 1; + } + return; +} + our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM); sub evaluate_gitweb_config { our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++"; - # die if there are errors parsing config file - if (-e $GITWEB_CONFIG) { - do $GITWEB_CONFIG; - die $@ if $@; - } elsif (-e $GITWEB_CONFIG_SYSTEM) { - do $GITWEB_CONFIG_SYSTEM; - die $@ if $@; - } + + # use first config file that exists + read_config_file($GITWEB_CONFIG) or + read_config_file($GITWEB_CONFIG_SYSTEM); } # Get loadavg of system, to compare against $maxload. @@ -2562,20 +2594,34 @@ sub git_get_path_by_hash { ## ...................................................................... ## git utility functions, directly accessing git repository -sub git_get_project_description { - my $path = shift; +# get the value of config variable either from file named as the variable +# itself in the repository ($GIT_DIR/$name file), or from gitweb.$name +# configuration variable in the repository config file. +sub git_get_file_or_project_config { + my ($path, $name) = @_; $git_dir = "$projectroot/$path"; - open my $fd, '<', "$git_dir/description" - or return git_get_project_config('description'); - my $descr = <$fd>; + open my $fd, '<', "$git_dir/$name" + or return git_get_project_config($name); + my $conf = <$fd>; close $fd; - if (defined $descr) { - chomp $descr; + if (defined $conf) { + chomp $conf; } - return $descr; + return $conf; +} + +sub git_get_project_description { + my $path = shift; + return git_get_file_or_project_config($path, 'description'); +} + +sub git_get_project_category { + my $path = shift; + return git_get_file_or_project_config($path, 'category'); } + # supported formats: # * $GIT_DIR/ctags/<tagname> file (in 'ctags' subdirectory) # - if its contents is a number, use it as tag weight, @@ -3863,9 +3909,20 @@ sub git_footer_html { qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!. qq! "!. href() .qq!");\n!. qq!</script>\n!; - } elsif (gitweb_check_feature('javascript-actions')) { + } else { + my ($jstimezone, $tz_cookie, $datetime_class) = + gitweb_get_feature('javascript-timezone'); + print qq!<script type="text/javascript">\n!. - qq!window.onload = fixLinks;\n!. + qq!window.onload = function () {\n!; + if (gitweb_check_feature('javascript-actions')) { + print qq! fixLinks();\n!; + } + if ($jstimezone && $tz_cookie && $datetime_class) { + print qq! var tz_cookie = { name: '$tz_cookie', expires: 14, path: '/' };\n!. # in days + qq! onloadTZSetup('$jstimezone', tz_cookie, '$datetime_class');\n!; + } + print qq!};\n!. qq!</script>\n!; } @@ -4069,22 +4126,25 @@ sub git_print_section { print $cgi->end_div; } -sub print_local_time { - print format_local_time(@_); -} +sub format_timestamp_html { + my $date = shift; + my $strtime = $date->{'rfc2822'}; -sub format_local_time { - my $localtime = ''; - my %date = @_; - if ($date{'hour_local'} < 6) { - $localtime .= sprintf(" (<span class=\"atnight\">%02d:%02d</span> %s)", - $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'}); - } else { - $localtime .= sprintf(" (%02d:%02d %s)", - $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'}); + my (undef, undef, $datetime_class) = + gitweb_get_feature('javascript-timezone'); + if ($datetime_class) { + $strtime = qq!<span class="$datetime_class">$strtime</span>!; + } + + my $localtime_format = '(%02d:%02d %s)'; + if ($date->{'hour_local'} < 6) { + $localtime_format = '(<span class="atnight">%02d:%02d</span> %s)'; } + $strtime .= ' ' . + sprintf($localtime_format, + $date->{'hour_local'}, $date->{'minute_local'}, $date->{'tz_local'}); - return $localtime; + return $strtime; } # Outputs the author name and date in long form @@ -4097,10 +4157,9 @@ sub git_print_authorship { my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'}); print "<$tag class=\"author_date\">" . format_search_author($author, "author", esc_html($author)) . - " [$ad{'rfc2822'}"; - print_local_time(%ad) if ($opts{-localtime}); - print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1) - . "</$tag>\n"; + " [".format_timestamp_html(\%ad)."]". + git_get_avatar($co->{'author_email'}, -pad_before => 1) . + "</$tag>\n"; } # Outputs table rows containing the full author or committer information, @@ -4117,16 +4176,16 @@ sub git_print_authorship_rows { my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"}); print "<tr><td>$who</td><td>" . format_search_author($co->{"${who}_name"}, $who, - esc_html($co->{"${who}_name"})) . " " . + esc_html($co->{"${who}_name"})) . " " . format_search_author($co->{"${who}_email"}, $who, - esc_html("<" . $co->{"${who}_email"} . ">")) . + esc_html("<" . $co->{"${who}_email"} . ">")) . "</td><td rowspan=\"2\">" . git_get_avatar($co->{"${who}_email"}, -size => 'double') . "</td></tr>\n" . "<tr>" . - "<td></td><td> $wd{'rfc2822'}"; - print_local_time(%wd); - print "</td>" . + "<td></td><td>" . + format_timestamp_html(\%wd) . + "</td>" . "</tr>\n"; } } @@ -4869,8 +4928,9 @@ sub git_patchset_body { # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -# fills project list info (age, description, owner, forks) for each -# project in the list, removing invalid projects from returned list +# fills project list info (age, description, owner, category, forks) +# for each project in the list, removing invalid projects from +# returned list # NOTE: modifies $projlist, but does not remove entries from it sub fill_project_list_info { my $projlist = shift; @@ -4896,6 +4956,12 @@ sub fill_project_list_info { if ($show_ctags) { $pr->{'ctags'} = git_get_project_ctags($pr->{'path'}); } + if ($projects_list_group_categories && !defined $pr->{'category'}) { + my $cat = git_get_project_category($pr->{'path'}) || + $project_list_default_category; + $pr->{'category'} = to_utf8($cat); + } + push @projects, $pr; } @@ -4923,6 +4989,23 @@ sub sort_projects_list { return @projects; } +# returns a hash of categories, containing the list of project +# belonging to each category +sub build_projlist_by_category { + my ($projlist, $from, $to) = @_; + my %categories; + + $from = 0 unless defined $from; + $to = $#$projlist if (!defined $to || $#$projlist < $to); + + for (my $i = $from; $i <= $to; $i++) { + my $pr = $projlist->[$i]; + push @{$categories{ $pr->{'category'} }}, $pr; + } + + return wantarray ? %categories : \%categories; +} + # print 'sort by' <th> element, generating 'sort by $name' replay link # if that order is not selected sub print_sort_th { @@ -4946,6 +5029,55 @@ sub format_sort_th { return $sort_th; } +sub git_project_list_rows { + my ($projlist, $from, $to, $check_forks) = @_; + + $from = 0 unless defined $from; + $to = $#$projlist if (!defined $to || $#$projlist < $to); + + my $alternate = 1; + for (my $i = $from; $i <= $to; $i++) { + my $pr = $projlist->[$i]; + + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + + if ($check_forks) { + print "<td>"; + if ($pr->{'forks'}) { + my $nforks = scalar @{$pr->{'forks'}}; + if ($nforks > 0) { + print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks"), + -title => "$nforks forks"}, "+"); + } else { + print $cgi->span({-title => "$nforks forks"}, "+"); + } + } + print "</td>\n"; + } + print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), + -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" . + "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), + -class => "list", -title => $pr->{'descr_long'}}, + esc_html($pr->{'descr'})) . "</td>\n" . + "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n"; + print "<td class=\"". age_class($pr->{'age'}) . "\">" . + (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") . + ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') . + "</td>\n" . + "</tr>\n"; + } +} + sub git_project_list_body { # actually uses global variable $project my ($projlist, $order, $from, $to, $extra, $no_header) = @_; @@ -5001,47 +5133,27 @@ sub git_project_list_body { print "<th></th>\n" . # for links "</tr>\n"; } - my $alternate = 1; - for (my $i = $from; $i <= $to; $i++) { - my $pr = $projects[$i]; - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - - if ($check_forks) { - print "<td>"; - if ($pr->{'forks'}) { - my $nforks = scalar @{$pr->{'forks'}}; - if ($nforks > 0) { - print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks"), - -title => "$nforks forks"}, "+"); - } else { - print $cgi->span({-title => "$nforks forks"}, "+"); + if ($projects_list_group_categories) { + # only display categories with projects in the $from-$to window + @projects = sort {$a->{'category'} cmp $b->{'category'}} @projects[$from..$to]; + my %categories = build_projlist_by_category(\@projects, $from, $to); + foreach my $cat (sort keys %categories) { + unless ($cat eq "") { + print "<tr>\n"; + if ($check_forks) { + print "<td></td>\n"; } + print "<td class=\"category\" colspan=\"5\">".esc_html($cat)."</td>\n"; + print "</tr>\n"; } - print "</td>\n"; + + git_project_list_rows($categories{$cat}, undef, undef, $check_forks); } - print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), - -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" . - "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), - -class => "list", -title => $pr->{'descr_long'}}, - esc_html($pr->{'descr'})) . "</td>\n" . - "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n"; - print "<td class=\"". age_class($pr->{'age'}) . "\">" . - (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") . - ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') . - "</td>\n" . - "</tr>\n"; + } else { + git_project_list_rows(\@projects, $from, $to, $check_forks); } + if (defined $extra) { print "<tr>\n"; if ($check_forks) { @@ -5561,7 +5673,8 @@ sub git_summary { "<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" . "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n"; if (defined $cd{'rfc2822'}) { - print "<tr id=\"metadata_lchange\"><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n"; + print "<tr id=\"metadata_lchange\"><td>last change</td>" . + "<td>".format_timestamp_html(\%cd)."</td></tr>\n"; } # use per project git URL list in $projectroot/$project/cloneurl diff --git a/gitweb/static/gitweb.css b/gitweb/static/gitweb.css index 79d7eebba7..7d88509208 100644 --- a/gitweb/static/gitweb.css +++ b/gitweb/static/gitweb.css @@ -295,6 +295,13 @@ td.current_head { text-decoration: underline; } +td.category { + background-color: #d9d8d1; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + font-weight: bold; +} + table.diff_tree span.file_status.new { color: #008000; } @@ -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/js/README b/gitweb/static/js/README new file mode 100644 index 0000000000..f8460ed32f --- /dev/null +++ b/gitweb/static/js/README @@ -0,0 +1,20 @@ +GIT web interface (gitweb) - JavaScript +======================================= + +This directory holds JavaScript code used by gitweb (GIT web interface). +Scripts from there would be concatenated together in the order specified +by gitweb/Makefile into gitweb/static/gitweb.js, during building of +gitweb/gitweb.cgi (during gitweb building). The resulting file (or its +minification) would then be installed / deployed together with gitweb. + +Scripts in 'lib/' subdirectory compose generic JavaScript library, +providing features required by gitweb but in no way limited to gitweb +only. In the future those scripts could be replaced by some JavaScript +library / framework, like e.g. jQuery, YUI, Prototype, MooTools, Dojo, +ExtJS, Script.aculo.us or SproutCore. + +All scripts that manipulate gitweb output should be put outside 'lib/', +directly in this directory ('gitweb/static/js/'). Those scripts would +have to be rewritten if gitweb moves to using some JavaScript library. + +See also comments in gitweb/Makefile. diff --git a/gitweb/static/js/adjust-timezone.js b/gitweb/static/js/adjust-timezone.js new file mode 100644 index 0000000000..0c67779500 --- /dev/null +++ b/gitweb/static/js/adjust-timezone.js @@ -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±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/gitweb.js b/gitweb/static/js/blame_incremental.js index 40ec08440b..676da6b590 100644 --- a/gitweb/static/gitweb.js +++ b/gitweb/static/js/blame_incremental.js @@ -1,45 +1,13 @@ // Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com> // 2007, Petr Baudis <pasky@suse.cz> -// 2008-2009, Jakub Narebski <jnareb@gmail.com> +// 2008-2011, Jakub Narebski <jnareb@gmail.com> /** - * @fileOverview JavaScript code for gitweb (git web interface). + * @fileOverview JavaScript side of Ajax-y 'blame_incremental' view in gitweb * @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. @@ -58,72 +26,7 @@ function fixLinks() { */ -/* ============================================================ */ -/* 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 @@ -229,7 +132,7 @@ function writeTimeInterval() { } /** - * show an error message alert to user within page (in prohress info area) + * show an error message alert to user within page (in progress info area) * @param {String} str: plain text error message (no HTML) * * @globals div_progress_info @@ -279,7 +182,7 @@ function getColorNo(tr) { var colorsFreq = [0, 0, 0]; /** - * return one of given possible colors (curently least used one) + * return one of given possible colors (currently least used one) * example: chooseColorNoFrom(2, 3) returns 2 or 3 * * @param {Number[]} arguments: one or more numbers @@ -300,8 +203,8 @@ function chooseColorNoFrom() { } /** - * given two neigbour <tr> elements, find color which would be different - * from color of both of neighbours; used to 3-color blame table + * 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 @@ -313,14 +216,14 @@ function findColorNo(tr_prev, tr_next) { var color_next = getColorNo(tr_next); - // neither of neighbours has color set + // neither of neighbors has color set // THEN we can use any of 3 possible colors if (!color_prev && !color_next) { return chooseColorNoFrom(1,2,3); } - // either both neighbours have the same color, - // or only one of neighbours have color set + // either both neighbors have the same color, + // or only one of neighbors have color set // THEN we can use any color except given var color; if (color_prev === color_next) { @@ -334,7 +237,7 @@ function findColorNo(tr_prev, tr_next) { return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1); } - // neighbours have different colors + // neighbors have different colors // THEN there is only one color left return (3 - ((color_prev + color_next) % 3)); } @@ -355,7 +258,7 @@ function isStartOfGroup(tr) { /** * change colors to use zebra coloring (2 colors) instead of 3 colors - * concatenate neighbour commit groups belonging to the same commit + * concatenate neighbor commit groups belonging to the same commit * * @globals colorRe */ @@ -392,111 +295,6 @@ function fixColorsAndGroups() { } } -/* ............................................................ */ -/* 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 */ @@ -886,4 +684,4 @@ function startBlame(blamedataUrl, bUrl) { pollTimer = setInterval(xhr.onreadystatechange, 1000); } -// end of gitweb.js +/* end of blame_incremental.js */ diff --git a/gitweb/static/js/javascript-detection.js b/gitweb/static/js/javascript-detection.js new file mode 100644 index 0000000000..93dd2bdd91 --- /dev/null +++ b/gitweb/static/js/javascript-detection.js @@ -0,0 +1,43 @@ +// Copyright (C) 2007, Fredrik Kuivinen <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 index 0000000000..018bbb7d4c --- /dev/null +++ b/gitweb/static/js/lib/common-lib.js @@ -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 ' ' 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 index 0000000000..72b51cd1b4 --- /dev/null +++ b/gitweb/static/js/lib/cookies.js @@ -0,0 +1,114 @@ +/** + * @fileOverview Accessing cookies from JavaScript + * @license GPLv2 or later + */ + +/* + * Based on subsection "Cookies in JavaScript" of "Professional + * JavaScript for Web Developers" by Nicholas C. Zakas and cookie + * plugin from jQuery (dual licensed under the MIT and GPL licenses) + */ + + +/** + * Create a cookie with the given name and value, + * and other optional parameters. + * + * @example + * setCookie('foo', 'bar'); // will be deleted when browser exits + * setCookie('foo', 'bar', { expires: new Date(Date.parse('Jan 1, 2012')) }); + * setCookie('foo', 'bar', { expires: 7 }); // 7 days = 1 week + * setCookie('foo', 'bar', { expires: 14, path: '/' }); + * + * @param {String} sName: Unique name of a cookie (letters, numbers, underscores). + * @param {String} sValue: The string value stored in a cookie. + * @param {Object} [options] An object literal containing key/value pairs + * to provide optional cookie attributes. + * @param {String|Number|Date} [options.expires] Either literal string to be used as cookie expires, + * or an integer specifying the expiration date from now on in days, + * or a Date object to be used as cookie expiration date. + * If a negative value is specified or a date in the past), + * the cookie will be deleted. + * If set to null or omitted, the cookie will be a session cookie + * and will not be retained when the the browser exits. + * @param {String} [options.path] Restrict access of a cookie to particular directory + * (default: path of page that created the cookie). + * @param {String} [options.domain] Override what web sites are allowed to access cookie + * (default: domain of page that created the cookie). + * @param {Boolean} [options.secure] If true, the secure attribute of the cookie will be set + * and the cookie would be accessible only from secure sites + * (cookie transmission will require secure protocol like HTTPS). + */ +function setCookie(sName, sValue, options) { + options = options || {}; + if (sValue === null) { + sValue = ''; + option.expires = 'delete'; + } + + var sCookie = sName + '=' + encodeURIComponent(sValue); + + if (options.expires) { + var oExpires = options.expires, sDate; + if (oExpires === 'delete') { + sDate = 'Thu, 01 Jan 1970 00:00:00 GMT'; + } else if (typeof oExpires === 'string') { + sDate = oExpires; + } else { + var oDate; + if (typeof oExpires === 'number') { + oDate = new Date(); + oDate.setTime(oDate.getTime() + (oExpires * 24 * 60 * 60 * 1000)); // days to ms + } else { + oDate = oExpires; + } + sDate = oDate.toGMTString(); + } + sCookie += '; expires=' + sDate; + } + + if (options.path) { + sCookie += '; path=' + (options.path); + } + if (options.domain) { + sCookie += '; domain=' + (options.domain); + } + if (options.secure) { + sCookie += '; secure'; + } + document.cookie = sCookie; +} + +/** + * Get the value of a cookie with the given name. + * + * @param {String} sName: Unique name of a cookie (letters, numbers, underscores) + * @returns {String|null} The string value stored in a cookie + */ +function getCookie(sName) { + var sRE = '(?:; )?' + sName + '=([^;]*);?'; + var oRE = new RegExp(sRE); + if (oRE.test(document.cookie)) { + return decodeURIComponent(RegExp['$1']); + } else { + return null; + } +} + +/** + * Delete cookie with given name + * + * @param {String} sName: Unique name of a cookie (letters, numbers, underscores) + * @param {Object} [options] An object literal containing key/value pairs + * to provide optional cookie attributes. + * @param {String} [options.path] Must be the same as when setting a cookie + * @param {String} [options.domain] Must be the same as when setting a cookie + */ +function deleteCookie(sName, options) { + options = options || {}; + options.expires = 'delete'; + + setCookie(sName, '', options); +} + +/* end of cookies.js */ diff --git a/gitweb/static/js/lib/datetime.js b/gitweb/static/js/lib/datetime.js new file mode 100644 index 0000000000..f78c60a912 --- /dev/null +++ b/gitweb/static/js/lib/datetime.js @@ -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 */ @@ -59,6 +59,84 @@ struct grep_opt *grep_opt_dup(const struct grep_opt *opt) return ret; } +static NORETURN void compile_regexp_failed(const struct grep_pat *p, + const char *error) +{ + char where[1024]; + + if (p->no) + sprintf(where, "In '%s' at %d, ", p->origin, p->no); + else if (p->origin) + sprintf(where, "%s, ", p->origin); + else + where[0] = 0; + + die("%s'%s': %s", where, p->pattern, error); +} + +#ifdef USE_LIBPCRE +static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt) +{ + const char *error; + int erroffset; + int options = 0; + + if (opt->ignore_case) + options |= PCRE_CASELESS; + + p->pcre_regexp = pcre_compile(p->pattern, options, &error, &erroffset, + NULL); + if (!p->pcre_regexp) + compile_regexp_failed(p, error); + + p->pcre_extra_info = pcre_study(p->pcre_regexp, 0, &error); + if (!p->pcre_extra_info && error) + die("%s", error); +} + +static int pcrematch(struct grep_pat *p, const char *line, const char *eol, + regmatch_t *match, int eflags) +{ + int ovector[30], ret, flags = 0; + + if (eflags & REG_NOTBOL) + flags |= PCRE_NOTBOL; + + ret = pcre_exec(p->pcre_regexp, p->pcre_extra_info, line, eol - line, + 0, flags, ovector, ARRAY_SIZE(ovector)); + if (ret < 0 && ret != PCRE_ERROR_NOMATCH) + die("pcre_exec failed with error code %d", ret); + if (ret > 0) { + ret = 0; + match->rm_so = ovector[0]; + match->rm_eo = ovector[1]; + } + + return ret; +} + +static void free_pcre_regexp(struct grep_pat *p) +{ + pcre_free(p->pcre_regexp); + pcre_free(p->pcre_extra_info); +} +#else /* !USE_LIBPCRE */ +static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt) +{ + die("cannot use Perl-compatible regexes when not compiled with USE_LIBPCRE"); +} + +static int pcrematch(struct grep_pat *p, const char *line, const char *eol, + regmatch_t *match, int eflags) +{ + return 1; +} + +static void free_pcre_regexp(struct grep_pat *p) +{ +} +#endif /* !USE_LIBPCRE */ + static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) { int err; @@ -70,20 +148,17 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) if (p->fixed) return; + if (opt->pcre) { + compile_pcre_regexp(p, opt); + return; + } + err = regcomp(&p->regexp, p->pattern, opt->regflags); if (err) { char errbuf[1024]; - char where[1024]; - if (p->no) - sprintf(where, "In '%s' at %d, ", - p->origin, p->no); - else if (p->origin) - sprintf(where, "%s, ", p->origin); - else - where[0] = 0; regerror(err, &p->regexp, errbuf, 1024); regfree(&p->regexp); - die("%s'%s': %s", where, p->pattern, errbuf); + compile_regexp_failed(p, errbuf); } } @@ -320,7 +395,10 @@ void free_grep_patterns(struct grep_opt *opt) case GREP_PATTERN: /* atom */ case GREP_PATTERN_HEAD: case GREP_PATTERN_BODY: - regfree(&p->regexp); + if (p->pcre_regexp) + free_pcre_regexp(p); + else + regfree(&p->regexp); break; default: break; @@ -412,6 +490,21 @@ static int regmatch(const regex_t *preg, char *line, char *eol, return regexec(preg, line, 1, match, eflags); } +static int patmatch(struct grep_pat *p, char *line, char *eol, + regmatch_t *match, int eflags) +{ + int hit; + + if (p->fixed) + hit = !fixmatch(p, line, eol, match); + else if (p->pcre_regexp) + hit = !pcrematch(p, line, eol, match, eflags); + else + hit = !regmatch(&p->regexp, line, eol, match, eflags); + + return hit; +} + static int strip_timestamp(char *bol, char **eol_p) { char *eol = *eol_p; @@ -461,10 +554,7 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol, } again: - if (p->fixed) - hit = !fixmatch(p, bol, eol, pmatch); - else - hit = !regmatch(&p->regexp, bol, eol, pmatch, eflags); + hit = patmatch(p, bol, eol, pmatch, eflags); if (hit && p->word_regexp) { if ((pmatch[0].rm_so < 0) || @@ -791,10 +881,7 @@ static int look_ahead(struct grep_opt *opt, int hit; regmatch_t m; - if (p->fixed) - hit = !fixmatch(p, bol, bol + *left_p, &m); - else - hit = !regmatch(&p->regexp, bol, bol + *left_p, &m, 0); + hit = patmatch(p, bol, bol + *left_p, &m, 0); if (!hit || m.rm_so < 0 || m.rm_eo < 0) continue; if (earliest < 0 || m.rm_so < earliest) @@ -891,7 +978,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name, int hit; /* - * look_ahead() skips quicly to the line that possibly + * look_ahead() skips quickly to the line that possibly * has the next hit; don't call it if we need to do * something more than just skipping the current line * in response to an unmatch for the current line. E.g. @@ -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; @@ -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; diff --git a/notes-merge.c b/notes-merge.c index 28046a9984..e1aaf43b43 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -707,7 +707,7 @@ int notes_merge_commit(struct notes_merge_options *o, /* write file as blob, and add to partial_tree */ if (stat(ent->name, &st)) die_errno("Failed to stat '%s'", ent->name); - if (index_path(blob_sha1, ent->name, &st, 1)) + if (index_path(blob_sha1, ent->name, &st, HASH_WRITE_OBJECT)) die("Failed to write blob object from '%s'", ent->name); if (add_note(partial_tree, obj_sha1, blob_sha1, NULL)) die("Failed to add resolved note '%s' to notes tree", @@ -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); } diff --git a/read-cache.c b/read-cache.c index f38471cac3..4ac9a037f4 100644 --- a/read-cache.c +++ b/read-cache.c @@ -92,7 +92,7 @@ static int ce_compare_data(struct cache_entry *ce, struct stat *st) if (fd >= 0) { unsigned char sha1[20]; - if (!index_fd(sha1, fd, st, 0, OBJ_BLOB, ce->name, 0)) + if (!index_fd(sha1, fd, st, OBJ_BLOB, ce->name, 0)) match = hashcmp(sha1, ce->sha1); /* index_fd() closed the file descriptor already */ } @@ -641,7 +641,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, return 0; } if (!intent_only) { - if (index_path(ce->sha1, path, st, 1)) + if (index_path(ce->sha1, path, st, HASH_WRITE_OBJECT)) return error("unable to index file %s", path); } else record_intent_to_add(ce); @@ -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); diff --git a/revision.c b/revision.c index 7934b2f68b..c46cfaa3e4 100644 --- a/revision.c +++ b/revision.c @@ -133,6 +133,8 @@ void mark_parents_uninteresting(struct commit *commit) static void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode) { + if (!obj) + return; if (revs->no_walk && (obj->flags & UNINTERESTING)) revs->no_walk = 0; if (revs->reflog_info && obj->type == OBJ_COMMIT) { @@ -174,8 +176,11 @@ static struct object *get_reference(struct rev_info *revs, const char *name, con struct object *object; object = parse_object(sha1); - if (!object) + if (!object) { + if (revs->ignore_missing) + return object; die("bad object %s", name); + } object->flags |= flags; return object; } @@ -906,6 +911,8 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags) return 0; while (1) { it = get_reference(revs, arg, sha1, 0); + if (!it && revs->ignore_missing) + return 0; if (it->type != OBJ_TAG) break; if (!((struct tag*)it)->tagged) @@ -1044,6 +1051,8 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, a = lookup_commit_reference(from_sha1); b = lookup_commit_reference(sha1); if (!a || !b) { + if (revs->ignore_missing) + return 0; die(symmetric ? "Invalid symmetric difference expression %s...%s" : "Invalid revision range %s..%s", @@ -1090,7 +1099,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, arg++; } if (get_sha1_with_mode(arg, sha1, &mode)) - return -1; + return revs->ignore_missing ? 0 : -1; if (!cant_be_filename) verify_non_filename(revs->prefix, arg); object = get_reference(revs, arg, sha1, flags ^ local_flags); @@ -1428,6 +1437,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->abbrev = 40; } else if (!strcmp(arg, "--abbrev-commit")) { revs->abbrev_commit = 1; + revs->abbrev_commit_given = 1; + } else if (!strcmp(arg, "--no-abbrev-commit")) { + revs->abbrev_commit = 0; } else if (!strcmp(arg, "--full-diff")) { revs->diff = 1; revs->full_diff = 1; @@ -1474,6 +1486,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--children")) { revs->children.name = "children"; revs->limited = 1; + } else if (!strcmp(arg, "--ignore-missing")) { + revs->ignore_missing = 1; } else { int opts = diff_opt_parse(&revs->diffopt, argv, argc); if (!opts) diff --git a/revision.h b/revision.h index bca9947977..4499cebded 100644 --- a/revision.h +++ b/revision.h @@ -36,7 +36,8 @@ struct rev_info { const char *prefix; const char *def; struct pathspec prune_data; - unsigned int early_output; + unsigned int early_output:1, + ignore_missing:1; /* Traversal flags */ unsigned int dense:1, @@ -90,6 +91,7 @@ struct rev_info { show_notes_given:1, pretty_given:1, abbrev_commit:1, + abbrev_commit_given:1, use_terminator:1, missing_newline:1, date_mode_explicit:1; @@ -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; diff --git a/sh-i18n--envsubst.c b/sh-i18n--envsubst.c index 71250930db..2eb0ee44b8 100644 --- a/sh-i18n--envsubst.c +++ b/sh-i18n--envsubst.c @@ -68,7 +68,7 @@ int main (int argc, char *argv[]) { /* Default values for command line options. */ - unsigned short int show_variables = 0; + /* unsigned short int show_variables = 0; */ switch (argc) { @@ -88,7 +88,7 @@ main (int argc, char *argv[]) /* git sh-i18n--envsubst --variables '$foo and $bar' */ if (strcmp(argv[1], "--variables")) error ("first argument must be --variables when two are given"); - show_variables = 1; + /* show_variables = 1; */ print_variables (argv[2]); break; default: diff --git a/sha1-array.c b/sha1-array.c new file mode 100644 index 0000000000..b2f47f98fb --- /dev/null +++ b/sha1-array.c @@ -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 index 0000000000..4499b5dad4 --- /dev/null +++ b/sha1-array.h @@ -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 */ diff --git a/sha1_file.c b/sha1_file.c index 357f9ab7ff..064a330408 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -11,6 +11,7 @@ #include "pack.h" #include "blob.h" #include "commit.h" +#include "run-command.h" #include "tag.h" #include "tree.h" #include "tree-walk.h" @@ -2578,10 +2579,11 @@ static void check_tag(const void *buf, size_t size) } static int index_mem(unsigned char *sha1, void *buf, size_t size, - int write_object, enum object_type type, - const char *path, int format_check) + enum object_type type, + const char *path, unsigned flags) { int ret, re_allocated = 0; + int write_object = flags & HASH_WRITE_OBJECT; if (!type) type = OBJ_BLOB; @@ -2597,7 +2599,7 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size, re_allocated = 1; } } - if (format_check) { + if (flags & HASH_FORMAT_CHECK) { if (type == OBJ_TREE) check_tree(buf, size); if (type == OBJ_COMMIT) @@ -2615,44 +2617,141 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size, return ret; } +static int index_pipe(unsigned char *sha1, int fd, enum object_type type, + const char *path, unsigned flags) +{ + struct strbuf sbuf = STRBUF_INIT; + int ret; + + if (strbuf_read(&sbuf, fd, 4096) >= 0) + ret = index_mem(sha1, sbuf.buf, sbuf.len, type, path, flags); + else + ret = -1; + strbuf_release(&sbuf); + return ret; +} + #define SMALL_FILE_SIZE (32*1024) -int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, - enum object_type type, const char *path, int format_check) +static int index_core(unsigned char *sha1, int fd, size_t size, + enum object_type type, const char *path, + unsigned flags) { int ret; - size_t size = xsize_t(st->st_size); - if (!S_ISREG(st->st_mode)) { - struct strbuf sbuf = STRBUF_INIT; - if (strbuf_read(&sbuf, fd, 4096) >= 0) - ret = index_mem(sha1, sbuf.buf, sbuf.len, write_object, - type, path, format_check); - else - ret = -1; - strbuf_release(&sbuf); - } else if (!size) { - ret = index_mem(sha1, NULL, size, write_object, type, path, - format_check); + if (!size) { + ret = index_mem(sha1, NULL, size, type, path, flags); } else if (size <= SMALL_FILE_SIZE) { char *buf = xmalloc(size); if (size == read_in_full(fd, buf, size)) - ret = index_mem(sha1, buf, size, write_object, type, - path, format_check); + ret = index_mem(sha1, buf, size, type, path, flags); else ret = error("short read %s", strerror(errno)); free(buf); } else { void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); - ret = index_mem(sha1, buf, size, write_object, type, path, - format_check); + ret = index_mem(sha1, buf, size, type, path, flags); munmap(buf, size); } + return ret; +} + +/* + * This creates one packfile per large blob, because the caller + * immediately wants the result sha1, and fast-import can report the + * object name via marks mechanism only by closing the created + * packfile. + * + * This also bypasses the usual "convert-to-git" dance, and that is on + * purpose. We could write a streaming version of the converting + * functions and insert that before feeding the data to fast-import + * (or equivalent in-core API described above), but the primary + * motivation for trying to stream from the working tree file and to + * avoid mmaping it in core is to deal with large binary blobs, and + * by definition they do _not_ want to get any conversion. + */ +static int index_stream(unsigned char *sha1, int fd, size_t size, + enum object_type type, const char *path, + unsigned flags) +{ + struct child_process fast_import; + char export_marks[512]; + const char *argv[] = { "fast-import", "--quiet", export_marks, NULL }; + char tmpfile[512]; + char fast_import_cmd[512]; + char buf[512]; + int len, tmpfd; + + strcpy(tmpfile, git_path("hashstream_XXXXXX")); + tmpfd = git_mkstemp_mode(tmpfile, 0600); + if (tmpfd < 0) + die_errno("cannot create tempfile: %s", tmpfile); + if (close(tmpfd)) + die_errno("cannot close tempfile: %s", tmpfile); + sprintf(export_marks, "--export-marks=%s", tmpfile); + + memset(&fast_import, 0, sizeof(fast_import)); + fast_import.in = -1; + fast_import.argv = argv; + fast_import.git_cmd = 1; + if (start_command(&fast_import)) + die_errno("index-stream: git fast-import failed"); + + len = sprintf(fast_import_cmd, "blob\nmark :1\ndata %lu\n", + (unsigned long) size); + write_or_whine(fast_import.in, fast_import_cmd, len, + "index-stream: feeding fast-import"); + while (size) { + char buf[10240]; + size_t sz = size < sizeof(buf) ? size : sizeof(buf); + size_t actual; + + actual = read_in_full(fd, buf, sz); + if (actual < 0) + die_errno("index-stream: reading input"); + if (write_in_full(fast_import.in, buf, actual) != actual) + die_errno("index-stream: feeding fast-import"); + size -= actual; + } + if (close(fast_import.in)) + die_errno("index-stream: closing fast-import"); + if (finish_command(&fast_import)) + die_errno("index-stream: finishing fast-import"); + + tmpfd = open(tmpfile, O_RDONLY); + if (tmpfd < 0) + die_errno("index-stream: cannot open fast-import mark"); + len = read(tmpfd, buf, sizeof(buf)); + if (len < 0) + die_errno("index-stream: reading fast-import mark"); + if (close(tmpfd) < 0) + die_errno("index-stream: closing fast-import mark"); + if (unlink(tmpfile)) + die_errno("index-stream: unlinking fast-import mark"); + if (len != 44 || + memcmp(":1 ", buf, 3) || + get_sha1_hex(buf + 3, sha1)) + die_errno("index-stream: unexpected fast-import mark: <%s>", buf); + return 0; +} + +int index_fd(unsigned char *sha1, int fd, struct stat *st, + enum object_type type, const char *path, unsigned flags) +{ + int ret; + size_t size = xsize_t(st->st_size); + + if (!S_ISREG(st->st_mode)) + ret = index_pipe(sha1, fd, type, path, flags); + else if (size <= big_file_threshold || type != OBJ_BLOB) + ret = index_core(sha1, fd, size, type, path, flags); + else + ret = index_stream(sha1, fd, size, type, path, flags); close(fd); return ret; } -int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object) +int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags) { int fd; struct strbuf sb = STRBUF_INIT; @@ -2663,7 +2762,7 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write if (fd < 0) return error("open(\"%s\"): %s", path, strerror(errno)); - if (index_fd(sha1, fd, st, write_object, OBJ_BLOB, path, 0) < 0) + if (index_fd(sha1, fd, st, OBJ_BLOB, path, flags) < 0) return error("%s: failed to insert into database", path); break; @@ -2673,7 +2772,7 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write return error("readlink(\"%s\"): %s", path, errstr); } - if (!write_object) + if (!(flags & HASH_WRITE_OBJECT)) hash_sha1_file(sb.buf, sb.len, blob_type, sha1); else if (write_sha1_file(sb.buf, sb.len, blob_type, sha1)) return error("%s: failed to insert into database", @@ -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 ---------------------- diff --git a/t/t0001-init.sh b/t/t0001-init.sh index 8106af8fba..ad66410564 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -409,7 +409,7 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' ' cd newdir && mv .git here && ln -s here .git && - git init -L ../realgitdir + git init --separate-git-dir ../realgitdir ) && echo "gitdir: `pwd`/realgitdir" >expected && test_cmp expected newdir/.git && diff --git a/t/t0081-line-buffer.sh b/t/t0081-line-buffer.sh index 5067d1e15b..bd83ed371a 100755 --- a/t/t0081-line-buffer.sh +++ b/t/t0081-line-buffer.sh @@ -2,74 +2,14 @@ test_description="Test the svn importer's input handling routines. -These tests exercise the line_buffer library, but their real purpose -is to check the assumptions that library makes of the platform's input -routines. Processes engaged in bi-directional communication would -hang if fread or fgets is too greedy. +These tests provide some simple checks that the line_buffer API +behaves as advertised. While at it, check that input of newlines and null bytes are handled correctly. " . ./test-lib.sh -test -n "$GIT_REMOTE_SVN_TEST_BIG_FILES" && test_set_prereq EXPENSIVE - -generate_tens_of_lines () { - tens=$1 && - line=$2 && - - i=0 && - while test $i -lt "$tens" - do - for j in a b c d e f g h i j - do - echo "$line" - done && - : $((i = $i + 1)) || - return - done -} - -long_read_test () { - : each line is 10 bytes, including newline && - line=abcdefghi && - echo "$line" >expect && - - if ! test_declared_prereq PIPE - then - echo >&4 "long_read_test: need to declare PIPE prerequisite" - return 127 - fi && - tens_of_lines=$(($1 / 100 + 1)) && - lines=$(($tens_of_lines * 10)) && - readsize=$((($lines - 1) * 10 + 3)) && - copysize=7 && - rm -f input && - mkfifo input && - { - ( - generate_tens_of_lines $tens_of_lines "$line" && - exec sleep 100 - ) >input & - } && - test-line-buffer input <<-EOF >output && - binary $readsize - copy $copysize - EOF - kill $! && - test_line_count = $lines output && - tail -n 1 <output >actual && - test_cmp expect actual -} - -test_expect_success 'setup: have pipes?' ' - rm -f frob && - if mkfifo frob - then - test_set_prereq PIPE - fi -' - test_expect_success 'hello world' ' echo ">HELLO" >expect && test-line-buffer <<-\EOF >actual && @@ -79,21 +19,6 @@ test_expect_success 'hello world' ' test_cmp expect actual ' -test_expect_success PIPE '0-length read, no input available' ' - printf ">" >expect && - rm -f input && - mkfifo input && - { - sleep 100 >input & - } && - test-line-buffer input <<-\EOF >actual && - binary 0 - copy 0 - EOF - kill $! && - test_cmp expect actual -' - test_expect_success '0-length read, send along greeting' ' echo ">HELLO" >expect && test-line-buffer <<-\EOF >actual && @@ -104,33 +29,6 @@ test_expect_success '0-length read, send along greeting' ' test_cmp expect actual ' -test_expect_success PIPE '1-byte read, no input available' ' - printf ">%s" ab >expect && - rm -f input && - mkfifo input && - { - ( - printf "%s" a && - printf "%s" b && - exec sleep 100 - ) >input & - } && - test-line-buffer input <<-\EOF >actual && - binary 1 - copy 1 - EOF - kill $! && - test_cmp expect actual -' - -test_expect_success PIPE 'long read (around 8192 bytes)' ' - long_read_test 8192 -' - -test_expect_success PIPE,EXPENSIVE 'longer read (around 65536 bytes)' ' - long_read_test 65536 -' - test_expect_success 'read from file descriptor' ' rm -f input && echo hello >expect && diff --git a/t/t1050-large.sh b/t/t1050-large.sh new file mode 100755 index 0000000000..deba111bd7 --- /dev/null +++ b/t/t1050-large.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# Copyright (c) 2011, Google Inc. + +test_description='adding and checking out large blobs' + +. ./test-lib.sh + +test_expect_success setup ' + git config core.bigfilethreshold 200k && + echo X | dd of=large bs=1k seek=2000 +' + +test_expect_success 'add a large file' ' + git add large && + # make sure we got a packfile and no loose objects + test -f .git/objects/pack/pack-*.pack && + test ! -f .git/objects/??/?????????????????????????????????????? +' + +test_expect_success 'checkout a large file' ' + large=$(git rev-parse :large) && + git update-index --add --cacheinfo 100644 $large another && + git checkout another && + cmp large another ;# this must not be test_cmp +' + +test_done diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 53fb8228cf..3db56267ee 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -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 diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 28e17c8920..16de05aff9 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -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 diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh index 3646930623..b68c56b68c 100755 --- a/t/t4018-diff-funcname.sh +++ b/t/t4018-diff-funcname.sh @@ -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 diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 2fcc31a6f3..983e34bec6 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -448,6 +448,59 @@ test_expect_success 'log.decorate configuration' ' git log --oneline --decorate >actual && test_cmp expect.short actual + git config --unset-all log.decorate && + git log --pretty=raw >expect.raw && + git config log.decorate full && + git log --pretty=raw >actual && + test_cmp expect.raw actual + +' + +test_expect_success 'reflog is expected format' ' + test_might_fail git config --remove-section log && + git log -g --abbrev-commit --pretty=oneline >expect && + git reflog >actual && + test_cmp expect actual +' + +test_expect_success 'whatchanged is expected format' ' + git log --no-merges --raw >expect && + git whatchanged >actual && + test_cmp expect actual +' + +test_expect_success 'log.abbrevCommit configuration' ' + test_when_finished "git config --unset log.abbrevCommit" && + + test_might_fail git config --unset log.abbrevCommit && + + git log --abbrev-commit >expect.log.abbrev && + git log --no-abbrev-commit >expect.log.full && + git log --pretty=raw >expect.log.raw && + git reflog --abbrev-commit >expect.reflog.abbrev && + git reflog --no-abbrev-commit >expect.reflog.full && + git whatchanged --abbrev-commit >expect.whatchanged.abbrev && + git whatchanged --no-abbrev-commit >expect.whatchanged.full && + + git config log.abbrevCommit true && + + git log >actual && + test_cmp expect.log.abbrev actual && + git log --no-abbrev-commit >actual && + test_cmp expect.log.full actual && + + git log --pretty=raw >actual && + test_cmp expect.log.raw actual && + + git reflog >actual && + test_cmp expect.reflog.abbrev actual && + git reflog --no-abbrev-commit >actual && + test_cmp expect.reflog.full actual && + + git whatchanged >actual && + test_cmp expect.whatchanged.abbrev actual && + git whatchanged --no-abbrev-commit >actual && + test_cmp expect.whatchanged.full actual ' test_expect_success 'show added path under "--follow -M"' ' diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index 8184c264cf..69bd576d1c 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -26,6 +26,17 @@ test_expect_success setup ' echo foo mmap bar_mmap echo foo_mmap bar mmap baz } >file && + { + echo Hello world + echo HeLLo world + echo Hello_world + echo HeLLo_world + } >hello_world && + { + echo "a+b*c" + echo "a+bc" + echo "abc" + } >ab && echo vvv >v && echo ww w >w && echo x x xx x >x && @@ -221,7 +232,17 @@ do git grep --max-depth 0 -n -e vvv $H -- t . >actual && test_cmp expected actual ' + test_expect_success "grep $L with grep.extendedRegexp=false" ' + echo "ab:a+bc" >expected && + git -c grep.extendedRegexp=false grep "a+b*c" ab >actual && + test_cmp expected actual + ' + test_expect_success "grep $L with grep.extendedRegexp=true" ' + echo "ab:abc" >expected && + git -c grep.extendedRegexp=true grep "a+b*c" ab >actual && + test_cmp expected actual + ' done cat >expected <<EOF @@ -599,4 +620,100 @@ test_expect_success 'grep -e -- -- path' ' test_cmp expected actual ' +cat >expected <<EOF +hello.c:int main(int argc, const char **argv) +hello.c: printf("Hello world.\n"); +EOF + +test_expect_success LIBPCRE 'grep --perl-regexp pattern' ' + git grep --perl-regexp "\p{Ps}.*?\p{Pe}" hello.c >actual && + test_cmp expected actual +' + +test_expect_success LIBPCRE 'grep -P pattern' ' + git grep -P "\p{Ps}.*?\p{Pe}" hello.c >actual && + test_cmp expected actual +' + +test_expect_success 'grep pattern with grep.extendedRegexp=true' ' + >empty && + test_must_fail git -c grep.extendedregexp=true \ + grep "\p{Ps}.*?\p{Pe}" hello.c >actual && + test_cmp empty actual +' + +test_expect_success LIBPCRE 'grep -P pattern with grep.extendedRegexp=true' ' + git -c grep.extendedregexp=true \ + grep -P "\p{Ps}.*?\p{Pe}" hello.c >actual && + test_cmp expected actual +' + +test_expect_success LIBPCRE 'grep -P -v pattern' ' + { + echo "ab:a+b*c" + echo "ab:a+bc" + } >expected && + git grep -P -v "abc" ab >actual && + test_cmp expected actual +' + +test_expect_success LIBPCRE 'grep -P -i pattern' ' + { + echo "hello.c: printf(\"Hello world.\n\");" + } >expected && + git grep -P -i "PRINTF\([^\d]+\)" hello.c >actual && + test_cmp expected actual +' + +test_expect_success LIBPCRE 'grep -P -w pattern' ' + { + echo "hello_world:Hello world" + echo "hello_world:HeLLo world" + } >expected && + git grep -P -w "He((?i)ll)o" hello_world >actual && + test_cmp expected actual +' + +test_expect_success 'grep -G invalidpattern properly dies ' ' + test_must_fail git grep -G "a[" +' + +test_expect_success 'grep -E invalidpattern properly dies ' ' + test_must_fail git grep -E "a[" +' + +test_expect_success LIBPCRE 'grep -P invalidpattern properly dies ' ' + test_must_fail git grep -P "a[" +' + +test_expect_success 'grep -G -E -F pattern' ' + echo "ab:a+b*c" >expected && + git grep -G -E -F "a+b*c" ab >actual && + test_cmp expected actual +' + +test_expect_success 'grep -E -F -G pattern' ' + echo "ab:a+bc" >expected && + git grep -E -F -G "a+b*c" ab >actual && + test_cmp expected actual +' + +test_expect_success 'grep -F -G -E pattern' ' + echo "ab:abc" >expected && + git grep -F -G -E "a+b*c" ab >actual && + test_cmp expected actual +' + +test_expect_success 'grep -G -F -P -E pattern' ' + >empty && + test_must_fail git grep -G -F -P -E "a\x{2b}b\x{2a}c" ab >actual && + test_cmp empty actual +' + +test_expect_success LIBPCRE 'grep -G -F -E -P pattern' ' + echo "ab:a+b*c" >expected && + git grep -G -F -E -P "a\x{2b}b\x{2a}c" ab >actual && + test_cmp expected actual +' + test_done diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 71ef0acb1b..f5648a6694 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -644,4 +644,12 @@ test_expect_success \ 'ctags: search projects by non existent tag' \ 'gitweb_run "by_tag=non-existent"' +# ---------------------------------------------------------------------- +# categories + +test_expect_success \ + 'categories: projects list, only default category' \ + 'echo "\$projects_list_group_categories = 1;" >>gitweb_config.perl && + gitweb_run' + test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index b2ce2bc4b2..64390d716d 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -731,12 +731,11 @@ test_expect_code () { exit_code=$? if test $exit_code = $want_code then - echo >&2 "test_expect_code: command exited with $exit_code: $*" return 0 - else - echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*" - return 1 fi + + echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*" + return 1 } # test_cmp is a helper function to compare actual and expected output. @@ -1072,6 +1071,7 @@ esac test -z "$NO_PERL" && test_set_prereq PERL test -z "$NO_PYTHON" && test_set_prereq PYTHON +test -n "$USE_LIBPCRE" && test_set_prereq LIBPCRE # Can we rely on git's output in the C locale? if test -n "$GETTEXT_POISON" diff --git a/transport.c b/transport.c index a02f79aae3..c9c8056f9d 100644 --- a/transport.c +++ b/transport.c @@ -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 @@ literal_copy: 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); +} diff --git a/transport.h b/transport.h index efb1968869..161d724bba 100644 --- a/transport.h +++ b/transport.h @@ -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 diff --git a/userdiff.c b/userdiff.c index 1ff47977d5..e55310cd02 100644 --- a/userdiff.c +++ b/userdiff.c @@ -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_]*" |