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