Merge branch 'jn/gitweb-search-optim'
authorJunio C Hamano <gitster@pobox.com>
Mon, 27 Feb 2012 07:05:56 +0000 (23:05 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 27 Feb 2012 07:05:56 +0000 (23:05 -0800)
* jn/gitweb-search-optim:
gitweb: Faster project search
gitweb: Option for filling only specified info in fill_project_list_info
gitweb: Refactor checking if part of project info need filling

87 files changed:
Documentation/RelNotes/1.7.10.txt
Documentation/RelNotes/1.7.8.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.9.2.txt
Documentation/RelNotes/1.7.9.3.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/git-config.txt
Documentation/git-fmt-merge-msg.txt
Documentation/git-p4.txt
Documentation/git-push.txt
Documentation/git-remote.txt
Documentation/git-tag.txt
Documentation/git.txt
Documentation/merge-options.txt
Documentation/technical/api-config.txt [new file with mode: 0644]
Documentation/technical/api-strbuf.txt
Makefile
README
builtin/blame.c
builtin/clone.c
builtin/config.c
builtin/fetch-pack.c
builtin/fetch.c
builtin/merge.c
builtin/pack-objects.c
builtin/push.c
builtin/receive-pack.c
builtin/remote.c
builtin/rev-list.c
builtin/send-pack.c
builtin/tag.c
bundle.c
cache.h
config.c
configure.ac
contrib/completion/git-completion.bash
contrib/diff-highlight/README
contrib/diff-highlight/diff-highlight
contrib/fast-import/git-p4
convert.c
convert.h
diff.c
git-am.sh
git-rebase--merge.sh
git-svn.perl
gitweb/gitweb.perl
grep.c
help.c
pager.c
read-cache.c
remote.c
remote.h
sha1_file.c
strbuf.c
strbuf.h
t/Makefile
t/perf/.gitignore [new file with mode: 0644]
t/perf/Makefile [new file with mode: 0644]
t/perf/README [new file with mode: 0644]
t/perf/aggregate.perl [new file with mode: 0755]
t/perf/min_time.perl [new file with mode: 0755]
t/perf/p0000-perf-lib-sanity.sh [new file with mode: 0755]
t/perf/p0001-rev-list.sh [new file with mode: 0755]
t/perf/p7810-grep.sh [new file with mode: 0755]
t/perf/perf-lib.sh [new file with mode: 0644]
t/perf/run [new file with mode: 0755]
t/t1051-large-conversion.sh [new file with mode: 0755]
t/t1300-repo-config.sh
t/t1305-config-include.sh [new file with mode: 0755]
t/t1450-fsck.sh
t/t3700-add.sh
t/t4150-am.sh
t/t5504-fetch-receive-strict.sh
t/t5516-fetch-push.sh
t/t5523-push-upstream.sh
t/t5541-http-push.sh
t/t5704-bundle.sh
t/t7004-tag.sh
t/t9100-git-svn-basic.sh
t/t9500-gitweb-standalone-no-errors.sh
t/t9800-git-p4-basic.sh
t/t9809-git-p4-client-view.sh
t/t9810-git-p4-rcs.sh [new file with mode: 0755]
t/test-lib-functions.sh [new file with mode: 0644]
t/test-lib.sh
transport-helper.c
transport.c
transport.h
index be3001afe239121328b9d949332e4bf96698ec67..364e16d0d5a40bae90cc97e0d6c610d3c3221f22 100644 (file)
@@ -8,11 +8,20 @@ UI, Workflows & Features
 
  * Improved handling of views, labels and branches in git-p4 (in contrib).
 
- * Updated command line arguments completion script for zsh (in contrib).
+ * "git-p4" (in contrib) suffered from unnecessary merge conflicts when
+   p4 expanded the embedded $RCS$-like keywords; it can be now told to
+   unexpand them.
+
+ * Some "git-svn" updates.
 
  * "vcs-svn"/"svn-fe" learned to read dumps with svn-deltas and
    support incremental imports.
 
+ * The configuration mechanism learned an "include" facility; an
+   assignment to the include.path pseudo-variable causes the named
+   file to be included in-place when Git looks up configuration
+   variables.
+
  * "git am" learned to pass "-b" option to underlying "git mailinfo", so
    that bracketed string other than "PATCH" at the beginning can be kept.
 
@@ -22,19 +31,22 @@ UI, Workflows & Features
  * "git clone" learned to detach the HEAD in the resulting repository
    when the source repository's HEAD does not point to a branch.
 
- * The commands in the "git diff" family and "git apply --stat" that
-   count the number of files changed and the number of lines
-   inserted/deleted have been updated to match the output from
-   "diffstat".  This also opens the door to i18n this line.
-
  * When showing a patch while ignoring whitespace changes, the context
    lines are taken from the postimage, in order to make it easier to
    view the output.
 
+ * "diff-highlight" filter (in contrib/) was updated to produce more
+   aesthetically pleasing output.
+
+ * "git tag --list" can be given "--points-at <object>" to limit its
+   output to those that point at the given object.
+
  * "git merge" in an interactive session learned to spawn the editor
    by default to let the user edit the auto-generated merge message,
    to encourage people to explain their merges better. Legacy scripts
    can export MERGE_AUTOEDIT=no to retain the historical behaviour.
+   Both "git merge" and "git pull" can be given --no-edit from the
+   command line to accept the auto-generated merge message.
 
  * "gitweb" allows intermediate entries in the directory hierarchy
    that leads to a projects to be clicked, which in turn shows the
@@ -46,16 +58,22 @@ Performance
    to parse_object() have been eliminated, to help performance in
    repositories with excessive number of refs.
 
-Internal Implementation
+Internal Implementation (please report possible regressions)
 
  * Recursive call chains in "git index-pack" to deal with long delta
    chains have been flattened, to reduce the stack footprint.
 
- * Use of add_extra_ref() API is slowly getting removed, to make it
-   possible to cleanly restructure the overall refs API.
+ * Use of add_extra_ref() API is now gone, to make it possible to
+   cleanly restructure the overall refs API.
+
+ * The command line parser of "git pack-objects" now uses parse-options
+   API.
 
  * The test suite supports the new "test_pause" helper function.
 
+ * Parallel to the test suite, there is a beginning of performance
+   benchmarking framework.
+
  * t/Makefile is adjusted to prevent newer versions of GNU make from
    running tests in seemingly random order.
 
@@ -69,35 +87,13 @@ Unless otherwise noted, all the fixes since v1.7.9 in the maintenance
 releases are contained in this release (see release notes to them for
 details).
 
- * The error message emitted when we see an empty loose object was
-   not phrased correctly.
-   (merge 33e42de mm/empty-loose-error-message later to maint).
-
- * "git commit" refused to create a commit when entries added with
-   "add -N" remained in the index, without telling Git what their content
-   in the next commit should be. We should have created the commit without
-   these paths.
-   (merge 3f6d56d jc/maint-commit-ignore-i-t-a later to maint).
-
- * Search box in "gitweb" did not accept non-ASCII characters correctly.
-   (merge 84d9e2d jn/gitweb-search-utf-8 later to maint).
-
- * The code to ask for password did not fall back to the terminal
-   input when GIT_ASKPASS is set but does not work (e.g. lack of X
-   with GUI askpass helper).
-   (merge 84d7273 jk/prompt-fallback-to-tty later to maint).
-
- * map_user() was not rewriting its output correctly, which resulted
-   in the user visible symptom that "git blame -e" sometimes showed
-   excess '>' at the end of email addresses.
-   (merge f026358 jc/maint-mailmap-output later to maint).
-
- * "checkout -b" did not allow switching out of an unborn branch.
-   (merge abe1998 jc/checkout-out-of-unborn later to maint).
+ * "gitweb" used to drop warnings in the log file when "heads" view is
+   accessed in a repository whose HEAD does not point at a valid
+   branch.
 
 ---
 exec >/var/tmp/1
-O=v1.7.9-249-gaa47ec9
+O=v1.7.9.2-261-gd065f68
 echo O=$(git describe)
 git log --first-parent --oneline ^maint $O..
 echo
diff --git a/Documentation/RelNotes/1.7.8.5.txt b/Documentation/RelNotes/1.7.8.5.txt
new file mode 100644 (file)
index 0000000..011fd2a
--- /dev/null
@@ -0,0 +1,19 @@
+Git v1.7.8.5 Release Notes
+==========================
+
+Fixes since v1.7.8.4
+--------------------
+
+ * Dependency on our thread-utils.h header file was missing for
+   objects that depend on it in the Makefile.
+
+ * "git am" when fed an empty file did not correctly finish reading it
+   when it attempts to guess the input format.
+
+ * "git grep -P" (when PCRE is enabled in the build) did not match the
+   beginning and the end of the line correctly with ^ and $.
+
+ * "git rebase -m" tried to run "git notes copy" needlessly when
+   nothing was rewritten.
+
+Also contains minor fixes and documentation updates.
index 5dd06f2177754513b6724bf823d393b849252ade..e500da75ddc9f57a52642d8adbe395aa5c141051 100644 (file)
@@ -4,24 +4,66 @@ Git v1.7.9.2 Release Notes
 Fixes since v1.7.9.1
 --------------------
 
-* The error message emitted when we see an empty loose object was
-  not phrased correctly.
+ * Bash completion script (in contrib/) did not like a pattern that
+   begins with a dash to be passed to __git_ps1 helper function.
 
-* The code to ask for password did not fall back to the terminal
-  input when GIT_ASKPASS is set but does not work (e.g. lack of X
-  with GUI askpass helper).
+ * Adaptation of the bash completion script (in contrib/) for zsh
+   incorrectly listed all subcommands when "git <TAB><TAB>" was given
+   to ask for list of porcelain subcommands.
 
-* map_user() was not rewriting its output correctly, which resulted
-  in the user visible symptom that "git blame -e" sometimes showed
-  excess '>' at the end of email addresses.
+ * The build procedure for profile-directed optimized binary was not
+   working very well.
 
-* "git checkout -b" did not allow switching out of an unborn branch.
+ * Some systems need to explicitly link -lcharset to get locale_charset().
 
-* "git commit" refused to create a commit when entries added with
-  "add -N" remained in the index, without telling Git what their content
-  in the next commit should be. We should have created the commit without
-  these paths.
+ * t5541 ignored user-supplied port number used for HTTP server testing.
 
-* Search box in "gitweb" did not accept non-ASCII characters correctly.
+ * The error message emitted when we see an empty loose object was
+   not phrased correctly.
+
+ * The code to ask for password did not fall back to the terminal
+   input when GIT_ASKPASS is set but does not work (e.g. lack of X
+   with GUI askpass helper).
+
+ * We failed to give the true terminal width to any subcommand when
+   they are invoked with the pager, i.e. "git -p cmd".
+
+ * map_user() was not rewriting its output correctly, which resulted
+   in the user visible symptom that "git blame -e" sometimes showed
+   excess '>' at the end of email addresses.
+
+ * "git checkout -b" did not allow switching out of an unborn branch.
+
+ * When you have both .../foo and .../foo.git, "git clone .../foo" did not
+   favor the former but the latter.
+
+ * "git commit" refused to create a commit when entries added with
+   "add -N" remained in the index, without telling Git what their content
+   in the next commit should be. We should have created the commit without
+   these paths.
+
+ * "git diff --stat" said "files", "insertions", and "deletions" even
+   when it is showing one "file", one "insertion" or one "deletion".
+
+ * The output from "git diff --stat" for two paths that have the same
+   amount of changes showed graph bars of different length due to the
+   way we handled rounding errors.
+
+ * "git grep" did not pay attention to -diff (hence -binary) attribute.
+
+ * The transport programs (fetch, push, clone)ignored --no-progress
+   and showed progress when sending their output to a terminal.
+
+ * Sometimes error status detected by a check in an earlier phase of
+   "git receive-pack" (the other end of "git push") was lost by later
+   checks, resulting in false indication of success.
+
+ * "git rev-list --verify" sometimes skipped verification depending on
+   the phase of the moon, which dates back to 1.7.8.x series.
+
+ * Search box in "gitweb" did not accept non-ASCII characters correctly.
+
+ * Search interface of "gitweb" did not show multiple matches in the same file
+   correctly.
 
 Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.9.3.txt b/Documentation/RelNotes/1.7.9.3.txt
new file mode 100644 (file)
index 0000000..1d03fd1
--- /dev/null
@@ -0,0 +1,17 @@
+Git v1.7.9.3 Release Notes
+==========================
+
+Fixes since v1.7.9.2
+--------------------
+
+ * "git p4" (in contrib/) submit the changes to a wrong place when the
+   "--use-client-spec" option is set.
+
+ * The config.mak.autogen generated by optional autoconf support tried
+   to link the binary with -lintl even when libintl.h is missing from
+   the system.
+
+ * "git add --refresh <pathspec>" used to warn about unmerged paths
+   outside the given pathspec.
+
+Also contains minor fixes and documentation updates.
index abeb82b2c6d40e8557f7a5f8ad4c5e98b3a26a62..e55dae1806a8889d8179c94139bb60a2c5f7a9a6 100644 (file)
@@ -84,6 +84,17 @@ customary UNIX fashion.
 
 Some variables may require a special value format.
 
+Includes
+~~~~~~~~
+
+You can include one config file from another by setting the special
+`include.path` variable to the name of the file to be included. The
+included file is expanded immediately, as if its contents had been
+found at the location of the include directive. If the value of the
+`include.path` variable is a relative path, the path is considered to be
+relative to the configuration file in which the include directive was
+found. See below for examples.
+
 Example
 ~~~~~~~
 
@@ -106,6 +117,10 @@ Example
                gitProxy="ssh" for "kernel.org"
                gitProxy=default-proxy ; for the rest
 
+       [include]
+               path = /path/to/foo.inc ; include by absolute path
+               path = foo ; expand "foo" relative to the current file
+
 Variables
 ~~~~~~~~~
 
index e7ecf5d803e14dfa452671cf01e7730dce48b984..aa8303b1adb1ac6efba6a5919a6a59495e89c6fb 100644 (file)
@@ -178,6 +178,11 @@ See also <<FILES>>.
        Opens an editor to modify the specified config file; either
        '--system', '--global', or repository (default).
 
+--includes::
+--no-includes::
+       Respect `include.*` directives in config files when looking up
+       values. Defaults to on.
+
 [[FILES]]
 FILES
 -----
index 32aff954a2b2f95a61b39b8d08fb0482724400bb..3a0f55ec8e273545af3a92a9b886511ac79ba73c 100644 (file)
@@ -53,6 +53,11 @@ OPTIONS
 CONFIGURATION
 -------------
 
+merge.branchdesc::
+       In addition to branch names, populate the log message with
+       the branch description text associated with them.  Defaults
+       to false.
+
 merge.log::
        In addition to branch names, populate the log message with at
        most the specified number of one-line descriptions from the
index 8b92cc0f8d54a819236ab879690708ec486f7a19..b7c7929716adbad2e27f2d38b83a3c8f74604a59 100644 (file)
@@ -303,9 +303,13 @@ CLIENT SPEC
 -----------
 The p4 client specification is maintained with the 'p4 client' command
 and contains among other fields, a View that specifies how the depot
-is mapped into the client repository.  Git-p4 can consult the client
-spec when given the '--use-client-spec' option or useClientSpec
-variable.
+is mapped into the client repository.  The 'clone' and 'sync' commands
+can consult the client spec when given the '--use-client-spec' option or
+when the useClientSpec variable is true.  After 'git p4 clone', the
+useClientSpec variable is automatically set in the repository
+configuration file.  This allows future 'git p4 submit' commands to
+work properly; the submit command looks only at the variable and does
+not have a command-line option.
 
 The full syntax for a p4 view is documented in 'p4 help views'.  Git-p4
 knows only a subset of the view syntax.  It understands multi-line
@@ -483,6 +487,11 @@ git-p4.skipUserNameCheck::
        user map, 'git p4' exits.  This option can be used to force
        submission regardless.
 
+git-p4.attemptRCSCleanup:
+    If enabled, 'git p4 submit' will attempt to cleanup RCS keywords
+    ($Header$, etc). These would otherwise cause merge conflicts and prevent
+    the submit going ahead. This option should be considered experimental at
+    present.
 
 IMPLEMENTATION DETAILS
 ----------------------
index aede48877fb080bd12c346c74cf7453860d7de21..48760db3371ef762fe6e0f099045c208206742f1 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
-          [--repo=<repository>] [-f | --force] [-v | --verbose] [-u | --set-upstream]
+          [--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
           [<repository> [<refspec>...]]
 
 DESCRIPTION
@@ -71,6 +71,14 @@ nor in any Push line of the corresponding remotes file---see below).
        Instead of naming each ref to push, specifies that all
        refs under `refs/heads/` be pushed.
 
+--prune::
+       Remove remote branches that don't have a local counterpart. For example
+       a remote branch `tmp` will be removed if a local branch with the same
+       name doesn't exist any more. This also respects refspecs, e.g.
+       `git push --prune remote refs/heads/{asterisk}:refs/tmp/{asterisk}` would
+       make sure that remote `refs/tmp/foo` will be removed if `refs/heads/foo`
+       doesn't exist.
+
 --mirror::
        Instead of naming each ref to push, specifies that all
        refs under `refs/` (which includes but is not
index 5a8c5061f3701c57bab75b2b4c70ad620f7e536f..d376d19ef79962c5755e958eec7a491c96479a1f 100644 (file)
@@ -14,7 +14,7 @@ SYNOPSIS
 'git remote rename' <old> <new>
 'git remote rm' <name>
 'git remote set-head' <name> (-a | -d | <branch>)
-'git remote set-branches' <name> [--add] <branch>...
+'git remote set-branches' [--add] <name> <branch>...
 'git remote set-url' [--push] <name> <newurl> [<oldurl>]
 'git remote set-url --add' [--push] <name> <newurl>
 'git remote set-url --delete' [--push] <name> <url>
index 53ff5f6cf7b9420933b022accace1355db6337c6..8d32b9a814675c9ebb58c25615a7f647dae20c93 100644 (file)
@@ -12,7 +12,8 @@ SYNOPSIS
 'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]
        <tagname> [<commit> | <object>]
 'git tag' -d <tagname>...
-'git tag' [-n[<num>]] -l [--contains <commit>] [<pattern>...]
+'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
+       [<pattern>...]
 'git tag' -v <tagname>...
 
 DESCRIPTION
@@ -86,6 +87,9 @@ OPTIONS
 --contains <commit>::
        Only list tags which contain the specified commit.
 
+--points-at <object>::
+       Only list tags of the given object.
+
 -m <msg>::
 --message=<msg>::
        Use the given tag message (instead of prompting).
index d8a13bf61c44adf29b642fe1729fb866144afdaa..22fadeb114b56249920a3338a6ceb7bd71615a79 100644 (file)
@@ -44,9 +44,10 @@ unreleased) version of git, that is available from 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v1.7.9.1/git.html[documentation for release 1.7.9.1]
+* link:v1.7.9.2/git.html[documentation for release 1.7.9.2]
 
 * release notes for
+  link:RelNotes/1.7.9.2.txt[1.7.9.2],
   link:RelNotes/1.7.9.1.txt[1.7.9.1],
   link:RelNotes/1.7.9.txt[1.7.9].
 
index f2f1d0f51ceffd4f36a408886cee222a37cf4126..0bcbe0ac3c474ab12068f468476946b9db5ef3e8 100644 (file)
@@ -24,13 +24,18 @@ updated behaviour, the environment variable `GIT_MERGE_AUTOEDIT` can be
 set to `no` at the beginning of them.
 
 --ff::
+       When the merge resolves as a fast-forward, only update the branch
+       pointer, without creating a merge commit.  This is the default
+       behavior.
+
 --no-ff::
-       Do not generate a merge commit if the merge resolved as
-       a fast-forward, only update the branch pointer. This is
-       the default behavior of git-merge.
-+
-With --no-ff Generate a merge commit even if the merge
-resolved as a fast-forward.
+       Create a merge commit even when the merge resolves as a
+       fast-forward.
+
+--ff-only::
+       Refuse to merge and exit with a non-zero status unless the
+       current `HEAD` is already up-to-date or the merge can be
+       resolved as a fast-forward.
 
 --log[=<n>]::
 --no-log::
@@ -65,11 +70,6 @@ merge.
 With --no-squash perform the merge and commit the result. This
 option can be used to override --squash.
 
---ff-only::
-       Refuse to merge and exit with a non-zero status unless the
-       current `HEAD` is already up-to-date or the merge can be
-       resolved as a fast-forward.
-
 -s <strategy>::
 --strategy=<strategy>::
        Use the given merge strategy; can be supplied more than
diff --git a/Documentation/technical/api-config.txt b/Documentation/technical/api-config.txt
new file mode 100644 (file)
index 0000000..edf8dfb
--- /dev/null
@@ -0,0 +1,140 @@
+config API
+==========
+
+The config API gives callers a way to access git configuration files
+(and files which have the same syntax). See linkgit:git-config[1] for a
+discussion of the config file syntax.
+
+General Usage
+-------------
+
+Config files are parsed linearly, and each variable found is passed to a
+caller-provided callback function. The callback function is responsible
+for any actions to be taken on the config option, and is free to ignore
+some options. It is not uncommon for the configuration to be parsed
+several times during the run of a git program, with different callbacks
+picking out different variables useful to themselves.
+
+A config callback function takes three parameters:
+
+- the name of the parsed variable. This is in canonical "flat" form: the
+  section, subsection, and variable segments will be separated by dots,
+  and the section and variable segments will be all lowercase. E.g.,
+  `core.ignorecase`, `diff.SomeType.textconv`.
+
+- the value of the found variable, as a string. If the variable had no
+  value specified, the value will be NULL (typically this means it
+  should be interpreted as boolean true).
+
+- a void pointer passed in by the caller of the config API; this can
+  contain callback-specific data
+
+A config callback should return 0 for success, or -1 if the variable
+could not be parsed properly.
+
+Basic Config Querying
+---------------------
+
+Most programs will simply want to look up variables in all config files
+that git knows about, using the normal precedence rules. To do this,
+call `git_config` with a callback function and void data pointer.
+
+`git_config` will read all config sources in order of increasing
+priority. Thus a callback should typically overwrite previously-seen
+entries with new ones (e.g., if both the user-wide `~/.gitconfig` and
+repo-specific `.git/config` contain `color.ui`, the config machinery
+will first feed the user-wide one to the callback, and then the
+repo-specific one; by overwriting, the higher-priority repo-specific
+value is left at the end).
+
+The `git_config_with_options` function lets the caller examine config
+while adjusting some of the default behavior of `git_config`. It should
+almost never be used by "regular" git code that is looking up
+configuration variables. It is intended for advanced callers like
+`git-config`, which are intentionally tweaking the normal config-lookup
+process. It takes two extra parameters:
+
+`filename`::
+If this parameter is non-NULL, it specifies the name of a file to
+parse for configuration, rather than looking in the usual files. Regular
+`git_config` defaults to `NULL`.
+
+`respect_includes`::
+Specify whether include directives should be followed in parsed files.
+Regular `git_config` defaults to `1`.
+
+There is a special version of `git_config` called `git_config_early`.
+This version takes an additional parameter to specify the repository
+config, instead of having it looked up via `git_path`. This is useful
+early in a git program before the repository has been found. Unless
+you're working with early setup code, you probably don't want to use
+this.
+
+Reading Specific Files
+----------------------
+
+To read a specific file in git-config format, use
+`git_config_from_file`. This takes the same callback and data parameters
+as `git_config`.
+
+Value Parsing Helpers
+---------------------
+
+To aid in parsing string values, the config API provides callbacks with
+a number of helper functions, including:
+
+`git_config_int`::
+Parse the string to an integer, including unit factors. Dies on error;
+otherwise, returns the parsed result.
+
+`git_config_ulong`::
+Identical to `git_config_int`, but for unsigned longs.
+
+`git_config_bool`::
+Parse a string into a boolean value, respecting keywords like "true" and
+"false". Integer values are converted into true/false values (when they
+are non-zero or zero, respectively). Other values cause a die(). If
+parsing is successful, the return value is the result.
+
+`git_config_bool_or_int`::
+Same as `git_config_bool`, except that integers are returned as-is, and
+an `is_bool` flag is unset.
+
+`git_config_maybe_bool`::
+Same as `git_config_bool`, except that it returns -1 on error rather
+than dying.
+
+`git_config_string`::
+Allocates and copies the value string into the `dest` parameter; if no
+string is given, prints an error message and returns -1.
+
+`git_config_pathname`::
+Similar to `git_config_string`, but expands `~` or `~user` into the
+user's home directory when found at the beginning of the path.
+
+Include Directives
+------------------
+
+By default, the config parser does not respect include directives.
+However, a caller can use the special `git_config_include` wrapper
+callback to support them. To do so, you simply wrap your "real" callback
+function and data pointer in a `struct config_include_data`, and pass
+the wrapper to the regular config-reading functions. For example:
+
+-------------------------------------------
+int read_file_with_include(const char *file, config_fn_t fn, void *data)
+{
+       struct config_include_data inc = CONFIG_INCLUDE_INIT;
+       inc.fn = fn;
+       inc.data = data;
+       return git_config_from_file(git_config_include, file, &inc);
+}
+-------------------------------------------
+
+`git_config` respects includes automatically. The lower-level
+`git_config_from_file` does not.
+
+Writing Config Files
+--------------------
+
+TODO
index afe27599511c5f6bab6e4f799fd18e7d83bdd454..95a8bf3846b30650f3ee089a4fbadfcc5a42da20 100644 (file)
@@ -255,8 +255,24 @@ same behaviour as well.
 
 `strbuf_getline`::
 
-       Read a line from a FILE* pointer. The second argument specifies the line
+       Read a line from a FILE *, overwriting the existing contents
+       of the strbuf. The second argument specifies the line
        terminator character, typically `'\n'`.
+       Reading stops after the terminator or at EOF.  The terminator
+       is removed from the buffer before returning.  Returns 0 unless
+       there was nothing left before EOF, in which case it returns `EOF`.
+
+`strbuf_getwholeline`::
+
+       Like `strbuf_getline`, but keeps the trailing terminator (if
+       any) in the buffer.
+
+`strbuf_getwholeline_fd`::
+
+       Like `strbuf_getwholeline`, but operates on a file descriptor.
+       It reads one character at a time, so it is very slow.  Do not
+       use it unless you need the correct position in the file
+       descriptor.
 
 `stripspace`::
 
index a0de4e9c6b1f8d7df104505f9b8edd3940c900f0..cf2c40b44f8383d002235400660d76ef7f6de33c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -620,6 +620,7 @@ LIB_H += streaming.h
 LIB_H += string-list.h
 LIB_H += submodule.h
 LIB_H += tag.h
+LIB_H += thread-utils.h
 LIB_H += transport.h
 LIB_H += tree.h
 LIB_H += tree-walk.h
@@ -2361,6 +2362,10 @@ GIT-BUILD-OPTIONS: FORCE
        @echo USE_LIBPCRE=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@
        @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
        @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
+       @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@
+ifdef GIT_TEST_OPTS
+       @echo GIT_TEST_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_OPTS)))'\' >>$@
+endif
 ifdef GIT_TEST_CMP
        @echo GIT_TEST_CMP=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_CMP)))'\' >>$@
 endif
@@ -2369,7 +2374,18 @@ ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
 endif
        @echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@
        @echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@
-       @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@
+ifdef GIT_PERF_REPEAT_COUNT
+       @echo GIT_PERF_REPEAT_COUNT=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPEAT_COUNT)))'\' >>$@
+endif
+ifdef GIT_PERF_REPO
+       @echo GIT_PERF_REPO=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPO)))'\' >>$@
+endif
+ifdef GIT_PERF_LARGE_REPO
+       @echo GIT_PERF_LARGE_REPO=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_LARGE_REPO)))'\' >>$@
+endif
+ifdef GIT_PERF_MAKE_OPTS
+       @echo GIT_PERF_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_MAKE_OPTS)))'\' >>$@
+endif
 
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
@@ -2405,6 +2421,11 @@ export NO_SVN_TESTS
 test: all
        $(MAKE) -C t/ all
 
+perf: all
+       $(MAKE) -C t/perf/ all
+
+.PHONY: test perf
+
 test-ctype$X: ctype.o
 
 test-date$X: date.o ctype.o
diff --git a/README b/README
index 67cfeb2016b24df1cb406c18145efd399f6a1792..d2690ec8dc6e5f054c33a5f3b4c26f6ad038706d 100644 (file)
--- a/README
+++ b/README
@@ -42,10 +42,12 @@ including full documentation and Git related tools.
 
 The user discussion and development of Git take place on the Git
 mailing list -- everyone is welcome to post bug reports, feature
-requests, comments and patches to git@vger.kernel.org. To subscribe
-to the list, send an email with just "subscribe git" in the body to
-majordomo@vger.kernel.org. The mailing list archives are available at
-http://marc.theaimsgroup.com/?l=git and other archival sites.
+requests, comments and patches to git@vger.kernel.org (read
+Documentation/SubmittingPatches for instructions on patch submission).
+To subscribe to the list, send an email with just "subscribe git" in
+the body to majordomo@vger.kernel.org. The mailing list archives are
+available at http://marc.theaimsgroup.com/?l=git and other archival
+sites.
 
 The messages titled "A note from the maintainer", "What's in
 git.git (stable)" and "What's cooking in git.git (topics)" and
index 01956c80815a4f330ae27b77b4d21d263a887d1b..b35bd6249de66d02b7f33eb7aae4866193447156 100644 (file)
@@ -1828,18 +1828,6 @@ static int read_ancestry(const char *graft_file)
        return 0;
 }
 
-/*
- * How many columns do we need to show line numbers in decimal?
- */
-static int lineno_width(int lines)
-{
-       int i, width;
-
-       for (width = 1, i = 10; i <= lines; width++)
-               i *= 10;
-       return width;
-}
-
 /*
  * How many columns do we need to show line numbers, authors,
  * and filenames?
@@ -1880,9 +1868,9 @@ static void find_alignment(struct scoreboard *sb, int *option)
                if (largest_score < ent_score(sb, e))
                        largest_score = ent_score(sb, e);
        }
-       max_orig_digits = lineno_width(longest_src_lines);
-       max_digits = lineno_width(longest_dst_lines);
-       max_score_digits = lineno_width(largest_score);
+       max_orig_digits = decimal_width(longest_src_lines);
+       max_digits = decimal_width(longest_dst_lines);
+       max_score_digits = decimal_width(largest_score);
 }
 
 /*
index 7559f62bc7a846ecb8f8d085e8bf5c3e6d4df2cd..bbd5c96237fc332e159face6c8678d8ae3b9a3e9 100644 (file)
@@ -45,7 +45,7 @@ static char *option_branch = NULL;
 static const char *real_git_dir;
 static char *option_upload_pack = "git-upload-pack";
 static int option_verbosity;
-static int option_progress;
+static int option_progress = -1;
 static struct string_list option_config;
 static struct string_list option_reference;
 
@@ -60,8 +60,8 @@ static int opt_parse_reference(const struct option *opt, const char *arg, int un
 
 static struct option builtin_clone_options[] = {
        OPT__VERBOSITY(&option_verbosity),
-       OPT_BOOLEAN(0, "progress", &option_progress,
-                       "force progress reporting"),
+       OPT_BOOL(0, "progress", &option_progress,
+                "force progress reporting"),
        OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
                    "don't create a checkout"),
        OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
index d35c06ae51573eafbddd6309fda3f90ecef35d54..d41a9bfb143c2bd82e539c3f390f17914c2e853a 100644 (file)
@@ -25,6 +25,7 @@ static const char *given_config_file;
 static int actions, types;
 static const char *get_color_slot, *get_colorbool_slot;
 static int end_null;
+static int respect_includes = -1;
 
 #define ACTION_GET (1<<0)
 #define ACTION_GET_ALL (1<<1)
@@ -74,6 +75,7 @@ static struct option builtin_config_options[] = {
        OPT_BIT(0, "path", &types, "value is a path (file or directory name)", TYPE_PATH),
        OPT_GROUP("Other"),
        OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"),
+       OPT_BOOL(0, "includes", &respect_includes, "respect include directives on lookup"),
        OPT_END(),
 };
 
@@ -161,8 +163,11 @@ static int get_value(const char *key_, const char *regex_)
        int ret = -1;
        char *global = NULL, *repo_config = NULL;
        const char *system_wide = NULL, *local;
+       struct config_include_data inc = CONFIG_INCLUDE_INIT;
+       config_fn_t fn;
+       void *data;
 
-       local = config_exclusive_filename;
+       local = given_config_file;
        if (!local) {
                const char *home = getenv("HOME");
                local = repo_config = git_pathdup("config");
@@ -213,19 +218,28 @@ static int get_value(const char *key_, const char *regex_)
                }
        }
 
+       fn = show_config;
+       data = NULL;
+       if (respect_includes) {
+               inc.fn = fn;
+               inc.data = data;
+               fn = git_config_include;
+               data = &inc;
+       }
+
        if (do_all && system_wide)
-               git_config_from_file(show_config, system_wide, NULL);
+               git_config_from_file(fn, system_wide, data);
        if (do_all && global)
-               git_config_from_file(show_config, global, NULL);
+               git_config_from_file(fn, global, data);
        if (do_all)
-               git_config_from_file(show_config, local, NULL);
-       git_config_from_parameters(show_config, NULL);
+               git_config_from_file(fn, local, data);
+       git_config_from_parameters(fn, data);
        if (!do_all && !seen)
-               git_config_from_file(show_config, local, NULL);
+               git_config_from_file(fn, local, data);
        if (!do_all && !seen && global)
-               git_config_from_file(show_config, global, NULL);
+               git_config_from_file(fn, global, data);
        if (!do_all && !seen && system_wide)
-               git_config_from_file(show_config, system_wide, NULL);
+               git_config_from_file(fn, system_wide, data);
 
        free(key);
        if (regexp) {
@@ -301,7 +315,8 @@ static void get_color(const char *def_color)
 {
        get_color_found = 0;
        parsed_color[0] = '\0';
-       git_config(git_get_color_config, NULL);
+       git_config_with_options(git_get_color_config, NULL,
+                               given_config_file, respect_includes);
 
        if (!get_color_found && def_color)
                color_parse(def_color, "command line", parsed_color);
@@ -328,7 +343,8 @@ static int get_colorbool(int print)
 {
        get_colorbool_found = -1;
        get_diff_color_found = -1;
-       git_config(git_get_colorbool_config, NULL);
+       git_config_with_options(git_get_colorbool_config, NULL,
+                               given_config_file, respect_includes);
 
        if (get_colorbool_found < 0) {
                if (!strcmp(get_colorbool_slot, "color.diff"))
@@ -351,7 +367,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
        int nongit = !startup_info->have_repository;
        char *value;
 
-       config_exclusive_filename = getenv(CONFIG_ENVIRONMENT);
+       given_config_file = getenv(CONFIG_ENVIRONMENT);
 
        argc = parse_options(argc, argv, prefix, builtin_config_options,
                             builtin_config_usage,
@@ -366,24 +382,28 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                char *home = getenv("HOME");
                if (home) {
                        char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
-                       config_exclusive_filename = user_config;
+                       given_config_file = user_config;
                } else {
                        die("$HOME not set");
                }
        }
        else if (use_system_config)
-               config_exclusive_filename = git_etc_gitconfig();
+               given_config_file = git_etc_gitconfig();
        else if (use_local_config)
-               config_exclusive_filename = git_pathdup("config");
+               given_config_file = git_pathdup("config");
        else if (given_config_file) {
                if (!is_absolute_path(given_config_file) && prefix)
-                       config_exclusive_filename = prefix_filename(prefix,
-                                                                   strlen(prefix),
-                                                                   given_config_file);
+                       given_config_file =
+                               xstrdup(prefix_filename(prefix,
+                                                       strlen(prefix),
+                                                       given_config_file));
                else
-                       config_exclusive_filename = given_config_file;
+                       given_config_file = given_config_file;
        }
 
+       if (respect_includes == -1)
+               respect_includes = !given_config_file;
+
        if (end_null) {
                term = '\0';
                delim = '\n';
@@ -420,28 +440,30 @@ int cmd_config(int argc, const char **argv, const char *prefix)
 
        if (actions == ACTION_LIST) {
                check_argc(argc, 0, 0);
-               if (git_config(show_all_config, NULL) < 0) {
-                       if (config_exclusive_filename)
+               if (git_config_with_options(show_all_config, NULL,
+                                           given_config_file,
+                                           respect_includes) < 0) {
+                       if (given_config_file)
                                die_errno("unable to read config file '%s'",
-                                         config_exclusive_filename);
+                                         given_config_file);
                        else
                                die("error processing config file(s)");
                }
        }
        else if (actions == ACTION_EDIT) {
                check_argc(argc, 0, 0);
-               if (!config_exclusive_filename && nongit)
+               if (!given_config_file && nongit)
                        die("not in a git directory");
                git_config(git_default_config, NULL);
-               launch_editor(config_exclusive_filename ?
-                             config_exclusive_filename : git_path("config"),
+               launch_editor(given_config_file ?
+                             given_config_file : git_path("config"),
                              NULL, NULL);
        }
        else if (actions == ACTION_SET) {
                int ret;
                check_argc(argc, 2, 2);
                value = normalize_value(argv[0], argv[1]);
-               ret = git_config_set(argv[0], value);
+               ret = git_config_set_in_file(given_config_file, argv[0], value);
                if (ret == CONFIG_NOTHING_SET)
                        error("cannot overwrite multiple values with a single value\n"
                        "       Use a regexp, --add or --replace-all to change %s.", argv[0]);
@@ -450,17 +472,20 @@ int cmd_config(int argc, const char **argv, const char *prefix)
        else if (actions == ACTION_SET_ALL) {
                check_argc(argc, 2, 3);
                value = normalize_value(argv[0], argv[1]);
-               return git_config_set_multivar(argv[0], value, argv[2], 0);
+               return git_config_set_multivar_in_file(given_config_file,
+                                                      argv[0], value, argv[2], 0);
        }
        else if (actions == ACTION_ADD) {
                check_argc(argc, 2, 2);
                value = normalize_value(argv[0], argv[1]);
-               return git_config_set_multivar(argv[0], value, "^$", 0);
+               return git_config_set_multivar_in_file(given_config_file,
+                                                      argv[0], value, "^$", 0);
        }
        else if (actions == ACTION_REPLACE_ALL) {
                check_argc(argc, 2, 3);
                value = normalize_value(argv[0], argv[1]);
-               return git_config_set_multivar(argv[0], value, argv[2], 1);
+               return git_config_set_multivar_in_file(given_config_file,
+                                                      argv[0], value, argv[2], 1);
        }
        else if (actions == ACTION_GET) {
                check_argc(argc, 1, 2);
@@ -481,18 +506,22 @@ int cmd_config(int argc, const char **argv, const char *prefix)
        else if (actions == ACTION_UNSET) {
                check_argc(argc, 1, 2);
                if (argc == 2)
-                       return git_config_set_multivar(argv[0], NULL, argv[1], 0);
+                       return git_config_set_multivar_in_file(given_config_file,
+                                                              argv[0], NULL, argv[1], 0);
                else
-                       return git_config_set(argv[0], NULL);
+                       return git_config_set_in_file(given_config_file,
+                                                     argv[0], NULL);
        }
        else if (actions == ACTION_UNSET_ALL) {
                check_argc(argc, 1, 2);
-               return git_config_set_multivar(argv[0], NULL, argv[1], 1);
+               return git_config_set_multivar_in_file(given_config_file,
+                                                      argv[0], NULL, argv[1], 1);
        }
        else if (actions == ACTION_RENAME_SECTION) {
                int ret;
                check_argc(argc, 2, 2);
-               ret = git_config_rename_section(argv[0], argv[1]);
+               ret = git_config_rename_section_in_file(given_config_file,
+                                                       argv[0], argv[1]);
                if (ret < 0)
                        return ret;
                if (ret == 0)
@@ -501,7 +530,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
        else if (actions == ACTION_REMOVE_SECTION) {
                int ret;
                check_argc(argc, 1, 1);
-               ret = git_config_rename_section(argv[0], NULL);
+               ret = git_config_rename_section_in_file(given_config_file,
+                                                       argv[0], NULL);
                if (ret < 0)
                        return ret;
                if (ret == 0)
index 0e8560f60f43dd6e971a38381838d217bdc28899..7124c4b49cfba7985c5ba2046296f006de04e3bb 100644 (file)
@@ -737,7 +737,7 @@ static int get_pack(int xd[2], char **pack_lockfile)
        }
        else {
                *av++ = "unpack-objects";
-               if (args.quiet)
+               if (args.quiet || args.no_progress)
                        *av++ = "-q";
        }
        if (*hdr_arg)
index ab186332fa881e0f823b0042f2d650fda8f365a8..65f5f9b72f92ec64ac5ad1ad264f78199337aba6 100644 (file)
@@ -30,7 +30,7 @@ enum {
 };
 
 static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
-static int progress, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
+static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
 static int tags = TAGS_DEFAULT;
 static const char *depth;
 static const char *upload_pack;
@@ -78,7 +78,7 @@ static struct option builtin_fetch_options[] = {
        OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
        OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
                    "allow updating of HEAD ref"),
-       OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
+       OPT_BOOL(0, "progress", &progress, "force progress reporting"),
        OPT_STRING(0, "depth", &depth, "depth",
                   "deepen history of shallow clone"),
        { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "dir",
index ed0f959ac49c02aaffa77bc748a9efc69f1fd339..d3e1e8dc9e478aaea7ef4da855fb7c2e10397644 100644 (file)
@@ -1129,7 +1129,7 @@ static int default_edit_option(void)
        /* Use editor if stdin and stdout are the same and is a tty */
        return (!fstat(0, &st_stdin) &&
                !fstat(1, &st_stdout) &&
-               isatty(0) &&
+               isatty(0) && isatty(1) &&
                st_stdin.st_dev == st_stdout.st_dev &&
                st_stdin.st_ino == st_stdout.st_ino &&
                st_stdin.st_mode == st_stdout.st_mode);
index e21e5af8f9753ada34837df2c5d4c95234c7c00a..7b07c092cc5550d6784531c3137f05f54cfec258 100644 (file)
@@ -2451,7 +2451,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                die("bad pack compression level %d", pack_compression_level);
 #ifdef NO_PTHREADS
        if (delta_search_threads != 1)
-               warning("no threads support, ignoring %s", arg);
+               warning("no threads support, ignoring --threads");
 #endif
        if (!pack_to_stdout && !pack_size_limit)
                pack_size_limit = pack_size_limit_cfg;
index 35cce532f2bb632e01c0de0a8e6f9e1395eece88..d315475f16c96a831a11c2aebf00ada40b7c9663 100644 (file)
@@ -19,7 +19,7 @@ static int thin;
 static int deleterefs;
 static const char *receivepack;
 static int verbosity;
-static int progress;
+static int progress = -1;
 
 static const char **refspec;
 static int refspec_nr;
@@ -260,7 +260,9 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
                OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status",
                        TRANSPORT_PUSH_SET_UPSTREAM),
-               OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
+               OPT_BOOL(0, "progress", &progress, "force progress reporting"),
+               OPT_BIT(0, "prune", &flags, "prune locally removed refs",
+                       TRANSPORT_PUSH_PRUNE),
                OPT_END()
        };
 
index fa7448be5aaf9d2830168b77f9a555e9b6740993..0afb8b289621c419bd7472097335e4235da37d61 100644 (file)
@@ -642,8 +642,10 @@ static void check_aliased_updates(struct command *commands)
        }
        sort_string_list(&ref_list);
 
-       for (cmd = commands; cmd; cmd = cmd->next)
-               check_aliased_update(cmd, &ref_list);
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               if (!cmd->error_string)
+                       check_aliased_update(cmd, &ref_list);
+       }
 
        string_list_clear(&ref_list, 0);
 }
@@ -707,8 +709,10 @@ static void execute_commands(struct command *commands, const char *unpacker_erro
                set_connectivity_errors(commands);
 
        if (run_receive_hook(commands, pre_receive_hook, 0)) {
-               for (cmd = commands; cmd; cmd = cmd->next)
-                       cmd->error_string = "pre-receive hook declined";
+               for (cmd = commands; cmd; cmd = cmd->next) {
+                       if (!cmd->error_string)
+                               cmd->error_string = "pre-receive hook declined";
+               }
                return;
        }
 
@@ -717,9 +721,15 @@ static void execute_commands(struct command *commands, const char *unpacker_erro
        free(head_name_to_free);
        head_name = head_name_to_free = resolve_refdup("HEAD", sha1, 0, NULL);
 
-       for (cmd = commands; cmd; cmd = cmd->next)
-               if (!cmd->skip_update)
-                       cmd->error_string = update(cmd);
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               if (cmd->error_string)
+                       continue;
+
+               if (cmd->skip_update)
+                       continue;
+
+               cmd->error_string = update(cmd);
+       }
 }
 
 static struct command *read_head_info(void)
index f54a89adc795fe86f76d60cff4de9487bc792bd3..fec92bc66e41b82f929e37b1935d00d6558c34a0 100644 (file)
@@ -16,7 +16,7 @@ static const char * const builtin_remote_usage[] = {
        "git remote [-v | --verbose] show [-n] <name>",
        "git remote prune [-n | --dry-run] <name>",
        "git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]",
-       "git remote set-branches <name> [--add] <branch>...",
+       "git remote set-branches [--add] <name> <branch>...",
        "git remote set-url <name> <newurl> [<oldurl>]",
        "git remote set-url --add <name> <newurl>",
        "git remote set-url --delete <name> <url>",
index ab3be7ca82ea36fbb1e98f8b899970a6748981c6..264e3ae9d840c342499634e21b21c274ebcebacf 100644 (file)
@@ -180,10 +180,10 @@ static void show_object(struct object *obj,
                        const struct name_path *path, const char *component,
                        void *cb_data)
 {
-       struct rev_info *info = cb_data;
+       struct rev_list_info *info = cb_data;
 
        finish_object(obj, path, component, cb_data);
-       if (info->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT)
+       if (info->revs->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT)
                parse_object(obj->sha1);
        show_object_with_name(stdout, obj, path, component);
 }
index 71f258ef6e620979a9dbeaa87d2501a304b3a0fa..9df341c793d58eff215805bf5ca5da383f4a99d9 100644 (file)
@@ -58,7 +58,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
                argv[i++] = "--thin";
        if (args->use_ofs_delta)
                argv[i++] = "--delta-base-offset";
-       if (args->quiet)
+       if (args->quiet || !args->progress)
                argv[i++] = "-q";
        if (args->progress)
                argv[i++] = "--progress";
@@ -250,6 +250,7 @@ int send_pack(struct send_pack_args *args,
        int allow_deleting_refs = 0;
        int status_report = 0;
        int use_sideband = 0;
+       int quiet_supported = 0;
        unsigned cmds_sent = 0;
        int ret;
        struct async demux;
@@ -263,8 +264,8 @@ int send_pack(struct send_pack_args *args,
                args->use_ofs_delta = 1;
        if (server_supports("side-band-64k"))
                use_sideband = 1;
-       if (!server_supports("quiet"))
-               args->quiet = 0;
+       if (server_supports("quiet"))
+               quiet_supported = 1;
 
        if (!remote_refs) {
                fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
@@ -302,17 +303,18 @@ int send_pack(struct send_pack_args *args,
                } else {
                        char *old_hex = sha1_to_hex(ref->old_sha1);
                        char *new_hex = sha1_to_hex(ref->new_sha1);
+                       int quiet = quiet_supported && (args->quiet || !args->progress);
 
                        if (!cmds_sent && (status_report || use_sideband || args->quiet)) {
                                packet_buf_write(&req_buf, "%s %s %s%c%s%s%s",
-                                       old_hex, new_hex, ref->name, 0,
-                                       status_report ? " report-status" : "",
-                                       use_sideband ? " side-band-64k" : "",
-                                       args->quiet ? " quiet" : "");
+                                                old_hex, new_hex, ref->name, 0,
+                                                status_report ? " report-status" : "",
+                                                use_sideband ? " side-band-64k" : "",
+                                                quiet ? " quiet" : "");
                        }
                        else
                                packet_buf_write(&req_buf, "%s %s %s",
-                                       old_hex, new_hex, ref->name);
+                                                old_hex, new_hex, ref->name);
                        ref->status = status_report ?
                                REF_STATUS_EXPECTING_REPORT :
                                REF_STATUS_OK;
index 03df16ac6e0e492483bf3695e0f6b2aa0a95978b..fe7e5e5b3d64dc8168ebe687ac5c6c2d1cbbdba1 100644 (file)
 #include "diff.h"
 #include "revision.h"
 #include "gpg-interface.h"
+#include "sha1-array.h"
 
 static const char * const git_tag_usage[] = {
        "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
        "git tag -d <tagname>...",
-       "git tag -l [-n[<num>]] [<pattern>...]",
+       "git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>] "
+               "\n\t\t[<pattern>...]",
        "git tag -v <tagname>...",
        NULL
 };
@@ -30,6 +32,8 @@ struct tag_filter {
        struct commit_list *with_commit;
 };
 
+static struct sha1_array points_at;
+
 static int match_pattern(const char **patterns, const char *ref)
 {
        /* no pattern means match everything */
@@ -41,6 +45,24 @@ static int match_pattern(const char **patterns, const char *ref)
        return 0;
 }
 
+static const unsigned char *match_points_at(const char *refname,
+                                           const unsigned char *sha1)
+{
+       const unsigned char *tagged_sha1 = NULL;
+       struct object *obj;
+
+       if (sha1_array_lookup(&points_at, sha1) >= 0)
+               return sha1;
+       obj = parse_object(sha1);
+       if (!obj)
+               die(_("malformed object at '%s'"), refname);
+       if (obj->type == OBJ_TAG)
+               tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
+       if (tagged_sha1 && sha1_array_lookup(&points_at, tagged_sha1) >= 0)
+               return tagged_sha1;
+       return NULL;
+}
+
 static int in_commit_list(const struct commit_list *want, struct commit *c)
 {
        for (; want; want = want->next)
@@ -138,6 +160,9 @@ static int show_reference(const char *refname, const unsigned char *sha1,
                                return 0;
                }
 
+               if (points_at.nr && !match_points_at(refname, sha1))
+                       return 0;
+
                if (!filter->lines) {
                        printf("%s\n", refname);
                        return 0;
@@ -383,6 +408,23 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
        return check_refname_format(sb->buf, 0);
 }
 
+static int parse_opt_points_at(const struct option *opt __attribute__((unused)),
+                       const char *arg, int unset)
+{
+       unsigned char sha1[20];
+
+       if (unset) {
+               sha1_array_clear(&points_at);
+               return 0;
+       }
+       if (!arg)
+               return error(_("switch 'points-at' requires an object"));
+       if (get_sha1(arg, sha1))
+               return error(_("malformed object name '%s'"), arg);
+       sha1_array_append(&points_at, sha1);
+       return 0;
+}
+
 int cmd_tag(int argc, const char **argv, const char *prefix)
 {
        struct strbuf buf = STRBUF_INIT;
@@ -425,6 +467,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                        PARSE_OPT_LASTARG_DEFAULT,
                        parse_opt_with_commit, (intptr_t)"HEAD",
                },
+               {
+                       OPTION_CALLBACK, 0, "points-at", NULL, "object",
+                       "print only tags of the object", 0, parse_opt_points_at
+               },
                OPT_END()
        };
 
@@ -456,6 +502,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                die(_("-n option is only allowed with -l."));
        if (with_commit)
                die(_("--contains option is only allowed with -l."));
+       if (points_at.nr)
+               die(_("--points-at option is only allowed with -l."));
        if (delete)
                return for_each_tag_name(argv, delete_tag);
        if (verify)
index b8acf3c18b600f1f413f95744ad281e3879b3f6e..7a760db2fc4ab2a807bead051edbf53182c8326a 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -23,23 +23,6 @@ static void add_to_ref_list(const unsigned char *sha1, const char *name,
        list->nr++;
 }
 
-/* Eventually this should go to strbuf.[ch] */
-static int strbuf_readline_fd(struct strbuf *sb, int fd)
-{
-       strbuf_reset(sb);
-
-       while (1) {
-               char ch;
-               ssize_t len = xread(fd, &ch, 1);
-               if (len <= 0)
-                       return len;
-               strbuf_addch(sb, ch);
-               if (ch == '\n')
-                       break;
-       }
-       return 0;
-}
-
 static int parse_bundle_header(int fd, struct bundle_header *header,
                               const char *report_path)
 {
@@ -47,7 +30,7 @@ static int parse_bundle_header(int fd, struct bundle_header *header,
        int status = 0;
 
        /* The bundle header begins with the signature */
-       if (strbuf_readline_fd(&buf, fd) ||
+       if (strbuf_getwholeline_fd(&buf, fd, '\n') ||
            strcmp(buf.buf, bundle_signature)) {
                if (report_path)
                        error("'%s' does not look like a v2 bundle file",
@@ -57,7 +40,7 @@ static int parse_bundle_header(int fd, struct bundle_header *header,
        }
 
        /* The bundle header ends with an empty line */
-       while (!strbuf_readline_fd(&buf, fd) &&
+       while (!strbuf_getwholeline_fd(&buf, fd, '\n') &&
               buf.len && buf.buf[0] != '\n') {
                unsigned char sha1[20];
                int is_prereq = 0;
@@ -251,7 +234,7 @@ int create_bundle(struct bundle_header *header, const char *path,
        const char **argv_boundary = xmalloc((argc + 4) * sizeof(const char *));
        const char **argv_pack = xmalloc(6 * sizeof(const char *));
        int i, ref_count = 0;
-       char buffer[1024];
+       struct strbuf buf = STRBUF_INIT;
        struct rev_info revs;
        struct child_process rls;
        FILE *rls_fout;
@@ -283,20 +266,21 @@ int create_bundle(struct bundle_header *header, const char *path,
        if (start_command(&rls))
                return -1;
        rls_fout = xfdopen(rls.out, "r");
-       while (fgets(buffer, sizeof(buffer), rls_fout)) {
+       while (strbuf_getwholeline(&buf, rls_fout, '\n') != EOF) {
                unsigned char sha1[20];
-               if (buffer[0] == '-') {
-                       write_or_die(bundle_fd, buffer, strlen(buffer));
-                       if (!get_sha1_hex(buffer + 1, sha1)) {
+               if (buf.len > 0 && buf.buf[0] == '-') {
+                       write_or_die(bundle_fd, buf.buf, buf.len);
+                       if (!get_sha1_hex(buf.buf + 1, sha1)) {
                                struct object *object = parse_object(sha1);
                                object->flags |= UNINTERESTING;
-                               add_pending_object(&revs, object, buffer);
+                               add_pending_object(&revs, object, buf.buf);
                        }
-               } else if (!get_sha1_hex(buffer, sha1)) {
+               } else if (!get_sha1_hex(buf.buf, sha1)) {
                        struct object *object = parse_object(sha1);
                        object->flags |= SHOWN;
                }
        }
+       strbuf_release(&buf);
        fclose(rls_fout);
        if (finish_command(&rls))
                return error("rev-list died");
diff --git a/cache.h b/cache.h
index ae0396b84d566108a6b43dfd3ffed3a82f07d2a5..e12b15f4b92da906c50f7f51df95b126347e9441 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -1115,6 +1115,8 @@ 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_from_parameters(config_fn_t fn, void *data);
 extern int git_config(config_fn_t fn, void *);
+extern int git_config_with_options(config_fn_t fn, void *,
+                                  const char *filename, int respect_includes);
 extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
 extern int git_parse_ulong(const char *, unsigned long *);
 extern int git_config_int(const char *, const char *);
@@ -1130,6 +1132,7 @@ extern int git_config_parse_key(const char *, char **, int *);
 extern int git_config_set_multivar(const char *, const char *, const char *, int);
 extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int);
 extern int git_config_rename_section(const char *, const char *);
+extern int git_config_rename_section_in_file(const char *, const char *, const char *);
 extern const char *git_etc_gitconfig(void);
 extern int check_repository_format_version(const char *var, const char *value, void *cb);
 extern int git_env_bool(const char *, int);
@@ -1140,7 +1143,13 @@ extern const char *get_commit_output_encoding(void);
 
 extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data);
 
-extern const char *config_exclusive_filename;
+struct config_include_data {
+       int depth;
+       config_fn_t fn;
+       void *data;
+};
+#define CONFIG_INCLUDE_INIT { 0 }
+extern int git_config_include(const char *name, const char *value, void *data);
 
 #define MAX_GITNAME (1000)
 extern char git_default_email[MAX_GITNAME];
@@ -1177,6 +1186,8 @@ extern void setup_pager(void);
 extern const char *pager_program;
 extern int pager_in_use(void);
 extern int pager_use_color;
+extern int term_columns(void);
+extern int decimal_width(int);
 
 extern const char *editor_program;
 extern const char *askpass_program;
index 40f9c6d10317ed47f7786e5c328df3ab6f167e7c..ad0390819d2701d6153adf9db2947ee4908742ce 100644 (file)
--- a/config.c
+++ b/config.c
@@ -26,7 +26,68 @@ static config_file *cf;
 
 static int zlib_compression_seen;
 
-const char *config_exclusive_filename = NULL;
+#define MAX_INCLUDE_DEPTH 10
+static const char include_depth_advice[] =
+"exceeded maximum include depth (%d) while including\n"
+"      %s\n"
+"from\n"
+"      %s\n"
+"Do you have circular includes?";
+static int handle_path_include(const char *path, struct config_include_data *inc)
+{
+       int ret = 0;
+       struct strbuf buf = STRBUF_INIT;
+
+       /*
+        * Use an absolute path as-is, but interpret relative paths
+        * based on the including config file.
+        */
+       if (!is_absolute_path(path)) {
+               char *slash;
+
+               if (!cf || !cf->name)
+                       return error("relative config includes must come from files");
+
+               slash = find_last_dir_sep(cf->name);
+               if (slash)
+                       strbuf_add(&buf, cf->name, slash - cf->name + 1);
+               strbuf_addstr(&buf, path);
+               path = buf.buf;
+       }
+
+       if (!access(path, R_OK)) {
+               if (++inc->depth > MAX_INCLUDE_DEPTH)
+                       die(include_depth_advice, MAX_INCLUDE_DEPTH, path,
+                           cf && cf->name ? cf->name : "the command line");
+               ret = git_config_from_file(git_config_include, path, inc);
+               inc->depth--;
+       }
+       strbuf_release(&buf);
+       return ret;
+}
+
+int git_config_include(const char *var, const char *value, void *data)
+{
+       struct config_include_data *inc = data;
+       const char *type;
+       int ret;
+
+       /*
+        * Pass along all values, including "include" directives; this makes it
+        * possible to query information on the includes themselves.
+        */
+       ret = inc->fn(var, value, inc->data);
+       if (ret < 0)
+               return ret;
+
+       type = skip_prefix(var, "include.");
+       if (!type)
+               return ret;
+
+       if (!strcmp(type, "path"))
+               ret = handle_path_include(value, inc);
+       return ret;
+}
 
 static void lowercase(char *p)
 {
@@ -879,9 +940,6 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
        int ret = 0, found = 0;
        const char *home = NULL;
 
-       /* Setting $GIT_CONFIG makes git read _only_ the given config file. */
-       if (config_exclusive_filename)
-               return git_config_from_file(fn, config_exclusive_filename, data);
        if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) {
                ret += git_config_from_file(fn, git_etc_gitconfig(),
                                            data);
@@ -917,10 +975,26 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
        return ret == 0 ? found : ret;
 }
 
-int git_config(config_fn_t fn, void *data)
+int git_config_with_options(config_fn_t fn, void *data,
+                           const char *filename, int respect_includes)
 {
        char *repo_config = NULL;
        int ret;
+       struct config_include_data inc = CONFIG_INCLUDE_INIT;
+
+       if (respect_includes) {
+               inc.fn = fn;
+               inc.data = data;
+               fn = git_config_include;
+               data = &inc;
+       }
+
+       /*
+        * If we have a specific filename, use it. Otherwise, follow the
+        * regular lookup sequence.
+        */
+       if (filename)
+               return git_config_from_file(fn, filename, data);
 
        repo_config = git_pathdup("config");
        ret = git_config_early(fn, data, repo_config);
@@ -929,6 +1003,11 @@ int git_config(config_fn_t fn, void *data)
        return ret;
 }
 
+int git_config(config_fn_t fn, void *data)
+{
+       return git_config_with_options(fn, data, NULL, 1);
+}
+
 /*
  * Find all the stuff for git_config_set() below.
  */
@@ -1233,6 +1312,7 @@ int git_config_set_multivar_in_file(const char *config_filename,
        int fd = -1, in_fd;
        int ret;
        struct lock_file *lock = NULL;
+       char *filename_buf = NULL;
 
        /* parse-key returns negative; flip the sign to feed exit(3) */
        ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
@@ -1241,6 +1321,8 @@ int git_config_set_multivar_in_file(const char *config_filename,
 
        store.multi_replace = multi_replace;
 
+       if (!config_filename)
+               config_filename = filename_buf = git_pathdup("config");
 
        /*
         * The lock serves a purpose in addition to locking: the new
@@ -1410,6 +1492,7 @@ int git_config_set_multivar_in_file(const char *config_filename,
 out_free:
        if (lock)
                rollback_lock_file(lock);
+       free(filename_buf);
        return ret;
 
 write_err_out:
@@ -1421,19 +1504,8 @@ int git_config_set_multivar_in_file(const char *config_filename,
 int git_config_set_multivar(const char *key, const char *value,
                        const char *value_regex, int multi_replace)
 {
-       const char *config_filename;
-       char *buf = NULL;
-       int ret;
-
-       if (config_exclusive_filename)
-               config_filename = config_exclusive_filename;
-       else
-               config_filename = buf = git_pathdup("config");
-
-       ret = git_config_set_multivar_in_file(config_filename, key, value,
-                                       value_regex, multi_replace);
-       free(buf);
-       return ret;
+       return git_config_set_multivar_in_file(NULL, key, value, value_regex,
+                                              multi_replace);
 }
 
 static int section_name_match (const char *buf, const char *name)
@@ -1476,19 +1548,19 @@ static int section_name_match (const char *buf, const char *name)
 }
 
 /* if new_name == NULL, the section is removed instead */
-int git_config_rename_section(const char *old_name, const char *new_name)
+int git_config_rename_section_in_file(const char *config_filename,
+                                     const char *old_name, const char *new_name)
 {
        int ret = 0, remove = 0;
-       char *config_filename;
+       char *filename_buf = NULL;
        struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1);
        int out_fd;
        char buf[1024];
        FILE *config_file;
 
-       if (config_exclusive_filename)
-               config_filename = xstrdup(config_exclusive_filename);
-       else
-               config_filename = git_pathdup("config");
+       if (!config_filename)
+               config_filename = filename_buf = git_pathdup("config");
+
        out_fd = hold_lock_file_for_update(lock, config_filename, 0);
        if (out_fd < 0) {
                ret = error("could not lock config file %s", config_filename);
@@ -1552,10 +1624,15 @@ int git_config_rename_section(const char *old_name, const char *new_name)
        if (commit_lock_file(lock) < 0)
                ret = error("could not commit config file %s", config_filename);
 out:
-       free(config_filename);
+       free(filename_buf);
        return ret;
 }
 
+int git_config_rename_section(const char *old_name, const char *new_name)
+{
+       return git_config_rename_section_in_file(NULL, old_name, new_name);
+}
+
 /*
  * Call this to report error for your variable that should not
  * get a boolean value (i.e. "[my] var" means "true").
index 24190de616eae048fbd24839259fc8f160c52d21..8bb0f44b489e0e963c70e88d04e5878dd78012d5 100644 (file)
@@ -640,7 +640,18 @@ AC_CHECK_LIB([c], [gettext],
 [LIBC_CONTAINS_LIBINTL=YesPlease],
 [LIBC_CONTAINS_LIBINTL=])
 AC_SUBST(LIBC_CONTAINS_LIBINTL)
-test -n "$LIBC_CONTAINS_LIBINTL" || LIBS="$LIBS -lintl"
+
+#
+# Define NO_GETTEXT if you don't want Git output to be translated.
+# A translated Git requires GNU libintl or another gettext implementation
+AC_CHECK_HEADER([libintl.h],
+[NO_GETTEXT=],
+[NO_GETTEXT=YesPlease])
+AC_SUBST(NO_GETTEXT)
+
+if test -z "$NO_GETTEXT"; then
+    test -n "$LIBC_CONTAINS_LIBINTL" || LIBS="$LIBS -lintl"
+fi
 
 ## Checks for header files.
 AC_MSG_NOTICE([CHECKS for header files])
@@ -824,13 +835,6 @@ AC_CHECK_HEADER([paths.h],
 [HAVE_PATHS_H=])
 AC_SUBST(HAVE_PATHS_H)
 #
-# Define NO_GETTEXT if you don't want Git output to be translated.
-# A translated Git requires GNU libintl or another gettext implementation
-AC_CHECK_HEADER([libintl.h],
-[NO_GETTEXT=],
-[NO_GETTEXT=YesPlease])
-AC_SUBST(NO_GETTEXT)
-#
 # Define HAVE_LIBCHARSET_H if have libcharset.h
 AC_CHECK_HEADER([libcharset.h],
 [HAVE_LIBCHARSET_H=YesPlease],
index 1505cff12d3ee01e868219eed3fcf459dc196543..554e30e961b1459817442c2eb11166c768fa0ec2 100755 (executable)
 #       per-repository basis by setting the bash.showUpstream config
 #       variable.
 #
-#
-# To submit patches:
-#
-#    *) Read Documentation/SubmittingPatches
-#    *) Send all patches to the current maintainer:
-#
-#       "Shawn O. Pearce" <spearce@spearce.org>
-#
-#    *) Always CC the Git mailing list:
-#
-#       git@vger.kernel.org
-#
 
 if [[ -n ${ZSH_VERSION-} ]]; then
        autoload -U +X bashcompinit && bashcompinit
@@ -298,13 +286,13 @@ __git_ps1 ()
                                fi
                        fi
                        if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
-                               git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
+                               git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
                        fi
 
                        if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then
-                          if [ -n "$(git ls-files --others --exclude-standard)" ]; then
-                             u="%"
-                          fi
+                               if [ -n "$(git ls-files --others --exclude-standard)" ]; then
+                                       u="%"
+                               fi
                        fi
 
                        if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
@@ -313,7 +301,7 @@ __git_ps1 ()
                fi
 
                local f="$w$i$s$u"
-               printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
+               printf -- "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
        fi
 }
 
@@ -2512,7 +2500,7 @@ _git_svn ()
                        __gitcomp "
                                --merge --strategy= --verbose --dry-run
                                --fetch-all --no-rebase --commit-url
-                               --revision $cmt_opts $fc_opts
+                               --revision --interactive $cmt_opts $fc_opts
                                "
                        ;;
                set-tree,--*)
index 1b7b6df8ebbdfccab8a632d883c321b4ee2919a4..502e03b3058e947835d396540af39c83a45d42aa 100644 (file)
@@ -14,13 +14,15 @@ Instead, this script post-processes the line-oriented diff, finds pairs
 of lines, and highlights the differing segments.  It's currently very
 simple and stupid about doing these tasks. In particular:
 
-  1. It will only highlight a pair of lines if they are the only two
-     lines in a hunk.  It could instead try to match up "before" and
-     "after" lines for a given hunk into pairs of similar lines.
-     However, this may end up visually distracting, as the paired
-     lines would have other highlighted lines in between them. And in
-     practice, the lines which most need attention called to their
-     small, hard-to-see changes are touching only a single line.
+  1. It will only highlight hunks in which the number of removed and
+     added lines is the same, and it will pair lines within the hunk by
+     position (so the first removed line is compared to the first added
+     line, and so forth). This is simple and tends to work well in
+     practice. More complex changes don't highlight well, so we tend to
+     exclude them due to the "same number of removed and added lines"
+     restriction. Or even if we do try to highlight them, they end up
+     not highlighting because of our "don't highlight if the whole line
+     would be highlighted" rule.
 
   2. It will find the common prefix and suffix of two lines, and
      consider everything in the middle to be "different". It could
@@ -55,3 +57,96 @@ following in your git configuration:
        show = diff-highlight | less
        diff = diff-highlight | less
 ---------------------------------------------
+
+Bugs
+----
+
+Because diff-highlight relies on heuristics to guess which parts of
+changes are important, there are some cases where the highlighting is
+more distracting than useful. Fortunately, these cases are rare in
+practice, and when they do occur, the worst case is simply a little
+extra highlighting. This section documents some cases known to be
+sub-optimal, in case somebody feels like working on improving the
+heuristics.
+
+1. Two changes on the same line get highlighted in a blob. For example,
+   highlighting:
+
+----------------------------------------------
+-foo(buf, size);
++foo(obj->buf, obj->size);
+----------------------------------------------
+
+   yields (where the inside of "+{}" would be highlighted):
+
+----------------------------------------------
+-foo(buf, size);
++foo(+{obj->buf, obj->}size);
+----------------------------------------------
+
+   whereas a more semantically meaningful output would be:
+
+----------------------------------------------
+-foo(buf, size);
++foo(+{obj->}buf, +{obj->}size);
+----------------------------------------------
+
+   Note that doing this right would probably involve a set of
+   content-specific boundary patterns, similar to word-diff. Otherwise
+   you get junk like:
+
+-----------------------------------------------------
+-this line has some -{i}nt-{ere}sti-{ng} text on it
++this line has some +{fa}nt+{a}sti+{c} text on it
+-----------------------------------------------------
+
+   which is less readable than the current output.
+
+2. The multi-line matching assumes that lines in the pre- and post-image
+   match by position. This is often the case, but can be fooled when a
+   line is removed from the top and a new one added at the bottom (or
+   vice versa). Unless the lines in the middle are also changed, diffs
+   will show this as two hunks, and it will not get highlighted at all
+   (which is good). But if the lines in the middle are changed, the
+   highlighting can be misleading. Here's a pathological case:
+
+-----------------------------------------------------
+-one
+-two
+-three
+-four
++two 2
++three 3
++four 4
++five 5
+-----------------------------------------------------
+
+   which gets highlighted as:
+
+-----------------------------------------------------
+-one
+-t-{wo}
+-three
+-f-{our}
++two 2
++t+{hree 3}
++four 4
++f+{ive 5}
+-----------------------------------------------------
+
+   because it matches "two" to "three 3", and so forth. It would be
+   nicer as:
+
+-----------------------------------------------------
+-one
+-two
+-three
+-four
++two +{2}
++three +{3}
++four +{4}
++five 5
+-----------------------------------------------------
+
+   which would probably involve pre-matching the lines into pairs
+   according to some heuristic.
index d8938982e413a9bf994bd12386121249c888649d..c4404d49c9968608510809309b26e2d08eec8810 100755 (executable)
@@ -1,28 +1,37 @@
 #!/usr/bin/perl
 
+use warnings FATAL => 'all';
+use strict;
+
 # Highlight by reversing foreground and background. You could do
 # other things like bold or underline if you prefer.
 my $HIGHLIGHT   = "\x1b[7m";
 my $UNHIGHLIGHT = "\x1b[27m";
 my $COLOR = qr/\x1b\[[0-9;]*m/;
+my $BORING = qr/$COLOR|\s/;
 
-my @window;
+my @removed;
+my @added;
+my $in_hunk;
 
 while (<>) {
-       # We highlight only single-line changes, so we need
-       # a 4-line window to make a decision on whether
-       # to highlight.
-       push @window, $_;
-       next if @window < 4;
-       if ($window[0] =~ /^$COLOR*(\@| )/ &&
-           $window[1] =~ /^$COLOR*-/ &&
-           $window[2] =~ /^$COLOR*\+/ &&
-           $window[3] !~ /^$COLOR*\+/) {
-               print shift @window;
-               show_pair(shift @window, shift @window);
+       if (!$in_hunk) {
+               print;
+               $in_hunk = /^$COLOR*\@/;
+       }
+       elsif (/^$COLOR*-/) {
+               push @removed, $_;
+       }
+       elsif (/^$COLOR*\+/) {
+               push @added, $_;
        }
        else {
-               print shift @window;
+               show_hunk(\@removed, \@added);
+               @removed = ();
+               @added = ();
+
+               print;
+               $in_hunk = /^$COLOR*[\@ ]/;
        }
 
        # Most of the time there is enough output to keep things streaming,
@@ -38,23 +47,40 @@ while (<>) {
        }
 }
 
-# Special case a single-line hunk at the end of file.
-if (@window == 3 &&
-    $window[0] =~ /^$COLOR*(\@| )/ &&
-    $window[1] =~ /^$COLOR*-/ &&
-    $window[2] =~ /^$COLOR*\+/) {
-       print shift @window;
-       show_pair(shift @window, shift @window);
-}
-
-# And then flush any remaining lines.
-while (@window) {
-       print shift @window;
-}
+# Flush any queued hunk (this can happen when there is no trailing context in
+# the final diff of the input).
+show_hunk(\@removed, \@added);
 
 exit 0;
 
-sub show_pair {
+sub show_hunk {
+       my ($a, $b) = @_;
+
+       # If one side is empty, then there is nothing to compare or highlight.
+       if (!@$a || !@$b) {
+               print @$a, @$b;
+               return;
+       }
+
+       # If we have mismatched numbers of lines on each side, we could try to
+       # be clever and match up similar lines. But for now we are simple and
+       # stupid, and only handle multi-line hunks that remove and add the same
+       # number of lines.
+       if (@$a != @$b) {
+               print @$a, @$b;
+               return;
+       }
+
+       my @queue;
+       for (my $i = 0; $i < @$a; $i++) {
+               my ($rm, $add) = highlight_pair($a->[$i], $b->[$i]);
+               print $rm;
+               push @queue, $add;
+       }
+       print @queue;
+}
+
+sub highlight_pair {
        my @a = split_line(shift);
        my @b = split_line(shift);
 
@@ -101,8 +127,14 @@ sub show_pair {
                }
        }
 
-       print highlight(\@a, $pa, $sa);
-       print highlight(\@b, $pb, $sb);
+       if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) {
+               return highlight_line(\@a, $pa, $sa),
+                      highlight_line(\@b, $pb, $sb);
+       }
+       else {
+               return join('', @a),
+                      join('', @b);
+       }
 }
 
 sub split_line {
@@ -111,7 +143,7 @@ sub split_line {
               split /($COLOR*)/;
 }
 
-sub highlight {
+sub highlight_line {
        my ($line, $prefix, $suffix) = @_;
 
        return join('',
@@ -122,3 +154,20 @@ sub highlight {
                @{$line}[($suffix+1)..$#$line]
        );
 }
+
+# Pairs are interesting to highlight only if we are going to end up
+# highlighting a subset (i.e., not the whole line). Otherwise, the highlighting
+# is just useless noise. We can detect this by finding either a matching prefix
+# or suffix (disregarding boring bits like whitespace and colorization).
+sub is_pair_interesting {
+       my ($a, $pa, $sa, $b, $pb, $sb) = @_;
+       my $prefix_a = join('', @$a[0..($pa-1)]);
+       my $prefix_b = join('', @$b[0..($pb-1)]);
+       my $suffix_a = join('', @$a[($sa+1)..$#$a]);
+       my $suffix_b = join('', @$b[($sb+1)..$#$b]);
+
+       return $prefix_a !~ /^$COLOR*-$BORING*$/ ||
+              $prefix_b !~ /^$COLOR*\+$BORING*$/ ||
+              $suffix_a !~ /^$BORING*$/ ||
+              $suffix_b !~ /^$BORING*$/;
+}
index a78d9c54931c7fc1f6a693a7f185f08bcb8f6861..c5362c4c11d00e169781ff7da89c88a3051f6227 100755 (executable)
@@ -10,7 +10,7 @@
 
 import optparse, sys, os, marshal, subprocess, shelve
 import tempfile, getopt, os.path, time, platform
-import re
+import re, shutil
 
 verbose = False
 
@@ -38,7 +38,7 @@ def p4_build_cmd(cmd):
 
     host = gitConfig("git-p4.host")
     if len(host) > 0:
-        real_cmd += ["-h", host]
+        real_cmd += ["-H", host]
 
     client = gitConfig("git-p4.client")
     if len(client) > 0:
@@ -186,6 +186,47 @@ def split_p4_type(p4type):
         mods = s[1]
     return (base, mods)
 
+#
+# return the raw p4 type of a file (text, text+ko, etc)
+#
+def p4_type(file):
+    results = p4CmdList(["fstat", "-T", "headType", file])
+    return results[0]['headType']
+
+#
+# Given a type base and modifier, return a regexp matching
+# the keywords that can be expanded in the file
+#
+def p4_keywords_regexp_for_type(base, type_mods):
+    if base in ("text", "unicode", "binary"):
+        kwords = None
+        if "ko" in type_mods:
+            kwords = 'Id|Header'
+        elif "k" in type_mods:
+            kwords = 'Id|Header|Author|Date|DateTime|Change|File|Revision'
+        else:
+            return None
+        pattern = r"""
+            \$              # Starts with a dollar, followed by...
+            (%s)            # one of the keywords, followed by...
+            (:[^$]+)?       # possibly an old expansion, followed by...
+            \$              # another dollar
+            """ % kwords
+        return pattern
+    else:
+        return None
+
+#
+# Given a file, return a regexp matching the possible
+# RCS keywords that will be expanded, or None for files
+# with kw expansion turned off.
+#
+def p4_keywords_regexp_for_file(file):
+    if not os.path.exists(file):
+        return None
+    else:
+        (type_base, type_mods) = split_p4_type(p4_type(file))
+        return p4_keywords_regexp_for_type(type_base, type_mods)
 
 def setP4ExecBit(file, mode):
     # Reopens an already open file and changes the execute bit to match
@@ -555,6 +596,46 @@ def p4PathStartsWith(path, prefix):
         return path.lower().startswith(prefix.lower())
     return path.startswith(prefix)
 
+def getClientSpec():
+    """Look at the p4 client spec, create a View() object that contains
+       all the mappings, and return it."""
+
+    specList = p4CmdList("client -o")
+    if len(specList) != 1:
+        die('Output from "client -o" is %d lines, expecting 1' %
+            len(specList))
+
+    # dictionary of all client parameters
+    entry = specList[0]
+
+    # just the keys that start with "View"
+    view_keys = [ k for k in entry.keys() if k.startswith("View") ]
+
+    # hold this new View
+    view = View()
+
+    # append the lines, in order, to the view
+    for view_num in range(len(view_keys)):
+        k = "View%d" % view_num
+        if k not in view_keys:
+            die("Expected view key %s missing" % k)
+        view.append(entry[k])
+
+    return view
+
+def getClientRoot():
+    """Grab the client directory."""
+
+    output = p4CmdList("client -o")
+    if len(output) != 1:
+        die('Output from "client -o" is %d lines, expecting 1' % len(output))
+
+    entry = output[0]
+    if "Root" not in entry:
+        die('Client has no "Root"')
+
+    return entry["Root"]
+
 class Command:
     def __init__(self):
         self.usage = "usage: %prog [options]"
@@ -753,6 +834,29 @@ class P4Submit(Command, P4UserMap):
 
         return result
 
+    def patchRCSKeywords(self, file, pattern):
+        # Attempt to zap the RCS keywords in a p4 controlled file matching the given pattern
+        (handle, outFileName) = tempfile.mkstemp(dir='.')
+        try:
+            outFile = os.fdopen(handle, "w+")
+            inFile = open(file, "r")
+            regexp = re.compile(pattern, re.VERBOSE)
+            for line in inFile.readlines():
+                line = regexp.sub(r'$\1$', line)
+                outFile.write(line)
+            inFile.close()
+            outFile.close()
+            # Forcibly overwrite the original file
+            os.unlink(file)
+            shutil.move(outFileName, file)
+        except:
+            # cleanup our temporary file
+            os.unlink(outFileName)
+            print "Failed to strip RCS keywords in %s" % file
+            raise
+
+        print "Patched up RCS keywords in %s" % file
+
     def p4UserForCommit(self,id):
         # Return the tuple (perforce user,git email) for a given git commit id
         self.getUserMapFromPerforceServer()
@@ -918,6 +1022,7 @@ class P4Submit(Command, P4UserMap):
         filesToDelete = set()
         editedFiles = set()
         filesToChangeExecBit = {}
+
         for line in diff:
             diff = parseDiffTreeEntry(line)
             modifier = diff['status']
@@ -964,9 +1069,45 @@ class P4Submit(Command, P4UserMap):
         patchcmd = diffcmd + " | git apply "
         tryPatchCmd = patchcmd + "--check -"
         applyPatchCmd = patchcmd + "--check --apply -"
+        patch_succeeded = True
 
         if os.system(tryPatchCmd) != 0:
+            fixed_rcs_keywords = False
+            patch_succeeded = False
             print "Unfortunately applying the change failed!"
+
+            # Patch failed, maybe it's just RCS keyword woes. Look through
+            # the patch to see if that's possible.
+            if gitConfig("git-p4.attemptRCSCleanup","--bool") == "true":
+                file = None
+                pattern = None
+                kwfiles = {}
+                for file in editedFiles | filesToDelete:
+                    # did this file's delta contain RCS keywords?
+                    pattern = p4_keywords_regexp_for_file(file)
+
+                    if pattern:
+                        # this file is a possibility...look for RCS keywords.
+                        regexp = re.compile(pattern, re.VERBOSE)
+                        for line in read_pipe_lines(["git", "diff", "%s^..%s" % (id, id), file]):
+                            if regexp.search(line):
+                                if verbose:
+                                    print "got keyword match on %s in %s in %s" % (pattern, line, file)
+                                kwfiles[file] = pattern
+                                break
+
+                for file in kwfiles:
+                    if verbose:
+                        print "zapping %s with %s" % (line,pattern)
+                    self.patchRCSKeywords(file, kwfiles[file])
+                    fixed_rcs_keywords = True
+
+            if fixed_rcs_keywords:
+                print "Retrying the patch with RCS keywords cleaned up"
+                if os.system(tryPatchCmd) == 0:
+                    patch_succeeded = True
+
+        if not patch_succeeded:
             print "What do you want to do?"
             response = "x"
             while response != "s" and response != "a" and response != "w":
@@ -1119,11 +1260,20 @@ class P4Submit(Command, P4UserMap):
             print "Internal error: cannot locate perforce depot path from existing branches"
             sys.exit(128)
 
-        self.clientPath = p4Where(self.depotPath)
+        self.useClientSpec = False
+        if gitConfig("git-p4.useclientspec", "--bool") == "true":
+            self.useClientSpec = True
+        if self.useClientSpec:
+            self.clientSpecDirs = getClientSpec()
 
-        if len(self.clientPath) == 0:
-            print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath
-            sys.exit(128)
+        if self.useClientSpec:
+            # all files are relative to the client spec
+            self.clientPath = getClientRoot()
+        else:
+            self.clientPath = p4Where(self.depotPath)
+
+        if self.clientPath == "":
+            die("Error: Cannot locate perforce checkout of %s in client view" % self.depotPath)
 
         print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
         self.oldWorkingDirectory = os.getcwd()
@@ -1429,6 +1579,7 @@ class P4Sync(Command, P4UserMap):
         self.p4BranchesInGit = []
         self.cloneExclude = []
         self.useClientSpec = False
+        self.useClientSpec_from_options = False
         self.clientSpecDirs = None
         self.tempBranches = []
         self.tempBranchLocation = "git-p4-tmp"
@@ -1585,15 +1736,12 @@ class P4Sync(Command, P4UserMap):
 
         # Note that we do not try to de-mangle keywords on utf16 files,
         # even though in theory somebody may want that.
-        if type_base in ("text", "unicode", "binary"):
-            if "ko" in type_mods:
-                text = ''.join(contents)
-                text = re.sub(r'\$(Id|Header):[^$]*\$', r'$\1$', text)
-                contents = [ text ]
-            elif "k" in type_mods:
-                text = ''.join(contents)
-                text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$', r'$\1$', text)
-                contents = [ text ]
+        pattern = p4_keywords_regexp_for_type(type_base, type_mods)
+        if pattern:
+            regexp = re.compile(pattern, re.VERBOSE)
+            text = ''.join(contents)
+            text = regexp.sub(r'$\1$', text)
+            contents = [ text ]
 
         self.gitStream.write("M %s inline %s\n" % (git_mode, relPath))
 
@@ -2125,33 +2273,6 @@ class P4Sync(Command, P4UserMap):
             print self.gitError.read()
 
 
-    def getClientSpec(self):
-        specList = p4CmdList("client -o")
-        if len(specList) != 1:
-            die('Output from "client -o" is %d lines, expecting 1' %
-                len(specList))
-
-        # dictionary of all client parameters
-        entry = specList[0]
-
-        # just the keys that start with "View"
-        view_keys = [ k for k in entry.keys() if k.startswith("View") ]
-
-        # hold this new View
-        view = View()
-
-        # append the lines, in order, to the view
-        for view_num in range(len(view_keys)):
-            k = "View%d" % view_num
-            if k not in view_keys:
-                die("Expected view key %s missing" % k)
-            view.append(entry[k])
-
-        self.clientSpecDirs = view
-        if self.verbose:
-            for i, m in enumerate(self.clientSpecDirs.mappings):
-                    print "clientSpecDirs %d: %s" % (i, str(m))
-
     def run(self, args):
         self.depotPaths = []
         self.changeRange = ""
@@ -2184,11 +2305,15 @@ class P4Sync(Command, P4UserMap):
             if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
                 system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
 
-        if not self.useClientSpec:
+        # accept either the command-line option, or the configuration variable
+        if self.useClientSpec:
+            # will use this after clone to set the variable
+            self.useClientSpec_from_options = True
+        else:
             if gitConfig("git-p4.useclientspec", "--bool") == "true":
                 self.useClientSpec = True
         if self.useClientSpec:
-            self.getClientSpec()
+            self.clientSpecDirs = getClientSpec()
 
         # TODO: should always look at previous commits,
         # merge with previous imports, if possible.
@@ -2509,6 +2634,10 @@ class P4Clone(P4Sync):
             else:
                 print "Could not detect main branch. No checkout/master branch created."
 
+        # auto-set this variable if invoked with --use-client-spec
+        if self.useClientSpec_from_options:
+            system("git config --bool git-p4.useclientspec true")
+
         return True
 
 class P4Branches(Command):
index 12868ed7bda11648704ffe4e5d3415067764a6e2..4534e2c2b003998a590637fc43c0549ff23f71b2 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -2,6 +2,7 @@
 #include "attr.h"
 #include "run-command.h"
 #include "quote.h"
+#include "sigchain.h"
 
 /*
  * convert.c - convert a file when checking it out and checking it in.
@@ -195,9 +196,17 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
        char *dst;
 
        if (crlf_action == CRLF_BINARY ||
-           (crlf_action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE) || !len)
+           (crlf_action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE) ||
+           (src && !len))
                return 0;
 
+       /*
+        * If we are doing a dry-run and have no source buffer, there is
+        * nothing to analyze; we must assume we would convert.
+        */
+       if (!buf && !src)
+               return 1;
+
        gather_stats(src, len, &stats);
 
        if (crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS) {
@@ -231,6 +240,13 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
        if (!stats.cr)
                return 0;
 
+       /*
+        * At this point all of our source analysis is done, and we are sure we
+        * would convert. If we are in dry-run mode, we can give an answer.
+        */
+       if (!buf)
+               return 1;
+
        /* only grow if not in place */
        if (strbuf_avail(buf) + buf->len < len)
                strbuf_grow(buf, len - buf->len);
@@ -360,12 +376,16 @@ static int filter_buffer(int in, int out, void *data)
        if (start_command(&child_process))
                return error("cannot fork to run external filter %s", params->cmd);
 
+       sigchain_push(SIGPIPE, SIG_IGN);
+
        write_err = (write_in_full(child_process.in, params->src, params->size) < 0);
        if (close(child_process.in))
                write_err = 1;
        if (write_err)
                error("cannot feed the input to external filter %s", params->cmd);
 
+       sigchain_pop(SIGPIPE);
+
        status = finish_command(&child_process);
        if (status)
                error("external filter %s failed %d", params->cmd, status);
@@ -391,6 +411,9 @@ static int apply_filter(const char *path, const char *src, size_t len,
        if (!cmd)
                return 0;
 
+       if (!dst)
+               return 1;
+
        memset(&async, 0, sizeof(async));
        async.proc = filter_buffer;
        async.data = &params;
@@ -522,9 +545,12 @@ static int ident_to_git(const char *path, const char *src, size_t len,
 {
        char *dst, *dollar;
 
-       if (!ident || !count_ident(src, len))
+       if (!ident || (src && !count_ident(src, len)))
                return 0;
 
+       if (!buf)
+               return 1;
+
        /* only grow if not in place */
        if (strbuf_avail(buf) + buf->len < len)
                strbuf_grow(buf, len - buf->len);
@@ -754,13 +780,13 @@ int convert_to_git(const char *path, const char *src, size_t len,
                filter = ca.drv->clean;
 
        ret |= apply_filter(path, src, len, dst, filter);
-       if (ret) {
+       if (ret && dst) {
                src = dst->buf;
                len = dst->len;
        }
        ca.crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr);
        ret |= crlf_to_git(path, src, len, dst, ca.crlf_action, checksafe);
-       if (ret) {
+       if (ret && dst) {
                src = dst->buf;
                len = dst->len;
        }
index d799a165b4731f2a44a44eaceffc588d1e9fcfc3..ec5fd69430908915cf9c0de62a2021882fd2c658 100644 (file)
--- a/convert.h
+++ b/convert.h
@@ -40,6 +40,11 @@ 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);
+static inline int would_convert_to_git(const char *path, const char *src,
+                                      size_t len, enum safe_crlf checksafe)
+{
+       return convert_to_git(path, src, len, NULL, checksafe);
+}
 
 /*****************************************************************
  *
diff --git a/diff.c b/diff.c
index 3550c18e390cac7a3cce8fcf93074c7178bf1994..a1c06b554b80e433dcb735c8e0adf186b6252ae5 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -1273,13 +1273,15 @@ const char mime_boundary_leader[] = "------------";
 
 static int scale_linear(int it, int width, int max_change)
 {
+       if (!it)
+               return 0;
        /*
-        * make sure that at least one '-' is printed if there were deletions,
-        * and likewise for '+'.
+        * make sure that at least one '-' or '+' is printed if
+        * there is any change to this path. The easiest way is to
+        * scale linearly as if the alloted width is one column shorter
+        * than it is, and then add 1 to the result.
         */
-       if (max_change < 2)
-               return it;
-       return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1);
+       return 1 + (it * (width - 1) / max_change);
 }
 
 static void show_name(FILE *file,
@@ -1495,8 +1497,19 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                dels += del;
 
                if (width <= max_change) {
-                       add = scale_linear(add, width, max_change);
-                       del = scale_linear(del, width, max_change);
+                       int total = add + del;
+
+                       total = scale_linear(add + del, width, max_change);
+                       if (total < 2 && add && del)
+                               /* width >= 2 due to the sanity check */
+                               total = 2;
+                       if (add < del) {
+                               add = scale_linear(add, width, max_change);
+                               del = total - add;
+                       } else {
+                               del = scale_linear(del, width, max_change);
+                               add = total - del;
+                       }
                }
                fprintf(options->file, "%s", line_prefix);
                show_name(options->file, prefix, name, len);
index 64d8e2a64ddd2e6162e7a8fd93ab4294de3f161e..906f91f1884a2df1b55dd00a229899ec6a47bb6e 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -202,7 +202,7 @@ check_patch_format () {
                l1=
                while test -z "$l1"
                do
-                       read l1
+                       read l1 || break
                done
                read l2
                read l3
index 26afc75cc7d0fbf3de69325043130836c32454a0..dc599077f0e55472ae814ed4e15f5b3a1b729caf 100644 (file)
@@ -90,10 +90,13 @@ call_merge () {
 
 finish_rb_merge () {
        move_to_original_branch
-       git notes copy --for-rewrite=rebase < "$state_dir"/rewritten
-       if test -x "$GIT_DIR"/hooks/post-rewrite &&
-               test -s "$state_dir"/rewritten; then
-               "$GIT_DIR"/hooks/post-rewrite rebase < "$state_dir"/rewritten
+       if test -s "$state_dir"/rewritten
+       then
+               git notes copy --for-rewrite=rebase <"$state_dir"/rewritten
+               if test -x "$GIT_DIR"/hooks/post-rewrite
+               then
+                       "$GIT_DIR"/hooks/post-rewrite rebase <"$state_dir"/rewritten
+               fi
        fi
        rm -r "$state_dir"
        say All done.
index eeb83d375931df81bd87ca285c8d0741c14f33a2..4334b95f70fab5c3cb9e446a9d6c418748b95bca 100755 (executable)
@@ -1878,8 +1878,7 @@ sub cmt_sha2rev_batch {
 
 sub working_head_info {
        my ($head, $refs) = @_;
-       my @args = qw/log --no-color --no-decorate --first-parent
-                     --pretty=medium/;
+       my @args = qw/rev-list --first-parent --pretty=medium/;
        my ($fh, $ctx) = command_output_pipe(@args, $head);
        my $hash;
        my %max;
@@ -2029,6 +2028,7 @@ package Git::SVN;
 use File::Path qw/mkpath/;
 use File::Copy qw/copy/;
 use IPC::Open3;
+use Time::Local;
 use Memoize;  # core since 5.8.0, Jul 2002
 use Memoize::Storable;
 
@@ -3287,6 +3287,14 @@ sub get_untracked {
        \@out;
 }
 
+sub get_tz {
+       # some systmes don't handle or mishandle %z, so be creative.
+       my $t = shift || time;
+       my $gm = timelocal(gmtime($t));
+       my $sign = qw( + + - )[ $t <=> $gm ];
+       return sprintf("%s%02d%02d", $sign, (gmtime(abs($t - $gm)))[2,1]);
+}
+
 # parse_svn_date(DATE)
 # --------------------
 # Given a date (in UTC) from Subversion, return a string in the format
@@ -3319,8 +3327,7 @@ sub parse_svn_date {
                        delete $ENV{TZ};
                }
 
-               my $our_TZ =
-                   POSIX::strftime('%Z', $S, $M, $H, $d, $m - 1, $Y - 1900);
+               my $our_TZ = get_tz();
 
                # This converts $epoch_in_UTC into our local timezone.
                my ($sec, $min, $hour, $mday, $mon, $year,
@@ -3920,7 +3927,7 @@ sub rebuild {
        my ($base_rev, $head) = ($partial ? $self->rev_map_max_norebuild(1) :
                (undef, undef));
        my ($log, $ctx) =
-           command_output_pipe(qw/rev-list --pretty=raw --no-color --reverse/,
+           command_output_pipe(qw/rev-list --pretty=raw --reverse/,
                                ($head ? "$head.." : "") . $self->refname,
                                '--');
        my $metadata_url = $self->metadata_url;
@@ -5130,7 +5137,7 @@ sub rmdirs {
 }
 
 sub open_or_add_dir {
-       my ($self, $full_path, $baton) = @_;
+       my ($self, $full_path, $baton, $deletions) = @_;
        my $t = $self->{types}->{$full_path};
        if (!defined $t) {
                die "$full_path not known in r$self->{r} or we have a bug!\n";
@@ -5139,7 +5146,7 @@ sub open_or_add_dir {
                no warnings 'once';
                # SVN::Node::none and SVN::Node::file are used only once,
                # so we're shutting up Perl's warnings about them.
-               if ($t == $SVN::Node::none) {
+               if ($t == $SVN::Node::none || defined($deletions->{$full_path})) {
                        return $self->add_directory($full_path, $baton,
                            undef, -1, $self->{pool});
                } elsif ($t == $SVN::Node::dir) {
@@ -5154,17 +5161,18 @@ sub open_or_add_dir {
 }
 
 sub ensure_path {
-       my ($self, $path) = @_;
+       my ($self, $path, $deletions) = @_;
        my $bat = $self->{bat};
        my $repo_path = $self->repo_path($path);
        return $bat->{''} unless (length $repo_path);
+
        my @p = split m#/+#, $repo_path;
        my $c = shift @p;
-       $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''});
+       $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''}, $deletions);
        while (@p) {
                my $c0 = $c;
                $c .= '/' . shift @p;
-               $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{$c0});
+               $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{$c0}, $deletions);
        }
        return $bat->{$c};
 }
@@ -5221,9 +5229,9 @@ sub apply_autoprops {
 }
 
 sub A {
-       my ($self, $m) = @_;
+       my ($self, $m, $deletions) = @_;
        my ($dir, $file) = split_path($m->{file_b});
-       my $pbat = $self->ensure_path($dir);
+       my $pbat = $self->ensure_path($dir, $deletions);
        my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
                                        undef, -1);
        print "\tA\t$m->{file_b}\n" unless $::_q;
@@ -5233,9 +5241,9 @@ sub A {
 }
 
 sub C {
-       my ($self, $m) = @_;
+       my ($self, $m, $deletions) = @_;
        my ($dir, $file) = split_path($m->{file_b});
-       my $pbat = $self->ensure_path($dir);
+       my $pbat = $self->ensure_path($dir, $deletions);
        my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
                                $self->url_path($m->{file_a}), $self->{r});
        print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
@@ -5252,9 +5260,9 @@ sub delete_entry {
 }
 
 sub R {
-       my ($self, $m) = @_;
+       my ($self, $m, $deletions) = @_;
        my ($dir, $file) = split_path($m->{file_b});
-       my $pbat = $self->ensure_path($dir);
+       my $pbat = $self->ensure_path($dir, $deletions);
        my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
                                $self->url_path($m->{file_a}), $self->{r});
        print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
@@ -5263,14 +5271,14 @@ sub R {
        $self->close_file($fbat,undef,$self->{pool});
 
        ($dir, $file) = split_path($m->{file_a});
-       $pbat = $self->ensure_path($dir);
+       $pbat = $self->ensure_path($dir, $deletions);
        $self->delete_entry($m->{file_a}, $pbat);
 }
 
 sub M {
-       my ($self, $m) = @_;
+       my ($self, $m, $deletions) = @_;
        my ($dir, $file) = split_path($m->{file_b});
-       my $pbat = $self->ensure_path($dir);
+       my $pbat = $self->ensure_path($dir, $deletions);
        my $fbat = $self->open_file($self->repo_path($m->{file_b}),
                                $pbat,$self->{r},$self->{pool});
        print "\t$m->{chg}\t$m->{file_b}\n" unless $::_q;
@@ -5340,9 +5348,9 @@ sub chg_file {
 }
 
 sub D {
-       my ($self, $m) = @_;
+       my ($self, $m, $deletions) = @_;
        my ($dir, $file) = split_path($m->{file_b});
-       my $pbat = $self->ensure_path($dir);
+       my $pbat = $self->ensure_path($dir, $deletions);
        print "\tD\t$m->{file_b}\n" unless $::_q;
        $self->delete_entry($m->{file_b}, $pbat);
 }
@@ -5374,11 +5382,19 @@ sub DESTROY {
 sub apply_diff {
        my ($self) = @_;
        my $mods = $self->{mods};
-       my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
+       my %o = ( D => 0, C => 1, R => 2, A => 3, M => 4, T => 5 );
+       my %deletions;
+
+       foreach my $m (@$mods) {
+               if ($m->{chg} eq "D") {
+                       $deletions{$m->{file_b}} = 1;
+               }
+       }
+
        foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
                my $f = $m->{chg};
                if (defined $o{$f}) {
-                       $self->$f($m);
+                       $self->$f($m, \%deletions);
                } else {
                        fatal("Invalid change type: $f");
                }
@@ -5994,7 +6010,6 @@ package Git::SVN::Log;
 use strict;
 use warnings;
 use POSIX qw/strftime/;
-use Time::Local;
 use constant commit_log_separator => ('-' x 72) . "\n";
 use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline
             %rusers $show_commit $incremental/;
@@ -6104,11 +6119,8 @@ sub run_pager {
 }
 
 sub format_svn_date {
-       # some systmes don't handle or mishandle %z, so be creative.
        my $t = shift || time;
-       my $gm = timelocal(gmtime($t));
-       my $sign = qw( + + - )[ $t <=> $gm ];
-       my $gmoff = sprintf("%s%02d%02d", $sign, (gmtime(abs($t - $gm)))[2,1]);
+       my $gmoff = Git::SVN::get_tz($t);
        return strftime("%Y-%m-%d %H:%M:%S $gmoff (%a, %d %b %Y)", localtime($t));
 }
 
index 568b0775f2de72d7f4fe9b3d7f5f72cbc2108085..eaf5f942502dd6593fa44724ea18d0153e725aea 100755 (executable)
@@ -5673,7 +5673,7 @@ sub git_tags_body {
 
 sub git_heads_body {
        # uses global variable $project
-       my ($headlist, $head, $from, $to, $extra) = @_;
+       my ($headlist, $head_at, $from, $to, $extra) = @_;
        $from = 0 unless defined $from;
        $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to);
 
@@ -5682,7 +5682,7 @@ sub git_heads_body {
        for (my $i = $from; $i <= $to; $i++) {
                my $entry = $headlist->[$i];
                my %ref = %$entry;
-               my $curr = $ref{'id'} eq $head;
+               my $curr = defined $head_at && $ref{'id'} eq $head_at;
                if ($alternate) {
                        print "<tr class=\"dark\">\n";
                } else {
@@ -5955,9 +5955,10 @@ sub git_search_files {
        my $alternate = 1;
        my $matches = 0;
        my $lastfile = '';
+       my $file_href;
        while (my $line = <$fd>) {
                chomp $line;
-               my ($file, $file_href, $lno, $ltext, $binary);
+               my ($file, $lno, $ltext, $binary);
                last if ($matches++ > 1000);
                if ($line =~ /^Binary file (.+) matches$/) {
                        $file = $1;
diff --git a/grep.c b/grep.c
index 3821400966823ff6bf877abf94f4edea9b6b80c6..f492d267cc157d46f899c70d01ea707d737d27ae 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -79,7 +79,7 @@ static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt)
 {
        const char *error;
        int erroffset;
-       int options = 0;
+       int options = PCRE_MULTILINE;
 
        if (opt->ignore_case)
                options |= PCRE_CASELESS;
diff --git a/help.c b/help.c
index cbbe966f685b276cac702bb0fd9a44bfbf5f0e79..14eefc91ced3890975d0833ffc83971c7986858b 100644 (file)
--- a/help.c
+++ b/help.c
@@ -5,28 +5,6 @@
 #include "help.h"
 #include "common-cmds.h"
 
-/* most GUI terminals set COLUMNS (although some don't export it) */
-static int term_columns(void)
-{
-       char *col_string = getenv("COLUMNS");
-       int n_cols;
-
-       if (col_string && (n_cols = atoi(col_string)) > 0)
-               return n_cols;
-
-#ifdef TIOCGWINSZ
-       {
-               struct winsize ws;
-               if (!ioctl(1, TIOCGWINSZ, &ws)) {
-                       if (ws.ws_col)
-                               return ws.ws_col;
-               }
-       }
-#endif
-
-       return 80;
-}
-
 void add_cmdname(struct cmdnames *cmds, const char *name, int len)
 {
        struct cmdname *ent = xmalloc(sizeof(*ent) + len + 1);
diff --git a/pager.c b/pager.c
index 975955ba82a0dbb128d6733090cd74c2b509ea81..05584dead6728ceff818630fbccaa91bb6c6b686 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -76,6 +76,12 @@ void setup_pager(void)
        if (!pager)
                return;
 
+       /*
+        * force computing the width of the terminal before we redirect
+        * the standard output to the pager.
+        */
+       (void) term_columns();
+
        setenv("GIT_PAGER_IN_USE", "true", 1);
 
        /* spawn the pager */
@@ -110,3 +116,46 @@ int pager_in_use(void)
        env = getenv("GIT_PAGER_IN_USE");
        return env ? git_config_bool("GIT_PAGER_IN_USE", env) : 0;
 }
+
+/*
+ * Return cached value (if set) or $COLUMNS environment variable (if
+ * set and positive) or ioctl(1, TIOCGWINSZ).ws_col (if positive),
+ * and default to 80 if all else fails.
+ */
+int term_columns(void)
+{
+       static int term_columns_at_startup;
+
+       char *col_string;
+       int n_cols;
+
+       if (term_columns_at_startup)
+               return term_columns_at_startup;
+
+       term_columns_at_startup = 80;
+
+       col_string = getenv("COLUMNS");
+       if (col_string && (n_cols = atoi(col_string)) > 0)
+               term_columns_at_startup = n_cols;
+#ifdef TIOCGWINSZ
+       else {
+               struct winsize ws;
+               if (!ioctl(1, TIOCGWINSZ, &ws) && ws.ws_col)
+                       term_columns_at_startup = ws.ws_col;
+       }
+#endif
+
+       return term_columns_at_startup;
+}
+
+/*
+ * How many columns do we need to show this number in decimal?
+ */
+int decimal_width(int number)
+{
+       int i, width;
+
+       for (width = 1, i = 10; i <= number; width++)
+               i *= 10;
+       return width;
+}
index a51bba1b9569d64c348f3affc90ed8640e0e0189..274e54b4f31da69bf7c0721d4c8ba8e264db5dde 100644 (file)
@@ -1120,11 +1120,16 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
                struct cache_entry *ce, *new;
                int cache_errno = 0;
                int changed = 0;
+               int filtered = 0;
 
                ce = istate->cache[i];
                if (ignore_submodules && S_ISGITLINK(ce->ce_mode))
                        continue;
 
+               if (pathspec &&
+                   !match_pathspec(pathspec, ce->name, strlen(ce->name), 0, seen))
+                       filtered = 1;
+
                if (ce_stage(ce)) {
                        while ((i < istate->cache_nr) &&
                               ! strcmp(istate->cache[i]->name, ce->name))
@@ -1132,12 +1137,14 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
                        i--;
                        if (allow_unmerged)
                                continue;
-                       show_file(unmerged_fmt, ce->name, in_porcelain, &first, header_msg);
+                       if (!filtered)
+                               show_file(unmerged_fmt, ce->name, in_porcelain,
+                                         &first, header_msg);
                        has_errors = 1;
                        continue;
                }
 
-               if (pathspec && !match_pathspec(pathspec, ce->name, strlen(ce->name), 0, seen))
+               if (filtered)
                        continue;
 
                new = refresh_cache_ent(istate, ce, options, &cache_errno, &changed);
index af597b3a625824d05196c8ed4754fb4020026cc2..b296d174043b5e5a11aceb9940ba6ba035e77678 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -8,6 +8,8 @@
 #include "tag.h"
 #include "string-list.h"
 
+enum map_direction { FROM_SRC, FROM_DST };
+
 static struct refspec s_tag_refspec = {
        0,
        1,
@@ -978,16 +980,20 @@ static void tail_link_ref(struct ref *ref, struct ref ***tail)
        *tail = &ref->next;
 }
 
+static struct ref *alloc_delete_ref(void)
+{
+       struct ref *ref = alloc_ref("(delete)");
+       hashclr(ref->new_sha1);
+       return ref;
+}
+
 static struct ref *try_explicit_object_name(const char *name)
 {
        unsigned char sha1[20];
        struct ref *ref;
 
-       if (!*name) {
-               ref = alloc_ref("(delete)");
-               hashclr(ref->new_sha1);
-               return ref;
-       }
+       if (!*name)
+               return alloc_delete_ref();
        if (get_sha1(name, sha1))
                return NULL;
        ref = alloc_ref(name);
@@ -1110,10 +1116,11 @@ static int match_explicit_refs(struct ref *src, struct ref *dst,
        return errs;
 }
 
-static const struct refspec *check_pattern_match(const struct refspec *rs,
-                                                int rs_nr,
-                                                const struct ref *src)
+static char *get_ref_match(const struct refspec *rs, int rs_nr, const struct ref *ref,
+               int send_mirror, int direction, const struct refspec **ret_pat)
 {
+       const struct refspec *pat;
+       char *name;
        int i;
        int matching_refs = -1;
        for (i = 0; i < rs_nr; i++) {
@@ -1123,14 +1130,36 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
                        continue;
                }
 
-               if (rs[i].pattern && match_name_with_pattern(rs[i].src, src->name,
-                                                            NULL, NULL))
-                       return rs + i;
+               if (rs[i].pattern) {
+                       const char *dst_side = rs[i].dst ? rs[i].dst : rs[i].src;
+                       int match;
+                       if (direction == FROM_SRC)
+                               match = match_name_with_pattern(rs[i].src, ref->name, dst_side, &name);
+                       else
+                               match = match_name_with_pattern(dst_side, ref->name, rs[i].src, &name);
+                       if (match) {
+                               matching_refs = i;
+                               break;
+                       }
+               }
        }
-       if (matching_refs != -1)
-               return rs + matching_refs;
-       else
+       if (matching_refs == -1)
                return NULL;
+
+       pat = rs + matching_refs;
+       if (pat->matching) {
+               /*
+                * "matching refs"; traditionally we pushed everything
+                * including refs outside refs/heads/ hierarchy, but
+                * that does not make much sense these days.
+                */
+               if (!send_mirror && prefixcmp(ref->name, "refs/heads/"))
+                       return NULL;
+               name = xstrdup(ref->name);
+       }
+       if (ret_pat)
+               *ret_pat = pat;
+       return name;
 }
 
 static struct ref **tail_ref(struct ref **head)
@@ -1155,9 +1184,10 @@ int match_push_refs(struct ref *src, struct ref **dst,
        struct refspec *rs;
        int send_all = flags & MATCH_REFS_ALL;
        int send_mirror = flags & MATCH_REFS_MIRROR;
+       int send_prune = flags & MATCH_REFS_PRUNE;
        int errs;
        static const char *default_refspec[] = { ":", NULL };
-       struct ref **dst_tail = tail_ref(dst);
+       struct ref *ref, **dst_tail = tail_ref(dst);
 
        if (!nr_refspec) {
                nr_refspec = 1;
@@ -1167,39 +1197,23 @@ int match_push_refs(struct ref *src, struct ref **dst,
        errs = match_explicit_refs(src, *dst, &dst_tail, rs, nr_refspec);
 
        /* pick the remainder */
-       for ( ; src; src = src->next) {
+       for (ref = src; ref; ref = ref->next) {
                struct ref *dst_peer;
                const struct refspec *pat = NULL;
                char *dst_name;
-               if (src->peer_ref)
-                       continue;
 
-               pat = check_pattern_match(rs, nr_refspec, src);
-               if (!pat)
+               if (ref->peer_ref)
                        continue;
 
-               if (pat->matching) {
-                       /*
-                        * "matching refs"; traditionally we pushed everything
-                        * including refs outside refs/heads/ hierarchy, but
-                        * that does not make much sense these days.
-                        */
-                       if (!send_mirror && prefixcmp(src->name, "refs/heads/"))
-                               continue;
-                       dst_name = xstrdup(src->name);
+               dst_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_SRC, &pat);
+               if (!dst_name)
+                       continue;
 
-               } else {
-                       const char *dst_side = pat->dst ? pat->dst : pat->src;
-                       if (!match_name_with_pattern(pat->src, src->name,
-                                                    dst_side, &dst_name))
-                               die("Didn't think it matches any more");
-               }
                dst_peer = find_ref_by_name(*dst, dst_name);
                if (dst_peer) {
                        if (dst_peer->peer_ref)
                                /* We're already sending something to this ref. */
                                goto free_name;
-
                } else {
                        if (pat->matching && !(send_all || send_mirror))
                                /*
@@ -1211,13 +1225,30 @@ int match_push_refs(struct ref *src, struct ref **dst,
 
                        /* Create a new one and link it */
                        dst_peer = make_linked_ref(dst_name, &dst_tail);
-                       hashcpy(dst_peer->new_sha1, src->new_sha1);
+                       hashcpy(dst_peer->new_sha1, ref->new_sha1);
                }
-               dst_peer->peer_ref = copy_ref(src);
+               dst_peer->peer_ref = copy_ref(ref);
                dst_peer->force = pat->force;
        free_name:
                free(dst_name);
        }
+       if (send_prune) {
+               /* check for missing refs on the remote */
+               for (ref = *dst; ref; ref = ref->next) {
+                       char *src_name;
+
+                       if (ref->peer_ref)
+                               /* We're already sending something to this ref. */
+                               continue;
+
+                       src_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_DST, NULL);
+                       if (src_name) {
+                               if (!find_ref_by_name(src, src_name))
+                                       ref->peer_ref = alloc_delete_ref();
+                               free(src_name);
+                       }
+               }
+       }
        if (errs)
                return -1;
        return 0;
index b3955983ba5caea698a78868abcbb54451b6daa8..9ad8eb6cc68b0842d8dc2aef9a65c25524739d97 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -145,7 +145,8 @@ int branch_merge_matches(struct branch *, int n, const char *);
 enum match_refs_flags {
        MATCH_REFS_NONE         = 0,
        MATCH_REFS_ALL          = (1 << 0),
-       MATCH_REFS_MIRROR       = (1 << 1)
+       MATCH_REFS_MIRROR       = (1 << 1),
+       MATCH_REFS_PRUNE        = (1 << 2)
 };
 
 /* Reporting of tracking info */
index f9f8d5e91c278000e5869f49ba6a79ddfaf13145..4f06a0e450359744528d3b125fb09eacebf1eb4a 100644 (file)
@@ -2700,10 +2700,13 @@ static int index_core(unsigned char *sha1, int fd, size_t size,
  * This also bypasses the usual "convert-to-git" dance, and that is on
  * purpose. We could write a streaming version of the converting
  * functions and insert that before feeding the data to fast-import
- * (or equivalent in-core API described above), but the primary
- * motivation for trying to stream from the working tree file and to
- * avoid mmaping it in core is to deal with large binary blobs, and
- * by definition they do _not_ want to get any conversion.
+ * (or equivalent in-core API described above). However, that is
+ * somewhat complicated, as we do not know the size of the filter
+ * result, which we need to know beforehand when writing a git object.
+ * Since the primary motivation for trying to stream from the working
+ * tree file and to avoid mmaping it in core is to deal with large
+ * binary blobs, they generally do not want to get any conversion, and
+ * callers should avoid this code path when filters are requested.
  */
 static int index_stream(unsigned char *sha1, int fd, size_t size,
                        enum object_type type, const char *path,
@@ -2720,7 +2723,8 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st,
 
        if (!S_ISREG(st->st_mode))
                ret = index_pipe(sha1, fd, type, path, flags);
-       else if (size <= big_file_threshold || type != OBJ_BLOB)
+       else if (size <= big_file_threshold || type != OBJ_BLOB ||
+                (path && would_convert_to_git(path, NULL, 0, 0)))
                ret = index_core(sha1, fd, size, type, path, flags);
        else
                ret = index_stream(sha1, fd, size, type, path, flags);
index ff0b96b4162bd92162a7eb05eee5be7a5ec2b6ba..5135d5950d9a5ea3ce8064e5491e53da17645da9 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -383,6 +383,22 @@ int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
        return 0;
 }
 
+int strbuf_getwholeline_fd(struct strbuf *sb, int fd, int term)
+{
+       strbuf_reset(sb);
+
+       while (1) {
+               char ch;
+               ssize_t len = xread(fd, &ch, 1);
+               if (len <= 0)
+                       return EOF;
+               strbuf_addch(sb, ch);
+               if (ch == term)
+                       break;
+       }
+       return 0;
+}
+
 int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint)
 {
        int fd, len;
index fbf059f4d371441b58fcad748e74e106a436241f..3effaa86b68f7b600dfd854558b7ee6f5d6f2f5e 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -116,6 +116,7 @@ extern int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint);
 
 extern int strbuf_getwholeline(struct strbuf *, FILE *, int);
 extern int strbuf_getline(struct strbuf *, FILE *, int);
+extern int strbuf_getwholeline_fd(struct strbuf *, int, int);
 
 extern void stripspace(struct strbuf *buf, int skip_comments);
 extern int launch_editor(const char *path, struct strbuf *buffer, const char *const *env);
index b5048ab77b9d580c3cc3f97e98f9a83209f2edb9..6091211f1009679aee741f3f6c2b290033aa5dc6 100644 (file)
@@ -73,4 +73,45 @@ gitweb-test:
 valgrind:
        $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind"
 
-.PHONY: pre-clean $(T) aggregate-results clean valgrind
+perf:
+       $(MAKE) -C perf/ all
+
+# Smoke testing targets
+-include ../GIT-VERSION-FILE
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo unknown')
+uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo unknown')
+
+test-results:
+       mkdir -p test-results
+
+test-results/git-smoke.tar.gz: test-results
+       $(PERL_PATH) ./harness \
+               --archive="test-results/git-smoke.tar.gz" \
+               $(T)
+
+smoke: test-results/git-smoke.tar.gz
+
+SMOKE_UPLOAD_FLAGS =
+ifdef SMOKE_USERNAME
+       SMOKE_UPLOAD_FLAGS += -F username="$(SMOKE_USERNAME)" -F password="$(SMOKE_PASSWORD)"
+endif
+ifdef SMOKE_COMMENT
+       SMOKE_UPLOAD_FLAGS += -F comments="$(SMOKE_COMMENT)"
+endif
+ifdef SMOKE_TAGS
+       SMOKE_UPLOAD_FLAGS += -F tags="$(SMOKE_TAGS)"
+endif
+
+smoke_report: smoke
+       curl \
+               -H "Expect: " \
+               -F project=Git \
+               -F architecture="$(uname_M)" \
+               -F platform="$(uname_S)" \
+               -F revision="$(GIT_VERSION)" \
+               -F report_file=@test-results/git-smoke.tar.gz \
+               $(SMOKE_UPLOAD_FLAGS) \
+               http://smoke.git.nix.is/app/projects/process_add_report/1 \
+       | grep -v ^Redirecting
+
+.PHONY: pre-clean $(T) aggregate-results clean valgrind perf
diff --git a/t/perf/.gitignore b/t/perf/.gitignore
new file mode 100644 (file)
index 0000000..50f5cc1
--- /dev/null
@@ -0,0 +1,2 @@
+build/
+test-results/
diff --git a/t/perf/Makefile b/t/perf/Makefile
new file mode 100644 (file)
index 0000000..8c47155
--- /dev/null
@@ -0,0 +1,15 @@
+-include ../../config.mak
+export GIT_TEST_OPTIONS
+
+all: perf
+
+perf: pre-clean
+       ./run
+
+pre-clean:
+       rm -rf test-results
+
+clean:
+       rm -rf build "trash directory".* test-results
+
+.PHONY: all perf pre-clean clean
diff --git a/t/perf/README b/t/perf/README
new file mode 100644 (file)
index 0000000..b2dbad4
--- /dev/null
@@ -0,0 +1,146 @@
+Git performance tests
+=====================
+
+This directory holds performance testing scripts for git tools.  The
+first part of this document describes the various ways in which you
+can run them.
+
+When fixing the tools or adding enhancements, you are strongly
+encouraged to add tests in this directory to cover what you are
+trying to fix or enhance.  The later part of this short document
+describes how your test scripts should be organized.
+
+
+Running Tests
+-------------
+
+The easiest way to run tests is to say "make".  This runs all
+the tests on the current git repository.
+
+    === Running 2 tests in this tree ===
+    [...]
+    Test                                     this tree
+    ---------------------------------------------------------
+    0001.1: rev-list --all                   0.54(0.51+0.02)
+    0001.2: rev-list --all --objects         6.14(5.99+0.11)
+    7810.1: grep worktree, cheap regex       0.16(0.16+0.35)
+    7810.2: grep worktree, expensive regex   7.90(29.75+0.37)
+    7810.3: grep --cached, cheap regex       3.07(3.02+0.25)
+    7810.4: grep --cached, expensive regex   9.39(30.57+0.24)
+
+You can compare multiple repositories and even git revisions with the
+'run' script:
+
+    $ ./run . origin/next /path/to/git-tree p0001-rev-list.sh
+
+where . stands for the current git tree.  The full invocation is
+
+    ./run [<revision|directory>...] [--] [<test-script>...]
+
+A '.' argument is implied if you do not pass any other
+revisions/directories.
+
+You can also manually test this or another git build tree, and then
+call the aggregation script to summarize the results:
+
+    $ ./p0001-rev-list.sh
+    [...]
+    $ GIT_BUILD_DIR=/path/to/other/git ./p0001-rev-list.sh
+    [...]
+    $ ./aggregate.perl . /path/to/other/git ./p0001-rev-list.sh
+
+aggregate.perl has the same invocation as 'run', it just does not run
+anything beforehand.
+
+You can set the following variables (also in your config.mak):
+
+    GIT_PERF_REPEAT_COUNT
+       Number of times a test should be repeated for best-of-N
+       measurements.  Defaults to 5.
+
+    GIT_PERF_MAKE_OPTS
+       Options to use when automatically building a git tree for
+       performance testing.  E.g., -j6 would be useful.
+
+    GIT_PERF_REPO
+    GIT_PERF_LARGE_REPO
+       Repositories to copy for the performance tests.  The normal
+       repo should be at least git.git size.  The large repo should
+       probably be about linux-2.6.git size for optimal results.
+       Both default to the git.git you are running from.
+
+You can also pass the options taken by ordinary git tests; the most
+useful one is:
+
+--root=<directory>::
+       Create "trash" directories used to store all temporary data during
+       testing under <directory>, instead of the t/ directory.
+       Using this option with a RAM-based filesystem (such as tmpfs)
+       can massively speed up the test suite.
+
+
+Naming Tests
+------------
+
+The performance test files are named as:
+
+       pNNNN-commandname-details.sh
+
+where N is a decimal digit.  The same conventions for choosing NNNN as
+for normal tests apply.
+
+
+Writing Tests
+-------------
+
+The perf script starts much like a normal test script, except it
+sources perf-lib.sh:
+
+       #!/bin/sh
+       #
+       # Copyright (c) 2005 Junio C Hamano
+       #
+
+       test_description='xxx performance test'
+       . ./perf-lib.sh
+
+After that you will want to use some of the following:
+
+       test_perf_default_repo  # sets up a "normal" repository
+       test_perf_large_repo    # sets up a "large" repository
+
+       test_perf_default_repo sub  # ditto, in a subdir "sub"
+
+        test_checkout_worktree  # if you need the worktree too
+
+At least one of the first two is required!
+
+You can use test_expect_success as usual.  For actual performance
+tests, use
+
+       test_perf 'descriptive string' '
+               command1 &&
+               command2
+       '
+
+test_perf spawns a subshell, for lack of better options.  This means
+that
+
+* you _must_ export all variables that you need in the subshell
+
+* you _must_ flag all variables that you want to persist from the
+  subshell with 'test_export':
+
+       test_perf 'descriptive string' '
+               foo=$(git rev-parse HEAD) &&
+               test_export foo
+       '
+
+  The so-exported variables are automatically marked for export in the
+  shell executing the perf test.  For your convenience, test_export is
+  the same as export in the main shell.
+
+  This feature relies on a bit of magic using 'set' and 'source'.
+  While we have tried to make sure that it can cope with embedded
+  whitespace and other special characters, it will not work with
+  multi-line data.
diff --git a/t/perf/aggregate.perl b/t/perf/aggregate.perl
new file mode 100755 (executable)
index 0000000..15f7fc1
--- /dev/null
@@ -0,0 +1,166 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Git;
+
+sub get_times {
+       my $name = shift;
+       open my $fh, "<", $name or return undef;
+       my $line = <$fh>;
+       return undef if not defined $line;
+       close $fh or die "cannot close $name: $!";
+       $line =~ /^(?:(\d+):)?(\d+):(\d+(?:\.\d+)?) (\d+(?:\.\d+)?) (\d+(?:\.\d+)?)$/
+               or die "bad input line: $line";
+       my $rt = ((defined $1 ? $1 : 0.0)*60+$2)*60+$3;
+       return ($rt, $4, $5);
+}
+
+sub format_times {
+       my ($r, $u, $s, $firstr) = @_;
+       if (!defined $r) {
+               return "<missing>";
+       }
+       my $out = sprintf "%.2f(%.2f+%.2f)", $r, $u, $s;
+       if (defined $firstr) {
+               if ($firstr > 0) {
+                       $out .= sprintf " %+.1f%%", 100.0*($r-$firstr)/$firstr;
+               } elsif ($r == 0) {
+                       $out .= " =";
+               } else {
+                       $out .= " +inf";
+               }
+       }
+       return $out;
+}
+
+my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests);
+while (scalar @ARGV) {
+       my $arg = $ARGV[0];
+       my $dir;
+       last if -f $arg or $arg eq "--";
+       if (! -d $arg) {
+               my $rev = Git::command_oneline(qw(rev-parse --verify), $arg);
+               $dir = "build/".$rev;
+       } else {
+               $arg =~ s{/*$}{};
+               $dir = $arg;
+               $dirabbrevs{$dir} = $dir;
+       }
+       push @dirs, $dir;
+       $dirnames{$dir} = $arg;
+       my $prefix = $dir;
+       $prefix =~ tr/^a-zA-Z0-9/_/c;
+       $prefixes{$dir} = $prefix . '.';
+       shift @ARGV;
+}
+
+if (not @dirs) {
+       @dirs = ('.');
+}
+$dirnames{'.'} = $dirabbrevs{'.'} = "this tree";
+$prefixes{'.'} = '';
+
+shift @ARGV if scalar @ARGV and $ARGV[0] eq "--";
+
+@tests = @ARGV;
+if (not @tests) {
+       @tests = glob "p????-*.sh";
+}
+
+my @subtests;
+my %shorttests;
+for my $t (@tests) {
+       $t =~ s{(?:.*/)?(p(\d+)-[^/]+)\.sh$}{$1} or die "bad test name: $t";
+       my $n = $2;
+       my $fname = "test-results/$t.subtests";
+       open my $fp, "<", $fname or die "cannot open $fname: $!";
+       for (<$fp>) {
+               chomp;
+               /^(\d+)$/ or die "malformed subtest line: $_";
+               push @subtests, "$t.$1";
+               $shorttests{"$t.$1"} = "$n.$1";
+       }
+       close $fp or die "cannot close $fname: $!";
+}
+
+sub read_descr {
+       my $name = shift;
+       open my $fh, "<", $name or return "<error reading description>";
+       my $line = <$fh>;
+       close $fh or die "cannot close $name";
+       chomp $line;
+       return $line;
+}
+
+my %descrs;
+my $descrlen = 4; # "Test"
+for my $t (@subtests) {
+       $descrs{$t} = $shorttests{$t}.": ".read_descr("test-results/$t.descr");
+       $descrlen = length $descrs{$t} if length $descrs{$t}>$descrlen;
+}
+
+sub have_duplicate {
+       my %seen;
+       for (@_) {
+               return 1 if exists $seen{$_};
+               $seen{$_} = 1;
+       }
+       return 0;
+}
+sub have_slash {
+       for (@_) {
+               return 1 if m{/};
+       }
+       return 0;
+}
+
+my %newdirabbrevs = %dirabbrevs;
+while (!have_duplicate(values %newdirabbrevs)) {
+       %dirabbrevs = %newdirabbrevs;
+       last if !have_slash(values %dirabbrevs);
+       %newdirabbrevs = %dirabbrevs;
+       for (values %newdirabbrevs) {
+               s{^[^/]*/}{};
+       }
+}
+
+my %times;
+my @colwidth = ((0)x@dirs);
+for my $i (0..$#dirs) {
+       my $d = $dirs[$i];
+       my $w = length (exists $dirabbrevs{$d} ? $dirabbrevs{$d} : $dirnames{$d});
+       $colwidth[$i] = $w if $w > $colwidth[$i];
+}
+for my $t (@subtests) {
+       my $firstr;
+       for my $i (0..$#dirs) {
+               my $d = $dirs[$i];
+               $times{$prefixes{$d}.$t} = [get_times("test-results/$prefixes{$d}$t.times")];
+               my ($r,$u,$s) = @{$times{$prefixes{$d}.$t}};
+               my $w = length format_times($r,$u,$s,$firstr);
+               $colwidth[$i] = $w if $w > $colwidth[$i];
+               $firstr = $r unless defined $firstr;
+       }
+}
+my $totalwidth = 3*@dirs+$descrlen;
+$totalwidth += $_ for (@colwidth);
+
+printf "%-${descrlen}s", "Test";
+for my $i (0..$#dirs) {
+       my $d = $dirs[$i];
+       printf "   %-$colwidth[$i]s", (exists $dirabbrevs{$d} ? $dirabbrevs{$d} : $dirnames{$d});
+}
+print "\n";
+print "-"x$totalwidth, "\n";
+for my $t (@subtests) {
+       printf "%-${descrlen}s", $descrs{$t};
+       my $firstr;
+       for my $i (0..$#dirs) {
+               my $d = $dirs[$i];
+               my ($r,$u,$s) = @{$times{$prefixes{$d}.$t}};
+               printf "   %-$colwidth[$i]s", format_times($r,$u,$s,$firstr);
+               $firstr = $r unless defined $firstr;
+       }
+       print "\n";
+}
diff --git a/t/perf/min_time.perl b/t/perf/min_time.perl
new file mode 100755 (executable)
index 0000000..c1a2717
--- /dev/null
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+
+my $minrt = 1e100;
+my $min;
+
+while (<>) {
+       # [h:]m:s.xx U.xx S.xx
+       /^(?:(\d+):)?(\d+):(\d+(?:\.\d+)?) (\d+(?:\.\d+)?) (\d+(?:\.\d+)?)$/
+               or die "bad input line: $_";
+       my $rt = ((defined $1 ? $1 : 0.0)*60+$2)*60+$3;
+       if ($rt < $minrt) {
+               $min = $_;
+               $minrt = $rt;
+       }
+}
+
+if (!defined $min) {
+       die "no input found";
+}
+
+print $min;
diff --git a/t/perf/p0000-perf-lib-sanity.sh b/t/perf/p0000-perf-lib-sanity.sh
new file mode 100755 (executable)
index 0000000..2ca4aac
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+test_description='Tests whether perf-lib facilities work'
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+test_perf 'test_perf_default_repo works' '
+       foo=$(git rev-parse HEAD) &&
+       test_export foo
+'
+
+test_checkout_worktree
+
+test_perf 'test_checkout_worktree works' '
+       wt=$(find . | wc -l) &&
+       idx=$(git ls-files | wc -l) &&
+       test $wt -gt $idx
+'
+
+baz=baz
+test_export baz
+
+test_expect_success 'test_export works' '
+       echo "$foo" &&
+       test "$foo" = "$(git rev-parse HEAD)" &&
+       echo "$baz" &&
+       test "$baz" = baz
+'
+
+test_perf 'export a weird var' '
+       bar="weird # variable" &&
+       test_export bar
+'
+
+test_expect_success 'test_export works with weird vars' '
+       echo "$bar" &&
+       test "$bar" = "weird # variable"
+'
+
+test_done
diff --git a/t/perf/p0001-rev-list.sh b/t/perf/p0001-rev-list.sh
new file mode 100755 (executable)
index 0000000..4f71a63
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test_description="Tests history walking performance"
+
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+test_perf 'rev-list --all' '
+       git rev-list --all >/dev/null
+'
+
+test_perf 'rev-list --all --objects' '
+       git rev-list --all --objects >/dev/null
+'
+
+test_done
diff --git a/t/perf/p7810-grep.sh b/t/perf/p7810-grep.sh
new file mode 100755 (executable)
index 0000000..9f4ade6
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+test_description="git-grep performance in various modes"
+
+. ./perf-lib.sh
+
+test_perf_large_repo
+test_checkout_worktree
+
+test_perf 'grep worktree, cheap regex' '
+       git grep some_nonexistent_string || :
+'
+test_perf 'grep worktree, expensive regex' '
+       git grep "^.* *some_nonexistent_string$" || :
+'
+test_perf 'grep --cached, cheap regex' '
+       git grep --cached some_nonexistent_string || :
+'
+test_perf 'grep --cached, expensive regex' '
+       git grep --cached "^.* *some_nonexistent_string$" || :
+'
+
+test_done
diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh
new file mode 100644 (file)
index 0000000..2a5e1f3
--- /dev/null
@@ -0,0 +1,198 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 Thomas Rast
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see http://www.gnu.org/licenses/ .
+
+# do the --tee work early; it otherwise confuses our careful
+# GIT_BUILD_DIR mangling
+case "$GIT_TEST_TEE_STARTED, $* " in
+done,*)
+       # do not redirect again
+       ;;
+*' --tee '*|*' --va'*)
+       mkdir -p test-results
+       BASE=test-results/$(basename "$0" .sh)
+       (GIT_TEST_TEE_STARTED=done ${SHELL-sh} "$0" "$@" 2>&1;
+        echo $? > $BASE.exit) | tee $BASE.out
+       test "$(cat $BASE.exit)" = 0
+       exit
+       ;;
+esac
+
+TEST_DIRECTORY=$(pwd)/..
+TEST_OUTPUT_DIRECTORY=$(pwd)
+if test -z "$GIT_TEST_INSTALLED"; then
+       perf_results_prefix=
+else
+       perf_results_prefix=$(printf "%s" "${GIT_TEST_INSTALLED%/bin-wrappers}" | tr -c "[a-zA-Z0-9]" "[_*]")"."
+       # make the tested dir absolute
+       GIT_TEST_INSTALLED=$(cd "$GIT_TEST_INSTALLED" && pwd)
+fi
+
+TEST_NO_CREATE_REPO=t
+
+. ../test-lib.sh
+
+perf_results_dir=$TEST_OUTPUT_DIRECTORY/test-results
+mkdir -p "$perf_results_dir"
+rm -f "$perf_results_dir"/$(basename "$0" .sh).subtests
+
+if test -z "$GIT_PERF_REPEAT_COUNT"; then
+       GIT_PERF_REPEAT_COUNT=3
+fi
+die_if_build_dir_not_repo () {
+       if ! ( cd "$TEST_DIRECTORY/.." &&
+                   git rev-parse --build-dir >/dev/null 2>&1 ); then
+               error "No $1 defined, and your build directory is not a repo"
+       fi
+}
+
+if test -z "$GIT_PERF_REPO"; then
+       die_if_build_dir_not_repo '$GIT_PERF_REPO'
+       GIT_PERF_REPO=$TEST_DIRECTORY/..
+fi
+if test -z "$GIT_PERF_LARGE_REPO"; then
+       die_if_build_dir_not_repo '$GIT_PERF_LARGE_REPO'
+       GIT_PERF_LARGE_REPO=$TEST_DIRECTORY/..
+fi
+
+test_perf_create_repo_from () {
+       test "$#" = 2 ||
+       error "bug in the test script: not 2 parameters to test-create-repo"
+       repo="$1"
+       source="$2"
+       source_git=$source/$(cd "$source" && git rev-parse --git-dir)
+       mkdir -p "$repo/.git"
+       (
+               cd "$repo/.git" &&
+               { cp -Rl "$source_git/objects" . 2>/dev/null ||
+                       cp -R "$source_git/objects" .; } &&
+               for stuff in "$source_git"/*; do
+                       case "$stuff" in
+                               */objects|*/hooks|*/config)
+                                       ;;
+                               *)
+                                       cp -R "$stuff" . || break
+                                       ;;
+                       esac
+               done &&
+               cd .. &&
+               git init -q &&
+               mv .git/hooks .git/hooks-disabled 2>/dev/null
+       ) || error "failed to copy repository '$source' to '$repo'"
+}
+
+# call at least one of these to establish an appropriately-sized repository
+test_perf_default_repo () {
+       test_perf_create_repo_from "${1:-$TRASH_DIRECTORY}" "$GIT_PERF_REPO"
+}
+test_perf_large_repo () {
+       if test "$GIT_PERF_LARGE_REPO" = "$GIT_BUILD_DIR"; then
+               echo "warning: \$GIT_PERF_LARGE_REPO is \$GIT_BUILD_DIR." >&2
+               echo "warning: This will work, but may not be a sufficiently large repo" >&2
+               echo "warning: for representative measurements." >&2
+       fi
+       test_perf_create_repo_from "${1:-$TRASH_DIRECTORY}" "$GIT_PERF_LARGE_REPO"
+}
+test_checkout_worktree () {
+       git checkout-index -u -a ||
+       error "git checkout-index failed"
+}
+
+# Performance tests should never fail.  If they do, stop immediately
+immediate=t
+
+test_run_perf_ () {
+       test_cleanup=:
+       test_export_="test_cleanup"
+       export test_cleanup test_export_
+       /usr/bin/time -f "%E %U %S" -o test_time.$i "$SHELL" -c '
+. '"$TEST_DIRECTORY"/../test-lib-functions.sh'
+test_export () {
+       [ $# != 0 ] || return 0
+       test_export_="$test_export_\\|$1"
+       shift
+       test_export "$@"
+}
+'"$1"'
+ret=$?
+set | sed -n "s'"/'/'\\\\''/g"';s/^\\($test_export_\\)/export '"'&'"'/p" >test_vars
+exit $ret' >&3 2>&4
+       eval_ret=$?
+
+       if test $eval_ret = 0 || test -n "$expecting_failure"
+       then
+               test_eval_ "$test_cleanup"
+               . ./test_vars || error "failed to load updated environment"
+       fi
+       if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then
+               echo ""
+       fi
+       return "$eval_ret"
+}
+
+
+test_perf () {
+       test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
+       test "$#" = 2 ||
+       error "bug in the test script: not 2 or 3 parameters to test-expect-success"
+       export test_prereq
+       if ! test_skip "$@"
+       then
+               base=$(basename "$0" .sh)
+               echo "$test_count" >>"$perf_results_dir"/$base.subtests
+               echo "$1" >"$perf_results_dir"/$base.$test_count.descr
+               if test -z "$verbose"; then
+                       echo -n "perf $test_count - $1:"
+               else
+                       echo "perf $test_count - $1:"
+               fi
+               for i in $(seq 1 $GIT_PERF_REPEAT_COUNT); do
+                       say >&3 "running: $2"
+                       if test_run_perf_ "$2"
+                       then
+                               if test -z "$verbose"; then
+                                       echo -n " $i"
+                               else
+                                       echo "* timing run $i/$GIT_PERF_REPEAT_COUNT:"
+                               fi
+                       else
+                               test -z "$verbose" && echo
+                               test_failure_ "$@"
+                               break
+                       fi
+               done
+               if test -z "$verbose"; then
+                       echo " ok"
+               else
+                       test_ok_ "$1"
+               fi
+               base="$perf_results_dir"/"$perf_results_prefix$(basename "$0" .sh)"."$test_count"
+               "$TEST_DIRECTORY"/perf/min_time.perl test_time.* >"$base".times
+       fi
+       echo >&3 ""
+}
+
+# We extend test_done to print timings at the end (./run disables this
+# and does it after running everything)
+test_at_end_hook_ () {
+       if test -z "$GIT_PERF_AGGREGATING_LATER"; then
+               ( cd "$TEST_DIRECTORY"/perf && ./aggregate.perl $(basename "$0") )
+       fi
+}
+
+test_export () {
+       export "$@"
+}
diff --git a/t/perf/run b/t/perf/run
new file mode 100755 (executable)
index 0000000..cfd7012
--- /dev/null
@@ -0,0 +1,82 @@
+#!/bin/sh
+
+case "$1" in
+       --help)
+               echo "usage: $0 [other_git_tree...] [--] [test_scripts]"
+               exit 0
+               ;;
+esac
+
+die () {
+       echo >&2 "error: $*"
+       exit 1
+}
+
+run_one_dir () {
+       if test $# -eq 0; then
+               set -- p????-*.sh
+       fi
+       echo "=== Running $# tests in ${GIT_TEST_INSTALLED:-this tree} ==="
+       for t in "$@"; do
+               ./$t $GIT_TEST_OPTS
+       done
+}
+
+unpack_git_rev () {
+       rev=$1
+       mkdir -p build/$rev
+       (cd "$(git rev-parse --show-cdup)" && git archive --format=tar $rev) |
+       (cd build/$rev && tar x)
+}
+build_git_rev () {
+       rev=$1
+       cp ../../config.mak build/$rev/config.mak
+       (cd build/$rev && make $GIT_PERF_MAKE_OPTS) ||
+       die "failed to build revision '$mydir'"
+}
+
+run_dirs_helper () {
+       mydir=${1%/}
+       shift
+       while test $# -gt 0 -a "$1" != -- -a ! -f "$1"; do
+               shift
+       done
+       if test $# -gt 0 -a "$1" = --; then
+               shift
+       fi
+       if [ ! -d "$mydir" ]; then
+               rev=$(git rev-parse --verify "$mydir" 2>/dev/null) ||
+               die "'$mydir' is neither a directory nor a valid revision"
+               if [ ! -d build/$rev ]; then
+                       unpack_git_rev $rev
+               fi
+               build_git_rev $rev
+               mydir=build/$rev
+       fi
+       if test "$mydir" = .; then
+               unset GIT_TEST_INSTALLED
+       else
+               GIT_TEST_INSTALLED="$mydir/bin-wrappers"
+               export GIT_TEST_INSTALLED
+       fi
+       run_one_dir "$@"
+}
+
+run_dirs () {
+       while test $# -gt 0 -a "$1" != -- -a ! -f "$1"; do
+               run_dirs_helper "$@"
+               shift
+       done
+}
+
+GIT_PERF_AGGREGATING_LATER=t
+export GIT_PERF_AGGREGATING_LATER
+
+cd "$(dirname $0)"
+. ../../GIT-BUILD-OPTIONS
+
+if test $# = 0 -o "$1" = -- -o -f "$1"; then
+       set -- . "$@"
+fi
+run_dirs "$@"
+./aggregate.perl "$@"
diff --git a/t/t1051-large-conversion.sh b/t/t1051-large-conversion.sh
new file mode 100755 (executable)
index 0000000..8b7640b
--- /dev/null
@@ -0,0 +1,86 @@
+#!/bin/sh
+
+test_description='test conversion filters on large files'
+. ./test-lib.sh
+
+set_attr() {
+       test_when_finished 'rm -f .gitattributes' &&
+       echo "* $*" >.gitattributes
+}
+
+check_input() {
+       git read-tree --empty &&
+       git add small large &&
+       git cat-file blob :small >small.index &&
+       git cat-file blob :large | head -n 1 >large.index &&
+       test_cmp small.index large.index
+}
+
+check_output() {
+       rm -f small large &&
+       git checkout small large &&
+       head -n 1 large >large.head &&
+       test_cmp small large.head
+}
+
+test_expect_success 'setup input tests' '
+       printf "\$Id: foo\$\\r\\n" >small &&
+       cat small small >large &&
+       git config core.bigfilethreshold 20 &&
+       git config filter.test.clean "sed s/.*/CLEAN/"
+'
+
+test_expect_success 'autocrlf=true converts on input' '
+       test_config core.autocrlf true &&
+       check_input
+'
+
+test_expect_success 'eol=crlf converts on input' '
+       set_attr eol=crlf &&
+       check_input
+'
+
+test_expect_success 'ident converts on input' '
+       set_attr ident &&
+       check_input
+'
+
+test_expect_success 'user-defined filters convert on input' '
+       set_attr filter=test &&
+       check_input
+'
+
+test_expect_success 'setup output tests' '
+       echo "\$Id\$" >small &&
+       cat small small >large &&
+       git add small large &&
+       git config core.bigfilethreshold 7 &&
+       git config filter.test.smudge "sed s/.*/SMUDGE/"
+'
+
+test_expect_success 'autocrlf=true converts on output' '
+       test_config core.autocrlf true &&
+       check_output
+'
+
+test_expect_success 'eol=crlf converts on output' '
+       set_attr eol=crlf &&
+       check_output
+'
+
+test_expect_success 'user-defined filters convert on output' '
+       set_attr filter=test &&
+       check_output
+'
+
+test_expect_success 'ident converts on output' '
+       set_attr ident &&
+       rm -f small large &&
+       git checkout small large &&
+       sed -n "s/Id: .*/Id: SHA/p" <small >small.clean &&
+       head -n 1 large >large.head &&
+       sed -n "s/Id: .*/Id: SHA/p" <large.head >large.clean &&
+       test_cmp small.clean large.clean
+'
+
+test_done
index 0690e0edf4e758200d4febb1c7837b5c7059add6..5f249f681e9324d7d35e3aee4dc9a834beff6c75 100755 (executable)
@@ -451,13 +451,21 @@ test_expect_success 'refer config from subdirectory' '
        mkdir x &&
        (
                cd x &&
-               echo strasse >expect
+               echo strasse >expect &&
                git config --get --file ../other-config ein.bahn >actual &&
                test_cmp expect actual
        )
 
 '
 
+test_expect_success 'refer config from subdirectory via GIT_CONFIG' '
+       (
+               cd x &&
+               GIT_CONFIG=../other-config git config --get ein.bahn >actual &&
+               test_cmp expect actual
+       )
+'
+
 cat > expect << EOF
 [ein]
        bahn = strasse
@@ -960,4 +968,21 @@ test_expect_success 'git -c complains about empty key and value' '
        test_must_fail git -c "" rev-parse
 '
 
+test_expect_success 'git config --edit works' '
+       git config -f tmp test.value no &&
+       echo test.value=yes >expect &&
+       GIT_EDITOR="echo [test]value=yes >" git config -f tmp --edit &&
+       git config -f tmp --list >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git config --edit respects core.editor' '
+       git config -f tmp test.value no &&
+       echo test.value=yes >expect &&
+       test_config core.editor "echo [test]value=yes >" &&
+       git config -f tmp --edit &&
+       git config -f tmp --list >actual &&
+       test_cmp expect actual
+'
+
 test_done
diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
new file mode 100755 (executable)
index 0000000..4b1cbaa
--- /dev/null
@@ -0,0 +1,134 @@
+#!/bin/sh
+
+test_description='test config file include directives'
+. ./test-lib.sh
+
+test_expect_success 'include file by absolute path' '
+       echo "[test]one = 1" >one &&
+       echo "[include]path = \"$(pwd)/one\"" >.gitconfig &&
+       echo 1 >expect &&
+       git config test.one >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'include file by relative path' '
+       echo "[test]one = 1" >one &&
+       echo "[include]path = one" >.gitconfig &&
+       echo 1 >expect &&
+       git config test.one >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'chained relative paths' '
+       mkdir subdir &&
+       echo "[test]three = 3" >subdir/three &&
+       echo "[include]path = three" >subdir/two &&
+       echo "[include]path = subdir/two" >.gitconfig &&
+       echo 3 >expect &&
+       git config test.three >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'include options can still be examined' '
+       echo "[test]one = 1" >one &&
+       echo "[include]path = one" >.gitconfig &&
+       echo one >expect &&
+       git config include.path >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'listing includes option and expansion' '
+       echo "[test]one = 1" >one &&
+       echo "[include]path = one" >.gitconfig &&
+       cat >expect <<-\EOF &&
+       include.path=one
+       test.one=1
+       EOF
+       git config --list >actual.full &&
+       grep -v ^core actual.full >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'single file lookup does not expand includes by default' '
+       echo "[test]one = 1" >one &&
+       echo "[include]path = one" >.gitconfig &&
+       test_must_fail git config -f .gitconfig test.one &&
+       test_must_fail git config --global test.one &&
+       echo 1 >expect &&
+       git config --includes -f .gitconfig test.one >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'single file list does not expand includes by default' '
+       echo "[test]one = 1" >one &&
+       echo "[include]path = one" >.gitconfig &&
+       echo "include.path=one" >expect &&
+       git config -f .gitconfig --list >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'writing config file does not expand includes' '
+       echo "[test]one = 1" >one &&
+       echo "[include]path = one" >.gitconfig &&
+       git config test.two 2 &&
+       echo 2 >expect &&
+       git config --no-includes test.two >actual &&
+       test_cmp expect actual &&
+       test_must_fail git config --no-includes test.one
+'
+
+test_expect_success 'config modification does not affect includes' '
+       echo "[test]one = 1" >one &&
+       echo "[include]path = one" >.gitconfig &&
+       git config test.one 2 &&
+       echo 1 >expect &&
+       git config -f one test.one >actual &&
+       test_cmp expect actual &&
+       cat >expect <<-\EOF &&
+       1
+       2
+       EOF
+       git config --get-all test.one >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'missing include files are ignored' '
+       cat >.gitconfig <<-\EOF &&
+       [include]path = foo
+       [test]value = yes
+       EOF
+       echo yes >expect &&
+       git config test.value >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'absolute includes from command line work' '
+       echo "[test]one = 1" >one &&
+       echo 1 >expect &&
+       git -c include.path="$PWD/one" config test.one >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'relative includes from command line fail' '
+       echo "[test]one = 1" >one &&
+       test_must_fail git -c include.path=one config test.one
+'
+
+test_expect_success 'include cycles are detected' '
+       cat >.gitconfig <<-\EOF &&
+       [test]value = gitconfig
+       [include]path = cycle
+       EOF
+       cat >cycle <<-\EOF &&
+       [test]value = cycle
+       [include]path = .gitconfig
+       EOF
+       cat >expect <<-\EOF &&
+       gitconfig
+       cycle
+       EOF
+       test_must_fail git config --get-all test.value 2>stderr &&
+       grep "exceeded maximum include depth" stderr
+'
+
+test_done
index 523ce9c45b75d85a129015a56004473a5fccf926..5b8ebd805378dc79e449ec1eecd1e97e333a7902 100755 (executable)
@@ -191,4 +191,30 @@ test_expect_success 'cleaned up' '
        test_cmp empty actual
 '
 
+test_expect_success 'rev-list --verify-objects' '
+       git rev-list --verify-objects --all >/dev/null 2>out &&
+       test_cmp empty out
+'
+
+test_expect_success 'rev-list --verify-objects with bad sha1' '
+       sha=$(echo blob | git hash-object -w --stdin) &&
+       old=$(echo $sha | sed "s+^..+&/+") &&
+       new=$(dirname $old)/ffffffffffffffffffffffffffffffffffffff &&
+       sha="$(dirname $new)$(basename $new)" &&
+       mv .git/objects/$old .git/objects/$new &&
+       test_when_finished "remove_object $sha" &&
+       git update-index --add --cacheinfo 100644 $sha foo &&
+       test_when_finished "git read-tree -u --reset HEAD" &&
+       tree=$(git write-tree) &&
+       test_when_finished "remove_object $tree" &&
+       cmt=$(echo bogus | git commit-tree $tree) &&
+       test_when_finished "remove_object $cmt" &&
+       git update-ref refs/heads/bogus $cmt &&
+       test_when_finished "git update-ref -d refs/heads/bogus" &&
+
+       test_might_fail git rev-list --verify-objects refs/heads/bogus >/dev/null 2>out &&
+       cat out &&
+       grep -q "error: sha1 mismatch 63ffffffffffffffffffffffffffffffffffffff" out
+'
+
 test_done
index 575d9508a09911f9d95dad2786fb2f1494abc093..874b3a6444ac64182eecf2b6aca4411fc90097e2 100755 (executable)
@@ -179,6 +179,21 @@ test_expect_success 'git add --refresh' '
        test -z "`git diff-index HEAD -- foo`"
 '
 
+test_expect_success 'git add --refresh with pathspec' '
+       git reset --hard &&
+       echo >foo && echo >bar && echo >baz &&
+       git add foo bar baz && H=$(git rev-parse :foo) && git rm -f foo &&
+       echo "100644 $H 3       foo" | git update-index --index-info &&
+       test-chmtime -60 bar baz &&
+       >expect &&
+       git add --refresh bar >actual &&
+       test_cmp expect actual &&
+
+       git diff-files --name-only >actual &&
+       ! grep bar actual&&
+       grep baz actual
+'
+
 test_expect_success POSIXPERM,SANITY 'git add should fail atomically upon an unreadable file' '
        git reset --hard &&
        date >foo1 &&
index f1b60b8560ce1b27497d9e0e08af007159e04d1d..6f77fffee60b5e37140dc8952c95144035ed457a 100755 (executable)
@@ -505,4 +505,14 @@ test_expect_success 'am -q is quiet' '
        ! test -s output.out
 '
 
+test_expect_success 'am empty-file does not infloop' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       touch empty-file &&
+       test_tick &&
+       { git am empty-file > actual 2>&1 && false || :; } &&
+       echo Patch format detection failed. >expected &&
+       test_cmp expected actual
+'
+
 test_done
index 8341fc4d154f6d50bf9055b206810dea4e1b807b..35ec294d9a56d5fc6b22ee2a39166325352bd639 100755 (executable)
@@ -58,6 +58,11 @@ test_expect_success 'fetch with transfer.fsckobjects' '
        )
 '
 
+cat >exp <<EOF
+To dst
+!      refs/heads/master:refs/heads/test       [remote rejected] (missing necessary objects)
+EOF
+
 test_expect_success 'push without strict' '
        rm -rf dst &&
        git init dst &&
@@ -66,7 +71,8 @@ test_expect_success 'push without strict' '
                git config fetch.fsckobjects false &&
                git config transfer.fsckobjects false
        ) &&
-       git push dst master:refs/heads/test
+       test_must_fail git push --porcelain dst master:refs/heads/test >act &&
+       test_cmp exp act
 '
 
 test_expect_success 'push with !receive.fsckobjects' '
@@ -77,9 +83,15 @@ test_expect_success 'push with !receive.fsckobjects' '
                git config receive.fsckobjects false &&
                git config transfer.fsckobjects true
        ) &&
-       git push dst master:refs/heads/test
+       test_must_fail git push --porcelain dst master:refs/heads/test >act &&
+       test_cmp exp act
 '
 
+cat >exp <<EOF
+To dst
+!      refs/heads/master:refs/heads/test       [remote rejected] (n/a (unpacker error))
+EOF
+
 test_expect_success 'push with receive.fsckobjects' '
        rm -rf dst &&
        git init dst &&
@@ -88,7 +100,8 @@ test_expect_success 'push with receive.fsckobjects' '
                git config receive.fsckobjects true &&
                git config transfer.fsckobjects false
        ) &&
-       test_must_fail git push dst master:refs/heads/test
+       test_must_fail git push --porcelain dst master:refs/heads/test >act &&
+       test_cmp exp act
 '
 
 test_expect_success 'push with transfer.fsckobjects' '
@@ -98,7 +111,8 @@ test_expect_success 'push with transfer.fsckobjects' '
                cd dst &&
                git config transfer.fsckobjects true
        ) &&
-       test_must_fail git push dst master:refs/heads/test
+       test_must_fail git push --porcelain dst master:refs/heads/test >act &&
+       test_cmp exp act
 '
 
 test_done
index b69cf574d7e9a272ea70864e87ed6556abe94f13..b5417cc951b1cecbbde613fa572387f362e889d1 100755 (executable)
@@ -979,4 +979,20 @@ test_expect_success 'push --porcelain --dry-run rejected' '
        test_cmp .git/foo .git/bar
 '
 
+test_expect_success 'push --prune' '
+       mk_test heads/master heads/second heads/foo heads/bar &&
+       git push --prune testrepo &&
+       check_push_result $the_commit heads/master &&
+       check_push_result $the_first_commit heads/second &&
+       ! check_push_result $the_first_commit heads/foo heads/bar
+'
+
+test_expect_success 'push --prune refspec' '
+       mk_test tmp/master tmp/second tmp/foo tmp/bar &&
+       git push --prune testrepo "refs/heads/*:refs/tmp/*" &&
+       check_push_result $the_commit tmp/master &&
+       check_push_result $the_first_commit tmp/second &&
+       ! check_push_result $the_first_commit tmp/foo tmp/bar
+'
+
 test_done
index 9ee52cfc458b2914ee82da6ac1117a2b1ae45a64..3683df13a6ae2208212bd3606187f9876e0562b5 100755 (executable)
@@ -101,10 +101,11 @@ test_expect_success TTY 'push -q suppresses progress' '
        ! grep "Writing objects" err
 '
 
-test_expect_failure TTY 'push --no-progress suppresses progress' '
+test_expect_success TTY 'push --no-progress suppresses progress' '
        ensure_fresh_upstream &&
 
        test_terminal git push -u --no-progress upstream master >out 2>err &&
+       ! grep "Unpacking objects" err &&
        ! grep "Writing objects" err
 '
 
index d66ed2450854c3091105a43bb2aa0f831140ed1f..cc6f081711002b42bcf6b2cb26287dcc56852a06 100755 (executable)
@@ -106,7 +106,7 @@ cat >exp <<EOF
 remote: error: hook declined to update refs/heads/dev2
 To http://127.0.0.1:$LIB_HTTPD_PORT/smart/test_repo.git
  ! [remote rejected] dev2 -> dev2 (hook declined)
-error: failed to push some refs to 'http://127.0.0.1:5541/smart/test_repo.git'
+error: failed to push some refs to 'http://127.0.0.1:$LIB_HTTPD_PORT/smart/test_repo.git'
 EOF
 
 test_expect_success 'rejected update prints status' '
index 4ae127d106c4a8ada0cea928affeff933bf0dbaa..a51c8b0560f85d31dc73d40a58ae3e4c6655f140 100755 (executable)
@@ -4,59 +4,58 @@ test_description='some bundle related tests'
 . ./test-lib.sh
 
 test_expect_success 'setup' '
-
-       : > file &&
-       git add file &&
-       test_tick &&
-       git commit -m initial &&
+       test_commit initial &&
        test_tick &&
        git tag -m tag tag &&
-       : > file2 &&
-       git add file2 &&
-       : > file3 &&
-       test_tick &&
-       git commit -m second &&
-       git add file3 &&
-       test_tick &&
-       git commit -m third
-
+       test_commit second &&
+       test_commit third &&
+       git tag -d initial &&
+       git tag -d second &&
+       git tag -d third
 '
 
 test_expect_success 'tags can be excluded by rev-list options' '
-
        git bundle create bundle --all --since=7.Apr.2005.15:16:00.-0700 &&
        git ls-remote bundle > output &&
        ! grep tag output
-
 '
 
 test_expect_success 'die if bundle file cannot be created' '
-
        mkdir adir &&
        test_must_fail git bundle create adir --all
-
 '
 
 test_expect_failure 'bundle --stdin' '
-
        echo master | git bundle create stdin-bundle.bdl --stdin &&
        git ls-remote stdin-bundle.bdl >output &&
        grep master output
-
 '
 
 test_expect_failure 'bundle --stdin <rev-list options>' '
-
        echo master | git bundle create hybrid-bundle.bdl --stdin tag &&
        git ls-remote hybrid-bundle.bdl >output &&
        grep master output
-
 '
 
 test_expect_success 'empty bundle file is rejected' '
+       : >empty-bundle &&
+       test_must_fail git fetch empty-bundle
+'
 
-    >empty-bundle && test_must_fail git fetch empty-bundle
-
+# This triggers a bug in older versions where the resulting line (with
+# --pretty=oneline) was longer than a 1024-char buffer.
+test_expect_success 'ridiculously long subject in boundary' '
+       : >file4 &&
+       test_tick &&
+       git add file4 &&
+       printf "%01200d\n" 0 | git commit -F - &&
+       test_commit fifth &&
+       git bundle create long-subject-bundle.bdl HEAD^..HEAD &&
+       git bundle list-heads long-subject-bundle.bdl >heads &&
+       test -s heads &&
+       git fetch long-subject-bundle.bdl &&
+       sed -n "/^-/{p;q}" long-subject-bundle.bdl >boundary &&
+       grep "^-$_x40 " boundary
 '
 
 test_done
index 4ef79aabc47a4ef2e9def65253edf6bcd8ce91a3..f8c247a7500d723e46796e7b9b76b9812e35db9b 100755 (executable)
@@ -1282,4 +1282,43 @@ test_expect_success 'mixing incompatibles modes and options is forbidden' '
        test_must_fail git tag -v -s
 '
 
+# check points-at
+
+test_expect_success '--points-at cannot be used in non-list mode' '
+       test_must_fail git tag --points-at=v4.0 foo
+'
+
+test_expect_success '--points-at finds lightweight tags' '
+       echo v4.0 >expect &&
+       git tag --points-at v4.0 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--points-at finds annotated tags of commits' '
+       git tag -m "v4.0, annotated" annotated-v4.0 v4.0 &&
+       echo annotated-v4.0 >expect &&
+       git tag -l --points-at v4.0 "annotated*" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--points-at finds annotated tags of tags' '
+       git tag -m "describing the v4.0 tag object" \
+               annotated-again-v4.0 annotated-v4.0 &&
+       cat >expect <<-\EOF &&
+       annotated-again-v4.0
+       annotated-v4.0
+       EOF
+       git tag --points-at=annotated-v4.0 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'multiple --points-at are OR-ed together' '
+       cat >expect <<-\EOF &&
+       v2.0
+       v3.0
+       EOF
+       git tag --points-at=v2.0 --points-at=v3.0 >actual &&
+       test_cmp expect actual
+'
+
 test_done
index b041516a1d6316dd36657b582c15100c0a7359d0..749b75e8d4fba546b22a0280b72891c38b5ea00a 100755 (executable)
@@ -65,7 +65,8 @@ test_expect_success "$name" "
        git update-index --add dir/file/file &&
        git commit -m '$name' &&
        test_must_fail git svn set-tree --find-copies-harder --rmdir \
-               ${remotes_git_svn}..mybranch" || true
+               ${remotes_git_svn}..mybranch
+"
 
 
 name='detect node change from directory to file #1'
@@ -79,7 +80,8 @@ test_expect_success "$name" '
        git update-index --add -- bar &&
        git commit -m "$name" &&
        test_must_fail git svn set-tree --find-copies-harder --rmdir \
-               ${remotes_git_svn}..mybranch2' || true
+               ${remotes_git_svn}..mybranch2
+'
 
 
 name='detect node change from file to directory #2'
@@ -92,9 +94,12 @@ test_expect_success "$name" '
        echo yyy > bar/zzz/yyy &&
        git update-index --add bar/zzz/yyy &&
        git commit -m "$name" &&
-       test_must_fail git svn set-tree --find-copies-harder --rmdir \
-               ${remotes_git_svn}..mybranch3' || true
-
+       git svn set-tree --find-copies-harder --rmdir \
+               ${remotes_git_svn}..mybranch3 &&
+       svn_cmd up "$SVN_TREE" &&
+       test -d "$SVN_TREE"/bar/zzz &&
+       test -e "$SVN_TREE"/bar/zzz/yyy
+'
 
 name='detect node change from directory to file #2'
 test_expect_success "$name" '
@@ -107,7 +112,8 @@ test_expect_success "$name" '
        git update-index --add -- dir &&
        git commit -m "$name" &&
        test_must_fail git svn set-tree --find-copies-harder --rmdir \
-               ${remotes_git_svn}..mybranch4' || true
+               ${remotes_git_svn}..mybranch4
+'
 
 
 name='remove executable bit from a file'
@@ -134,10 +140,10 @@ test_expect_success "$name" '
        test -x "$SVN_TREE"/exec.sh'
 
 
-name='executable file becomes a symlink to bar/zzz (file)'
+name='executable file becomes a symlink to file'
 test_expect_success "$name" '
        rm exec.sh &&
-       ln -s bar/zzz exec.sh &&
+       ln -s file exec.sh &&
        git update-index exec.sh &&
        git commit -m "$name" &&
        git svn set-tree --find-copies-harder --rmdir \
@@ -148,19 +154,19 @@ test_expect_success "$name" '
 name='new symlink is added to a file that was also just made executable'
 
 test_expect_success "$name" '
-       chmod +x bar/zzz &&
-       ln -s bar/zzz exec-2.sh &&
-       git update-index --add bar/zzz exec-2.sh &&
+       chmod +x file &&
+       ln -s file exec-2.sh &&
+       git update-index --add file exec-2.sh &&
        git commit -m "$name" &&
        git svn set-tree --find-copies-harder --rmdir \
                ${remotes_git_svn}..mybranch5 &&
        svn_cmd up "$SVN_TREE" &&
-       test -x "$SVN_TREE"/bar/zzz &&
+       test -x "$SVN_TREE"/file &&
        test -h "$SVN_TREE"/exec-2.sh'
 
 name='modify a symlink to become a file'
 test_expect_success "$name" '
-       echo git help > help || true &&
+       echo git help >help &&
        rm exec-2.sh &&
        cp help exec-2.sh &&
        git update-index exec-2.sh &&
@@ -195,14 +201,15 @@ name='check imported tree checksums expected tree checksums'
 rm -f expected
 if test_have_prereq UTF8
 then
-       echo tree bf522353586b1b883488f2bc73dab0d9f774b9a9 > expected
+       echo tree dc68b14b733e4ec85b04ab6f712340edc5dc936e > expected
 fi
 cat >> expected <<\EOF
-tree 83654bb36f019ae4fe77a0171f81075972087624
-tree 031b8d557afc6fea52894eaebb45bec52f1ba6d1
-tree 0b094cbff17168f24c302e297f55bfac65eb8bd3
-tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e
-tree 56a30b966619b863674f5978696f4a3594f2fca9
+tree c3322890dcf74901f32d216f05c5044f670ce632
+tree d3ccd5035feafd17b030c5732e7808cc49122853
+tree d03e1630363d4881e68929d532746b20b0986b83
+tree 149d63cd5878155c846e8c55d7d8487de283f89e
+tree 312b76e4f64ce14893aeac8591eb3960b065e247
+tree 149d63cd5878155c846e8c55d7d8487de283f89e
 tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e
 tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4
 EOF
index 0f771c673d58009e2bcde45254d4b4e9fa68efb9..90bb6050c13ece02199b6978e43e3c67de563c84 100755 (executable)
@@ -637,6 +637,45 @@ test_expect_success \
        'config override: tree view, features enabled in repo config (2)' \
        'gitweb_run "p=.git;a=tree"'
 
+# ----------------------------------------------------------------------
+# searching
+
+cat >>gitweb_config.perl <<\EOF
+
+# enable search
+$feature{'search'}{'default'} = [1];
+$feature{'grep'}{'default'} = [1];
+$feature{'pickaxe'}{'default'} = [1];
+EOF
+
+test_expect_success \
+       'search: preparation' \
+       'echo "1st MATCH" >>file &&
+        echo "2nd MATCH" >>file &&
+        echo "MATCH" >>bar &&
+        git add file bar &&
+        git commit -m "Added MATCH word"'
+
+test_expect_success \
+       'search: commit author' \
+       'gitweb_run "p=.git;a=search;h=HEAD;st=author;s=A+U+Thor"'
+
+test_expect_success \
+       'search: commit message' \
+       'gitweb_run "p=.git;a=search;h=HEAD;st=commitr;s=MATCH"'
+
+test_expect_success \
+       'search: grep' \
+       'gitweb_run "p=.git;a=search;h=HEAD;st=grep;s=MATCH"'
+
+test_expect_success \
+       'search: pickaxe' \
+       'gitweb_run "p=.git;a=search;h=HEAD;st=pickaxe;s=MATCH"'
+
+test_expect_success \
+       'search: projects' \
+       'gitweb_run "a=project_list;s=.git"'
+
 # ----------------------------------------------------------------------
 # non-ASCII in README.html
 
@@ -739,4 +778,13 @@ test_expect_success \
        'echo "\$projects_list_group_categories = 1;" >>gitweb_config.perl &&
         gitweb_run'
 
+# ----------------------------------------------------------------------
+# unborn branches
+
+test_expect_success \
+       'unborn HEAD: "summary" page (with "heads" subview)' \
+       'git checkout orphan_branch || git checkout --orphan orphan_branch &&
+        test_when_finished "git checkout master" &&
+        gitweb_run "p=.git;a=summary"'
+
 test_done
index 04ee20e642387e75e031f4c68ff126c06220c620..486c8eeb7e616c5dd964da25b068a8432e4c0c6f 100755 (executable)
@@ -234,8 +234,10 @@ test_expect_success 'refuse to preserve users without perms' '
                git config git-p4.skipSubmitEditCheck true &&
                echo "username-noperms: a change by alice" >>file1 &&
                git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 &&
-               P4EDITOR=touch P4USER=bob P4PASSWD=secret test_must_fail "$GITP4" commit --preserve-user &&
-               test_must_fail git diff --exit-code HEAD..p4/master
+               P4EDITOR=touch P4USER=bob P4PASSWD=secret &&
+               export P4EDITOR P4USER P4PASSWD &&
+               test_must_fail "$GITP4" commit --preserve-user &&
+               ! git diff --exit-code HEAD..p4/master
        )
 '
 
@@ -250,13 +252,15 @@ test_expect_success 'preserve user where author is unknown to p4' '
                git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 &&
                echo "username-unknown: a change by charlie" >>file1 &&
                git commit --author "Charlie <charlie@localhost>" -m "preserve: a change by charlie" file1 &&
-               P4EDITOR=touch P4USER=alice P4PASSWD=secret test_must_fail "$GITP4" commit --preserve-user &&
-               test_must_fail git diff --exit-code HEAD..p4/master &&
+               P4EDITOR=touch P4USER=alice P4PASSWD=secret &&
+               export P4EDITOR P4USER P4PASSWD &&
+               test_must_fail "$GITP4" commit --preserve-user &&
+               ! git diff --exit-code HEAD..p4/master &&
 
                echo "$0: repeat with allowMissingP4Users enabled" &&
                git config git-p4.allowMissingP4Users true &&
                git config git-p4.preserveUser true &&
-               P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit &&
+               "$GITP4" commit &&
                git diff --exit-code HEAD..p4/master &&
                p4_check_commit_author file1 alice
        )
@@ -275,20 +279,22 @@ test_expect_success 'not preserving user with mixed authorship' '
                p4_add_user derek Derek &&
 
                make_change_by_user usernamefile3 Derek derek@localhost &&
-               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit |\
+               P4EDITOR=cat P4USER=alice P4PASSWD=secret &&
+               export P4EDITOR P4USER P4PASSWD &&
+               "$GITP4" commit |\
                grep "git author derek@localhost does not match" &&
 
                make_change_by_user usernamefile3 Charlie charlie@localhost &&
-               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit |\
+               "$GITP4" commit |\
                grep "git author charlie@localhost does not match" &&
 
                make_change_by_user usernamefile3 alice alice@localhost &&
-               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" |\
+               "$GITP4" commit |\
                test_must_fail grep "git author.*does not match" &&
 
                git config git-p4.skipUserNameCheck true &&
                make_change_by_user usernamefile3 Charlie charlie@localhost &&
-               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit |\
+               "$GITP4" commit |\
                test_must_fail grep "git author.*does not match" &&
 
                p4_check_commit_author usernamefile3 alice
index ae9145e307dc1a3bc9ce14ccfac9bb1392b7d4ee..773a516ff0f40d396cb04cc474c697617192ae71 100755 (executable)
@@ -31,7 +31,7 @@ client_view() {
 #
 check_files_exist() {
        ok=0 &&
-       num=${#@} &&
+       num=$# &&
        for arg ; do
                test_path_is_file "$arg" &&
                ok=$(($ok + 1))
@@ -71,20 +71,24 @@ git_verify() {
 #   - dir2
 #     - file21
 #     - file22
+init_depot() {
+       for d in 1 2 ; do
+               mkdir -p dir$d &&
+               for f in 1 2 ; do
+                       echo dir$d/file$d$f >dir$d/file$d$f &&
+                       p4 add dir$d/file$d$f &&
+                       p4 submit -d "dir$d/file$d$f"
+               done
+       done &&
+       find . -type f ! -name files >files &&
+       check_files_exist dir1/file11 dir1/file12 \
+                         dir2/file21 dir2/file22
+}
+
 test_expect_success 'init depot' '
        (
                cd "$cli" &&
-               for d in 1 2 ; do
-                       mkdir -p dir$d &&
-                       for f in 1 2 ; do
-                               echo dir$d/file$d$f >dir$d/file$d$f &&
-                               p4 add dir$d/file$d$f &&
-                               p4 submit -d "dir$d/file$d$f"
-                       done
-               done &&
-               find . -type f ! -name files >files &&
-               check_files_exist dir1/file11 dir1/file12 \
-                                 dir2/file21 dir2/file22
+               init_depot
        )
 '
 
@@ -246,6 +250,139 @@ test_expect_success 'quotes on rhs only' '
        git_verify "cdir 1/file11" "cdir 1/file12"
 '
 
+#
+# Submit tests
+#
+
+# clone sets variable
+test_expect_success 'clone --use-client-spec sets useClientSpec' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git config --bool git-p4.useClientSpec >actual &&
+               echo true >true &&
+               test_cmp actual true
+       )
+'
+
+# clone just a subdir of the client spec
+test_expect_success 'subdir clone' '
+       client_view "//depot/... //client/..." &&
+       files="dir1/file11 dir1/file12 dir2/file21 dir2/file22" &&
+       client_verify $files &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       git_verify dir1/file11 dir1/file12
+'
+
+#
+# submit back, see what happens:  five cases
+#
+test_expect_success 'subdir clone, submit modify' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               echo line >>dir1/file12 &&
+               git add dir1/file12 &&
+               git commit -m dir1/file12 &&
+               "$GITP4" submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_file dir1/file12 &&
+               test_line_count = 2 dir1/file12
+       )
+'
+
+test_expect_success 'subdir clone, submit add' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               echo file13 >dir1/file13 &&
+               git add dir1/file13 &&
+               git commit -m dir1/file13 &&
+               "$GITP4" submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_file dir1/file13
+       )
+'
+
+test_expect_success 'subdir clone, submit delete' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               git rm dir1/file12 &&
+               git commit -m "delete dir1/file12" &&
+               "$GITP4" submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_missing dir1/file12
+       )
+'
+
+test_expect_success 'subdir clone, submit copy' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.detectCopies true &&
+               cp dir1/file11 dir1/file11a &&
+               git add dir1/file11a &&
+               git commit -m "copy to dir1/file11a" &&
+               "$GITP4" submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_file dir1/file11a
+       )
+'
+
+test_expect_success 'subdir clone, submit rename' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.detectRenames true &&
+               git mv dir1/file13 dir1/file13a &&
+               git commit -m "rename dir1/file13 to dir1/file13a" &&
+               "$GITP4" submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_missing dir1/file13 &&
+               test_path_is_file dir1/file13a
+       )
+'
+
+test_expect_success 'reinit depot' '
+       (
+               cd "$cli" &&
+               p4 sync -f &&
+               rm files &&
+               p4 delete */* &&
+               p4 submit -d "delete all files" &&
+               init_depot
+       )
+'
+
 #
 # What happens when two files of the same name are overlayed together?
 # The last-listed file should take preference.
diff --git a/t/t9810-git-p4-rcs.sh b/t/t9810-git-p4-rcs.sh
new file mode 100755 (executable)
index 0000000..49dfde0
--- /dev/null
@@ -0,0 +1,388 @@
+#!/bin/sh
+
+test_description='git-p4 rcs keywords'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+#
+# Make one file with keyword lines at the top, and
+# enough plain text to be able to test modifications
+# far away from the keywords.
+#
+test_expect_success 'init depot' '
+       (
+               cd "$cli" &&
+               cat <<-\EOF >filek &&
+               $Id$
+               /* $Revision$ */
+               # $Change$
+               line4
+               line5
+               line6
+               line7
+               line8
+               EOF
+               cp filek fileko &&
+               sed -i "s/Revision/Revision: do not scrub me/" fileko
+               cp fileko file_text &&
+               sed -i "s/Id/Id: do not scrub me/" file_text
+               p4 add -t text+k filek &&
+               p4 submit -d "filek" &&
+               p4 add -t text+ko fileko &&
+               p4 submit -d "fileko" &&
+               p4 add -t text file_text &&
+               p4 submit -d "file_text"
+       )
+'
+
+#
+# Generate these in a function to make it easy to use single quote marks.
+#
+write_scrub_scripts () {
+       cat >"$TRASH_DIRECTORY/scrub_k.py" <<-\EOF &&
+       import re, sys
+       sys.stdout.write(re.sub(r'(?i)\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$', r'$\1$', sys.stdin.read()))
+       EOF
+       cat >"$TRASH_DIRECTORY/scrub_ko.py" <<-\EOF
+       import re, sys
+       sys.stdout.write(re.sub(r'(?i)\$(Id|Header):[^$]*\$', r'$\1$', sys.stdin.read()))
+       EOF
+}
+
+test_expect_success 'scrub scripts' '
+       write_scrub_scripts
+'
+
+#
+# Compare $cli/file to its scrubbed version, should be different.
+# Compare scrubbed $cli/file to $git/file, should be same.
+#
+scrub_k_check () {
+       file="$1" &&
+       scrub="$TRASH_DIRECTORY/$file" &&
+       "$PYTHON_PATH" "$TRASH_DIRECTORY/scrub_k.py" <"$git/$file" >"$scrub" &&
+       ! test_cmp "$cli/$file" "$scrub" &&
+       test_cmp "$git/$file" "$scrub" &&
+       rm "$scrub"
+}
+scrub_ko_check () {
+       file="$1" &&
+       scrub="$TRASH_DIRECTORY/$file" &&
+       "$PYTHON_PATH" "$TRASH_DIRECTORY/scrub_ko.py" <"$git/$file" >"$scrub" &&
+       ! test_cmp "$cli/$file" "$scrub" &&
+       test_cmp "$git/$file" "$scrub" &&
+       rm "$scrub"
+}
+
+#
+# Modify far away from keywords.  If no RCS lines show up
+# in the diff, there is no conflict.
+#
+test_expect_success 'edit far away from RCS lines' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               sed -i "s/^line7/line7 edit/" filek &&
+               git commit -m "filek line7 edit" filek &&
+               "$GITP4" submit &&
+               scrub_k_check filek
+       )
+'
+
+#
+# Modify near the keywords.  This will require RCS scrubbing.
+#
+test_expect_success 'edit near RCS lines' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.attemptRCSCleanup true &&
+               sed -i "s/^line4/line4 edit/" filek &&
+               git commit -m "filek line4 edit" filek &&
+               "$GITP4" submit &&
+               scrub_k_check filek
+       )
+'
+
+#
+# Modify the keywords themselves.  This also will require RCS scrubbing.
+#
+test_expect_success 'edit keyword lines' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.attemptRCSCleanup true &&
+               sed -i "/Revision/d" filek &&
+               git commit -m "filek remove Revision line" filek &&
+               "$GITP4" submit &&
+               scrub_k_check filek
+       )
+'
+
+#
+# Scrubbing text+ko files should not alter all keywords, just Id, Header.
+#
+test_expect_success 'scrub ko files differently' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.attemptRCSCleanup true &&
+               sed -i "s/^line4/line4 edit/" fileko &&
+               git commit -m "fileko line4 edit" fileko &&
+               "$GITP4" submit &&
+               scrub_ko_check fileko &&
+               ! scrub_k_check fileko
+       )
+'
+
+# hack; git-p4 submit should do it on its own
+test_expect_success 'cleanup after failure' '
+       (
+               cd "$cli" &&
+               p4 revert ...
+       )
+'
+
+#
+# Do not scrub anything but +k or +ko files.  Sneak a change into
+# the cli file so that submit will get a conflict.  Make sure that
+# scrubbing doesn't make a mess of things.
+#
+# Assumes that git-p4 exits leaving the p4 file open, with the
+# conflict-generating patch unapplied.
+#
+# This might happen only if the git repo is behind the p4 repo at
+# submit time, and there is a conflict.
+#
+test_expect_success 'do not scrub plain text' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.attemptRCSCleanup true &&
+               sed -i "s/^line4/line4 edit/" file_text &&
+               git commit -m "file_text line4 edit" file_text &&
+               (
+                       cd "$cli" &&
+                       p4 open file_text &&
+                       sed -i "s/^line5/line5 p4 edit/" file_text &&
+                       p4 submit -d "file5 p4 edit"
+               ) &&
+               ! "$GITP4" submit &&
+               (
+                       # exepct something like:
+                       #    file_text - file(s) not opened on this client
+                       # but not copious diff output
+                       cd "$cli" &&
+                       p4 diff file_text >wc &&
+                       test_line_count = 1 wc
+               )
+       )
+'
+
+# hack; git-p4 submit should do it on its own
+test_expect_success 'cleanup after failure 2' '
+       (
+               cd "$cli" &&
+               p4 revert ...
+       )
+'
+
+create_kw_file () {
+       cat <<\EOF >"$1"
+/* A file
+       Id: $Id$
+       Revision: $Revision$
+       File: $File$
+ */
+int main(int argc, const char **argv) {
+       return 0;
+}
+EOF
+}
+
+test_expect_success 'add kwfile' '
+       (
+               cd "$cli" &&
+               echo file1 >file1 &&
+               p4 add file1 &&
+               p4 submit -d "file 1" &&
+               create_kw_file kwfile1.c &&
+               p4 add kwfile1.c &&
+               p4 submit -d "Add rcw kw file" kwfile1.c
+       )
+'
+
+p4_append_to_file () {
+       f="$1" &&
+       p4 edit -t ktext "$f" &&
+       echo "/* $(date) */" >>"$f" &&
+       p4 submit -d "appending a line in p4"
+}
+
+# Create some files with RCS keywords. If they get modified
+# elsewhere then the version number gets bumped which then
+# results in a merge conflict if we touch the RCS kw lines,
+# even though the change itself would otherwise apply cleanly.
+test_expect_success 'cope with rcs keyword expansion damage' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.attemptRCSCleanup true &&
+               (cd ../cli && p4_append_to_file kwfile1.c) &&
+               old_lines=$(wc -l <kwfile1.c) &&
+               perl -n -i -e "print unless m/Revision:/" kwfile1.c &&
+               new_lines=$(wc -l <kwfile1.c) &&
+               test $new_lines = $(($old_lines - 1)) &&
+
+               git add kwfile1.c &&
+               git commit -m "Zap an RCS kw line" &&
+               "$GITP4" submit &&
+               "$GITP4" rebase &&
+               git diff p4/master &&
+               "$GITP4" commit &&
+               echo "try modifying in both" &&
+               cd "$cli" &&
+               p4 edit kwfile1.c &&
+               echo "line from p4" >>kwfile1.c &&
+               p4 submit -d "add a line in p4" kwfile1.c &&
+               cd "$git" &&
+               echo "line from git at the top" | cat - kwfile1.c >kwfile1.c.new &&
+               mv kwfile1.c.new kwfile1.c &&
+               git commit -m "Add line in git at the top" kwfile1.c &&
+               "$GITP4" rebase &&
+               "$GITP4" submit
+       )
+'
+
+test_expect_success 'cope with rcs keyword file deletion' '
+       test_when_finished cleanup_git &&
+       (
+               cd "$cli" &&
+               echo "\$Revision\$" >kwdelfile.c &&
+               p4 add -t ktext kwdelfile.c &&
+               p4 submit -d "Add file to be deleted" &&
+               cat kwdelfile.c &&
+               grep 1 kwdelfile.c
+       ) &&
+       "$GITP4" clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               grep Revision kwdelfile.c &&
+               git rm -f kwdelfile.c &&
+               git commit -m "Delete a file containing RCS keywords" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.attemptRCSCleanup true &&
+               "$GITP4" submit
+       ) &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               ! test -f kwdelfile.c
+       )
+'
+
+# If you add keywords in git of the form $Header$ then everything should
+# work fine without any special handling.
+test_expect_success 'Add keywords in git which match the default p4 values' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo "NewKW: \$Revision\$" >>kwfile1.c &&
+               git add kwfile1.c &&
+               git commit -m "Adding RCS keywords in git" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.attemptRCSCleanup true &&
+               "$GITP4" submit
+       ) &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               test -f kwfile1.c &&
+               grep "NewKW.*Revision.*[0-9]" kwfile1.c
+
+       )
+'
+
+# If you add keywords in git of the form $Header:#1$ then things will fail
+# unless git-p4 takes steps to scrub the *git* commit.
+#
+test_expect_failure 'Add keywords in git which do not match the default p4 values' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo "NewKW2: \$Revision:1\$" >>kwfile1.c &&
+               git add kwfile1.c &&
+               git commit -m "Adding RCS keywords in git" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.attemptRCSCleanup true &&
+               "$GITP4" submit
+       ) &&
+       (
+               cd "$cli" &&
+               p4 sync &&
+               grep "NewKW2.*Revision.*[0-9]" kwfile1.c
+
+       )
+'
+
+# Check that the existing merge conflict handling still works.
+# Modify kwfile1.c in git, and delete in p4. We should be able
+# to skip the git commit.
+#
+test_expect_success 'merge conflict handling still works' '
+       test_when_finished cleanup_git &&
+       (
+               cd "$cli" &&
+               echo "Hello:\$Id\$" >merge2.c &&
+               echo "World" >>merge2.c &&
+               p4 add -t ktext merge2.c &&
+               p4 submit -d "add merge test file"
+       ) &&
+       "$GITP4" clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               sed -e "/Hello/d" merge2.c >merge2.c.tmp &&
+               mv merge2.c.tmp merge2.c &&
+               git add merge2.c &&
+               git commit -m "Modifying merge2.c"
+       ) &&
+       (
+               cd "$cli" &&
+               p4 delete merge2.c &&
+               p4 submit -d "remove merge test file"
+       ) &&
+       (
+               cd "$git" &&
+               test -f merge2.c &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.attemptRCSCleanup true &&
+               !(echo "s" | "$GITP4" submit) &&
+               git rebase --skip &&
+               ! test -f merge2.c
+       )
+'
+
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
new file mode 100644 (file)
index 0000000..7b3b4be
--- /dev/null
@@ -0,0 +1,565 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see http://www.gnu.org/licenses/ .
+
+# The semantics of the editor variables are that of invoking
+# sh -c "$EDITOR \"$@\"" files ...
+#
+# If our trash directory contains shell metacharacters, they will be
+# interpreted if we just set $EDITOR directly, so do a little dance with
+# environment variables to work around this.
+#
+# In particular, quoting isn't enough, as the path may contain the same quote
+# that we're using.
+test_set_editor () {
+       FAKE_EDITOR="$1"
+       export FAKE_EDITOR
+       EDITOR='"$FAKE_EDITOR"'
+       export EDITOR
+}
+
+test_decode_color () {
+       awk '
+               function name(n) {
+                       if (n == 0) return "RESET";
+                       if (n == 1) return "BOLD";
+                       if (n == 30) return "BLACK";
+                       if (n == 31) return "RED";
+                       if (n == 32) return "GREEN";
+                       if (n == 33) return "YELLOW";
+                       if (n == 34) return "BLUE";
+                       if (n == 35) return "MAGENTA";
+                       if (n == 36) return "CYAN";
+                       if (n == 37) return "WHITE";
+                       if (n == 40) return "BLACK";
+                       if (n == 41) return "BRED";
+                       if (n == 42) return "BGREEN";
+                       if (n == 43) return "BYELLOW";
+                       if (n == 44) return "BBLUE";
+                       if (n == 45) return "BMAGENTA";
+                       if (n == 46) return "BCYAN";
+                       if (n == 47) return "BWHITE";
+               }
+               {
+                       while (match($0, /\033\[[0-9;]*m/) != 0) {
+                               printf "%s<", substr($0, 1, RSTART-1);
+                               codes = substr($0, RSTART+2, RLENGTH-3);
+                               if (length(codes) == 0)
+                                       printf "%s", name(0)
+                               else {
+                                       n = split(codes, ary, ";");
+                                       sep = "";
+                                       for (i = 1; i <= n; i++) {
+                                               printf "%s%s", sep, name(ary[i]);
+                                               sep = ";"
+                                       }
+                               }
+                               printf ">";
+                               $0 = substr($0, RSTART + RLENGTH, length($0) - RSTART - RLENGTH + 1);
+                       }
+                       print
+               }
+       '
+}
+
+nul_to_q () {
+       perl -pe 'y/\000/Q/'
+}
+
+q_to_nul () {
+       perl -pe 'y/Q/\000/'
+}
+
+q_to_cr () {
+       tr Q '\015'
+}
+
+q_to_tab () {
+       tr Q '\011'
+}
+
+append_cr () {
+       sed -e 's/$/Q/' | tr Q '\015'
+}
+
+remove_cr () {
+       tr '\015' Q | sed -e 's/Q$//'
+}
+
+# In some bourne shell implementations, the "unset" builtin returns
+# nonzero status when a variable to be unset was not set in the first
+# place.
+#
+# Use sane_unset when that should not be considered an error.
+
+sane_unset () {
+       unset "$@"
+       return 0
+}
+
+test_tick () {
+       if test -z "${test_tick+set}"
+       then
+               test_tick=1112911993
+       else
+               test_tick=$(($test_tick + 60))
+       fi
+       GIT_COMMITTER_DATE="$test_tick -0700"
+       GIT_AUTHOR_DATE="$test_tick -0700"
+       export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
+}
+
+# Stop execution and start a shell. This is useful for debugging tests and
+# only makes sense together with "-v".
+#
+# Be sure to remove all invocations of this command before submitting.
+
+test_pause () {
+       if test "$verbose" = t; then
+               "$SHELL_PATH" <&6 >&3 2>&4
+       else
+               error >&5 "test_pause requires --verbose"
+       fi
+}
+
+# Call test_commit with the arguments "<message> [<file> [<contents>]]"
+#
+# This will commit a file with the given contents and the given commit
+# message.  It will also add a tag with <message> as name.
+#
+# Both <file> and <contents> default to <message>.
+
+test_commit () {
+       file=${2:-"$1.t"}
+       echo "${3-$1}" > "$file" &&
+       git add "$file" &&
+       test_tick &&
+       git commit -m "$1" &&
+       git tag "$1"
+}
+
+# Call test_merge with the arguments "<message> <commit>", where <commit>
+# can be a tag pointing to the commit-to-merge.
+
+test_merge () {
+       test_tick &&
+       git merge -m "$1" "$2" &&
+       git tag "$1"
+}
+
+# This function helps systems where core.filemode=false is set.
+# Use it instead of plain 'chmod +x' to set or unset the executable bit
+# of a file in the working directory and add it to the index.
+
+test_chmod () {
+       chmod "$@" &&
+       git update-index --add "--chmod=$@"
+}
+
+# Unset a configuration variable, but don't fail if it doesn't exist.
+test_unconfig () {
+       git config --unset-all "$@"
+       config_status=$?
+       case "$config_status" in
+       5) # ok, nothing to unset
+               config_status=0
+               ;;
+       esac
+       return $config_status
+}
+
+# Set git config, automatically unsetting it after the test is over.
+test_config () {
+       test_when_finished "test_unconfig '$1'" &&
+       git config "$@"
+}
+
+test_config_global () {
+       test_when_finished "test_unconfig --global '$1'" &&
+       git config --global "$@"
+}
+
+write_script () {
+       {
+               echo "#!${2-"$SHELL_PATH"}" &&
+               cat
+       } >"$1" &&
+       chmod +x "$1"
+}
+
+# Use test_set_prereq to tell that a particular prerequisite is available.
+# The prerequisite can later be checked for in two ways:
+#
+# - Explicitly using test_have_prereq.
+#
+# - Implicitly by specifying the prerequisite tag in the calls to
+#   test_expect_{success,failure,code}.
+#
+# The single parameter is the prerequisite tag (a simple word, in all
+# capital letters by convention).
+
+test_set_prereq () {
+       satisfied="$satisfied$1 "
+}
+satisfied=" "
+
+test_have_prereq () {
+       # prerequisites can be concatenated with ','
+       save_IFS=$IFS
+       IFS=,
+       set -- $*
+       IFS=$save_IFS
+
+       total_prereq=0
+       ok_prereq=0
+       missing_prereq=
+
+       for prerequisite
+       do
+               total_prereq=$(($total_prereq + 1))
+               case $satisfied in
+               *" $prerequisite "*)
+                       ok_prereq=$(($ok_prereq + 1))
+                       ;;
+               *)
+                       # Keep a list of missing prerequisites
+                       if test -z "$missing_prereq"
+                       then
+                               missing_prereq=$prerequisite
+                       else
+                               missing_prereq="$prerequisite,$missing_prereq"
+                       fi
+               esac
+       done
+
+       test $total_prereq = $ok_prereq
+}
+
+test_declared_prereq () {
+       case ",$test_prereq," in
+       *,$1,*)
+               return 0
+               ;;
+       esac
+       return 1
+}
+
+test_expect_failure () {
+       test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
+       test "$#" = 2 ||
+       error "bug in the test script: not 2 or 3 parameters to test-expect-failure"
+       export test_prereq
+       if ! test_skip "$@"
+       then
+               say >&3 "checking known breakage: $2"
+               if test_run_ "$2" expecting_failure
+               then
+                       test_known_broken_ok_ "$1"
+               else
+                       test_known_broken_failure_ "$1"
+               fi
+       fi
+       echo >&3 ""
+}
+
+test_expect_success () {
+       test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
+       test "$#" = 2 ||
+       error "bug in the test script: not 2 or 3 parameters to test-expect-success"
+       export test_prereq
+       if ! test_skip "$@"
+       then
+               say >&3 "expecting success: $2"
+               if test_run_ "$2"
+               then
+                       test_ok_ "$1"
+               else
+                       test_failure_ "$@"
+               fi
+       fi
+       echo >&3 ""
+}
+
+# test_external runs external test scripts that provide continuous
+# test output about their progress, and succeeds/fails on
+# zero/non-zero exit code.  It outputs the test output on stdout even
+# in non-verbose mode, and announces the external script with "# run
+# <n>: ..." before running it.  When providing relative paths, keep in
+# mind that all scripts run in "trash directory".
+# Usage: test_external description command arguments...
+# Example: test_external 'Perl API' perl ../path/to/test.pl
+test_external () {
+       test "$#" = 4 && { test_prereq=$1; shift; } || test_prereq=
+       test "$#" = 3 ||
+       error >&5 "bug in the test script: not 3 or 4 parameters to test_external"
+       descr="$1"
+       shift
+       export test_prereq
+       if ! test_skip "$descr" "$@"
+       then
+               # Announce the script to reduce confusion about the
+               # test output that follows.
+               say_color "" "# run $test_count: $descr ($*)"
+               # Export TEST_DIRECTORY, TRASH_DIRECTORY and GIT_TEST_LONG
+               # to be able to use them in script
+               export TEST_DIRECTORY TRASH_DIRECTORY GIT_TEST_LONG
+               # Run command; redirect its stderr to &4 as in
+               # test_run_, but keep its stdout on our stdout even in
+               # non-verbose mode.
+               "$@" 2>&4
+               if [ "$?" = 0 ]
+               then
+                       if test $test_external_has_tap -eq 0; then
+                               test_ok_ "$descr"
+                       else
+                               say_color "" "# test_external test $descr was ok"
+                               test_success=$(($test_success + 1))
+                       fi
+               else
+                       if test $test_external_has_tap -eq 0; then
+                               test_failure_ "$descr" "$@"
+                       else
+                               say_color error "# test_external test $descr failed: $@"
+                               test_failure=$(($test_failure + 1))
+                       fi
+               fi
+       fi
+}
+
+# Like test_external, but in addition tests that the command generated
+# no output on stderr.
+test_external_without_stderr () {
+       # The temporary file has no (and must have no) security
+       # implications.
+       tmp=${TMPDIR:-/tmp}
+       stderr="$tmp/git-external-stderr.$$.tmp"
+       test_external "$@" 4> "$stderr"
+       [ -f "$stderr" ] || error "Internal error: $stderr disappeared."
+       descr="no stderr: $1"
+       shift
+       say >&3 "# expecting no stderr from previous command"
+       if [ ! -s "$stderr" ]; then
+               rm "$stderr"
+
+               if test $test_external_has_tap -eq 0; then
+                       test_ok_ "$descr"
+               else
+                       say_color "" "# test_external_without_stderr test $descr was ok"
+                       test_success=$(($test_success + 1))
+               fi
+       else
+               if [ "$verbose" = t ]; then
+                       output=`echo; echo "# Stderr is:"; cat "$stderr"`
+               else
+                       output=
+               fi
+               # rm first in case test_failure exits.
+               rm "$stderr"
+               if test $test_external_has_tap -eq 0; then
+                       test_failure_ "$descr" "$@" "$output"
+               else
+                       say_color error "# test_external_without_stderr test $descr failed: $@: $output"
+                       test_failure=$(($test_failure + 1))
+               fi
+       fi
+}
+
+# debugging-friendly alternatives to "test [-f|-d|-e]"
+# The commands test the existence or non-existence of $1. $2 can be
+# given to provide a more precise diagnosis.
+test_path_is_file () {
+       if ! [ -f "$1" ]
+       then
+               echo "File $1 doesn't exist. $*"
+               false
+       fi
+}
+
+test_path_is_dir () {
+       if ! [ -d "$1" ]
+       then
+               echo "Directory $1 doesn't exist. $*"
+               false
+       fi
+}
+
+test_path_is_missing () {
+       if [ -e "$1" ]
+       then
+               echo "Path exists:"
+               ls -ld "$1"
+               if [ $# -ge 1 ]; then
+                       echo "$*"
+               fi
+               false
+       fi
+}
+
+# test_line_count checks that a file has the number of lines it
+# ought to. For example:
+#
+#      test_expect_success 'produce exactly one line of output' '
+#              do something >output &&
+#              test_line_count = 1 output
+#      '
+#
+# is like "test $(wc -l <output) = 1" except that it passes the
+# output through when the number of lines is wrong.
+
+test_line_count () {
+       if test $# != 3
+       then
+               error "bug in the test script: not 3 parameters to test_line_count"
+       elif ! test $(wc -l <"$3") "$1" "$2"
+       then
+               echo "test_line_count: line count for $3 !$1 $2"
+               cat "$3"
+               return 1
+       fi
+}
+
+# This is not among top-level (test_expect_success | test_expect_failure)
+# but is a prefix that can be used in the test script, like:
+#
+#      test_expect_success 'complain and die' '
+#           do something &&
+#           do something else &&
+#          test_must_fail git checkout ../outerspace
+#      '
+#
+# Writing this as "! git checkout ../outerspace" is wrong, because
+# the failure could be due to a segv.  We want a controlled failure.
+
+test_must_fail () {
+       "$@"
+       exit_code=$?
+       if test $exit_code = 0; then
+               echo >&2 "test_must_fail: command succeeded: $*"
+               return 1
+       elif test $exit_code -gt 129 -a $exit_code -le 192; then
+               echo >&2 "test_must_fail: died by signal: $*"
+               return 1
+       elif test $exit_code = 127; then
+               echo >&2 "test_must_fail: command not found: $*"
+               return 1
+       fi
+       return 0
+}
+
+# Similar to test_must_fail, but tolerates success, too.  This is
+# meant to be used in contexts like:
+#
+#      test_expect_success 'some command works without configuration' '
+#              test_might_fail git config --unset all.configuration &&
+#              do something
+#      '
+#
+# Writing "git config --unset all.configuration || :" would be wrong,
+# because we want to notice if it fails due to segv.
+
+test_might_fail () {
+       "$@"
+       exit_code=$?
+       if test $exit_code -gt 129 -a $exit_code -le 192; then
+               echo >&2 "test_might_fail: died by signal: $*"
+               return 1
+       elif test $exit_code = 127; then
+               echo >&2 "test_might_fail: command not found: $*"
+               return 1
+       fi
+       return 0
+}
+
+# Similar to test_must_fail and test_might_fail, but check that a
+# given command exited with a given exit code. Meant to be used as:
+#
+#      test_expect_success 'Merge with d/f conflicts' '
+#              test_expect_code 1 git merge "merge msg" B master
+#      '
+
+test_expect_code () {
+       want_code=$1
+       shift
+       "$@"
+       exit_code=$?
+       if test $exit_code = $want_code
+       then
+               return 0
+       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.
+# You can use it like:
+#
+#      test_expect_success 'foo works' '
+#              echo expected >expected &&
+#              foo >actual &&
+#              test_cmp expected actual
+#      '
+#
+# This could be written as either "cmp" or "diff -u", but:
+# - cmp's output is not nearly as easy to read as diff -u
+# - not all diff versions understand "-u"
+
+test_cmp() {
+       $GIT_TEST_CMP "$@"
+}
+
+# This function can be used to schedule some commands to be run
+# unconditionally at the end of the test to restore sanity:
+#
+#      test_expect_success 'test core.capslock' '
+#              git config core.capslock true &&
+#              test_when_finished "git config --unset core.capslock" &&
+#              hello world
+#      '
+#
+# That would be roughly equivalent to
+#
+#      test_expect_success 'test core.capslock' '
+#              git config core.capslock true &&
+#              hello world
+#              git config --unset core.capslock
+#      '
+#
+# 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="{ $*
+               } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup"
+}
+
+# Most tests can use the created repository, but some may need to create more.
+# Usage: test_create_repo <directory>
+test_create_repo () {
+       test "$#" = 1 ||
+       error "bug in the test script: not 1 parameter to test-create-repo"
+       repo="$1"
+       mkdir -p "$repo"
+       (
+               cd "$repo" || error "Cannot setup test environment"
+               "$GIT_EXEC_PATH/git-init" "--template=$GIT_BUILD_DIR/templates/blt/" >&3 2>&4 ||
+               error "cannot run git init -- have you built things yet?"
+               mv .git/hooks .git/hooks-disabled
+       ) || exit
+}
index e28d5fdebe21f33b91942ce00125cc215f6427fd..d75766adaf127bbe77022da4ba1e2af1fbdff878 100644 (file)
@@ -55,6 +55,7 @@ unset $(perl -e '
                .*_TEST
                PROVE
                VALGRIND
+               PERF_AGGREGATING_LATER
        ));
        my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env);
        print join("\n", @vars);
@@ -98,6 +99,8 @@ _z40=0000000000000000000000000000000000000000
 LF='
 '
 
+export _x05 _x40 _z40 LF
+
 # Each test should start with something like this, after copyright notices:
 #
 # test_description='Description of this test...
@@ -223,248 +226,9 @@ die () {
 GIT_EXIT_OK=
 trap 'die' EXIT
 
-# The semantics of the editor variables are that of invoking
-# sh -c "$EDITOR \"$@\"" files ...
-#
-# If our trash directory contains shell metacharacters, they will be
-# interpreted if we just set $EDITOR directly, so do a little dance with
-# environment variables to work around this.
-#
-# In particular, quoting isn't enough, as the path may contain the same quote
-# that we're using.
-test_set_editor () {
-       FAKE_EDITOR="$1"
-       export FAKE_EDITOR
-       EDITOR='"$FAKE_EDITOR"'
-       export EDITOR
-}
-
-test_decode_color () {
-       awk '
-               function name(n) {
-                       if (n == 0) return "RESET";
-                       if (n == 1) return "BOLD";
-                       if (n == 30) return "BLACK";
-                       if (n == 31) return "RED";
-                       if (n == 32) return "GREEN";
-                       if (n == 33) return "YELLOW";
-                       if (n == 34) return "BLUE";
-                       if (n == 35) return "MAGENTA";
-                       if (n == 36) return "CYAN";
-                       if (n == 37) return "WHITE";
-                       if (n == 40) return "BLACK";
-                       if (n == 41) return "BRED";
-                       if (n == 42) return "BGREEN";
-                       if (n == 43) return "BYELLOW";
-                       if (n == 44) return "BBLUE";
-                       if (n == 45) return "BMAGENTA";
-                       if (n == 46) return "BCYAN";
-                       if (n == 47) return "BWHITE";
-               }
-               {
-                       while (match($0, /\033\[[0-9;]*m/) != 0) {
-                               printf "%s<", substr($0, 1, RSTART-1);
-                               codes = substr($0, RSTART+2, RLENGTH-3);
-                               if (length(codes) == 0)
-                                       printf "%s", name(0)
-                               else {
-                                       n = split(codes, ary, ";");
-                                       sep = "";
-                                       for (i = 1; i <= n; i++) {
-                                               printf "%s%s", sep, name(ary[i]);
-                                               sep = ";"
-                                       }
-                               }
-                               printf ">";
-                               $0 = substr($0, RSTART + RLENGTH, length($0) - RSTART - RLENGTH + 1);
-                       }
-                       print
-               }
-       '
-}
-
-nul_to_q () {
-       perl -pe 'y/\000/Q/'
-}
-
-q_to_nul () {
-       perl -pe 'y/Q/\000/'
-}
-
-q_to_cr () {
-       tr Q '\015'
-}
-
-q_to_tab () {
-       tr Q '\011'
-}
-
-append_cr () {
-       sed -e 's/$/Q/' | tr Q '\015'
-}
-
-remove_cr () {
-       tr '\015' Q | sed -e 's/Q$//'
-}
-
-# In some bourne shell implementations, the "unset" builtin returns
-# nonzero status when a variable to be unset was not set in the first
-# place.
-#
-# Use sane_unset when that should not be considered an error.
-
-sane_unset () {
-       unset "$@"
-       return 0
-}
-
-test_tick () {
-       if test -z "${test_tick+set}"
-       then
-               test_tick=1112911993
-       else
-               test_tick=$(($test_tick + 60))
-       fi
-       GIT_COMMITTER_DATE="$test_tick -0700"
-       GIT_AUTHOR_DATE="$test_tick -0700"
-       export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
-}
-
-# Stop execution and start a shell. This is useful for debugging tests and
-# only makes sense together with "-v".
-#
-# Be sure to remove all invocations of this command before submitting.
-
-test_pause () {
-       if test "$verbose" = t; then
-               "$SHELL_PATH" <&6 >&3 2>&4
-       else
-               error >&5 "test_pause requires --verbose"
-       fi
-}
-
-# Call test_commit with the arguments "<message> [<file> [<contents>]]"
-#
-# This will commit a file with the given contents and the given commit
-# message.  It will also add a tag with <message> as name.
-#
-# Both <file> and <contents> default to <message>.
-
-test_commit () {
-       file=${2:-"$1.t"}
-       echo "${3-$1}" > "$file" &&
-       git add "$file" &&
-       test_tick &&
-       git commit -m "$1" &&
-       git tag "$1"
-}
-
-# Call test_merge with the arguments "<message> <commit>", where <commit>
-# can be a tag pointing to the commit-to-merge.
-
-test_merge () {
-       test_tick &&
-       git merge -m "$1" "$2" &&
-       git tag "$1"
-}
-
-# This function helps systems where core.filemode=false is set.
-# Use it instead of plain 'chmod +x' to set or unset the executable bit
-# of a file in the working directory and add it to the index.
-
-test_chmod () {
-       chmod "$@" &&
-       git update-index --add "--chmod=$@"
-}
-
-# Unset a configuration variable, but don't fail if it doesn't exist.
-test_unconfig () {
-       git config --unset-all "$@"
-       config_status=$?
-       case "$config_status" in
-       5) # ok, nothing to unset
-               config_status=0
-               ;;
-       esac
-       return $config_status
-}
-
-# Set git config, automatically unsetting it after the test is over.
-test_config () {
-       test_when_finished "test_unconfig '$1'" &&
-       git config "$@"
-}
-
-
-test_config_global () {
-       test_when_finished "test_unconfig --global '$1'" &&
-       git config --global "$@"
-}
-
-write_script () {
-       {
-               echo "#!${2-"$SHELL_PATH"}" &&
-               cat
-       } >"$1" &&
-       chmod +x "$1"
-}
-
-# Use test_set_prereq to tell that a particular prerequisite is available.
-# The prerequisite can later be checked for in two ways:
-#
-# - Explicitly using test_have_prereq.
-#
-# - Implicitly by specifying the prerequisite tag in the calls to
-#   test_expect_{success,failure,code}.
-#
-# The single parameter is the prerequisite tag (a simple word, in all
-# capital letters by convention).
-
-test_set_prereq () {
-       satisfied="$satisfied$1 "
-}
-satisfied=" "
-
-test_have_prereq () {
-       # prerequisites can be concatenated with ','
-       save_IFS=$IFS
-       IFS=,
-       set -- $*
-       IFS=$save_IFS
-
-       total_prereq=0
-       ok_prereq=0
-       missing_prereq=
-
-       for prerequisite
-       do
-               total_prereq=$(($total_prereq + 1))
-               case $satisfied in
-               *" $prerequisite "*)
-                       ok_prereq=$(($ok_prereq + 1))
-                       ;;
-               *)
-                       # Keep a list of missing prerequisites
-                       if test -z "$missing_prereq"
-                       then
-                               missing_prereq=$prerequisite
-                       else
-                               missing_prereq="$prerequisite,$missing_prereq"
-                       fi
-               esac
-       done
-
-       test $total_prereq = $ok_prereq
-}
-
-test_declared_prereq () {
-       case ",$test_prereq," in
-       *,$1,*)
-               return 0
-               ;;
-       esac
-       return 1
-}
+# The user-facing functions are loaded from a separate file so that
+# test_perf subshells can have them too
+. "${TEST_DIRECTORY:-.}"/test-lib-functions.sh
 
 # You are not expected to call test_ok_ and test_failure_ directly, use
 # the text_expect_* functions instead.
@@ -552,318 +316,16 @@ test_skip () {
        esac
 }
 
-test_expect_failure () {
-       test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
-       test "$#" = 2 ||
-       error "bug in the test script: not 2 or 3 parameters to test-expect-failure"
-       export test_prereq
-       if ! test_skip "$@"
-       then
-               say >&3 "checking known breakage: $2"
-               if test_run_ "$2" expecting_failure
-               then
-                       test_known_broken_ok_ "$1"
-               else
-                       test_known_broken_failure_ "$1"
-               fi
-       fi
-       echo >&3 ""
-}
-
-test_expect_success () {
-       test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
-       test "$#" = 2 ||
-       error "bug in the test script: not 2 or 3 parameters to test-expect-success"
-       export test_prereq
-       if ! test_skip "$@"
-       then
-               say >&3 "expecting success: $2"
-               if test_run_ "$2"
-               then
-                       test_ok_ "$1"
-               else
-                       test_failure_ "$@"
-               fi
-       fi
-       echo >&3 ""
-}
-
-# test_external runs external test scripts that provide continuous
-# test output about their progress, and succeeds/fails on
-# zero/non-zero exit code.  It outputs the test output on stdout even
-# in non-verbose mode, and announces the external script with "# run
-# <n>: ..." before running it.  When providing relative paths, keep in
-# mind that all scripts run in "trash directory".
-# Usage: test_external description command arguments...
-# Example: test_external 'Perl API' perl ../path/to/test.pl
-test_external () {
-       test "$#" = 4 && { test_prereq=$1; shift; } || test_prereq=
-       test "$#" = 3 ||
-       error >&5 "bug in the test script: not 3 or 4 parameters to test_external"
-       descr="$1"
-       shift
-       export test_prereq
-       if ! test_skip "$descr" "$@"
-       then
-               # Announce the script to reduce confusion about the
-               # test output that follows.
-               say_color "" "# run $test_count: $descr ($*)"
-               # Export TEST_DIRECTORY, TRASH_DIRECTORY and GIT_TEST_LONG
-               # to be able to use them in script
-               export TEST_DIRECTORY TRASH_DIRECTORY GIT_TEST_LONG
-               # Run command; redirect its stderr to &4 as in
-               # test_run_, but keep its stdout on our stdout even in
-               # non-verbose mode.
-               "$@" 2>&4
-               if [ "$?" = 0 ]
-               then
-                       if test $test_external_has_tap -eq 0; then
-                               test_ok_ "$descr"
-                       else
-                               say_color "" "# test_external test $descr was ok"
-                               test_success=$(($test_success + 1))
-                       fi
-               else
-                       if test $test_external_has_tap -eq 0; then
-                               test_failure_ "$descr" "$@"
-                       else
-                               say_color error "# test_external test $descr failed: $@"
-                               test_failure=$(($test_failure + 1))
-                       fi
-               fi
-       fi
-}
-
-# Like test_external, but in addition tests that the command generated
-# no output on stderr.
-test_external_without_stderr () {
-       # The temporary file has no (and must have no) security
-       # implications.
-       tmp=${TMPDIR:-/tmp}
-       stderr="$tmp/git-external-stderr.$$.tmp"
-       test_external "$@" 4> "$stderr"
-       [ -f "$stderr" ] || error "Internal error: $stderr disappeared."
-       descr="no stderr: $1"
-       shift
-       say >&3 "# expecting no stderr from previous command"
-       if [ ! -s "$stderr" ]; then
-               rm "$stderr"
-
-               if test $test_external_has_tap -eq 0; then
-                       test_ok_ "$descr"
-               else
-                       say_color "" "# test_external_without_stderr test $descr was ok"
-                       test_success=$(($test_success + 1))
-               fi
-       else
-               if [ "$verbose" = t ]; then
-                       output=`echo; echo "# Stderr is:"; cat "$stderr"`
-               else
-                       output=
-               fi
-               # rm first in case test_failure exits.
-               rm "$stderr"
-               if test $test_external_has_tap -eq 0; then
-                       test_failure_ "$descr" "$@" "$output"
-               else
-                       say_color error "# test_external_without_stderr test $descr failed: $@: $output"
-                       test_failure=$(($test_failure + 1))
-               fi
-       fi
-}
-
-# debugging-friendly alternatives to "test [-f|-d|-e]"
-# The commands test the existence or non-existence of $1. $2 can be
-# given to provide a more precise diagnosis.
-test_path_is_file () {
-       if ! [ -f "$1" ]
-       then
-               echo "File $1 doesn't exist. $*"
-               false
-       fi
-}
-
-test_path_is_dir () {
-       if ! [ -d "$1" ]
-       then
-               echo "Directory $1 doesn't exist. $*"
-               false
-       fi
-}
-
-test_path_is_missing () {
-       if [ -e "$1" ]
-       then
-               echo "Path exists:"
-               ls -ld "$1"
-               if [ $# -ge 1 ]; then
-                       echo "$*"
-               fi
-               false
-       fi
-}
-
-# test_line_count checks that a file has the number of lines it
-# ought to. For example:
-#
-#      test_expect_success 'produce exactly one line of output' '
-#              do something >output &&
-#              test_line_count = 1 output
-#      '
-#
-# is like "test $(wc -l <output) = 1" except that it passes the
-# output through when the number of lines is wrong.
-
-test_line_count () {
-       if test $# != 3
-       then
-               error "bug in the test script: not 3 parameters to test_line_count"
-       elif ! test $(wc -l <"$3") "$1" "$2"
-       then
-               echo "test_line_count: line count for $3 !$1 $2"
-               cat "$3"
-               return 1
-       fi
-}
-
-# This is not among top-level (test_expect_success | test_expect_failure)
-# but is a prefix that can be used in the test script, like:
-#
-#      test_expect_success 'complain and die' '
-#           do something &&
-#           do something else &&
-#          test_must_fail git checkout ../outerspace
-#      '
-#
-# Writing this as "! git checkout ../outerspace" is wrong, because
-# the failure could be due to a segv.  We want a controlled failure.
-
-test_must_fail () {
-       "$@"
-       exit_code=$?
-       if test $exit_code = 0; then
-               echo >&2 "test_must_fail: command succeeded: $*"
-               return 1
-       elif test $exit_code -gt 129 -a $exit_code -le 192; then
-               echo >&2 "test_must_fail: died by signal: $*"
-               return 1
-       elif test $exit_code = 127; then
-               echo >&2 "test_must_fail: command not found: $*"
-               return 1
-       fi
-       return 0
-}
-
-# Similar to test_must_fail, but tolerates success, too.  This is
-# meant to be used in contexts like:
-#
-#      test_expect_success 'some command works without configuration' '
-#              test_might_fail git config --unset all.configuration &&
-#              do something
-#      '
-#
-# Writing "git config --unset all.configuration || :" would be wrong,
-# because we want to notice if it fails due to segv.
-
-test_might_fail () {
-       "$@"
-       exit_code=$?
-       if test $exit_code -gt 129 -a $exit_code -le 192; then
-               echo >&2 "test_might_fail: died by signal: $*"
-               return 1
-       elif test $exit_code = 127; then
-               echo >&2 "test_might_fail: command not found: $*"
-               return 1
-       fi
-       return 0
-}
-
-# Similar to test_must_fail and test_might_fail, but check that a
-# given command exited with a given exit code. Meant to be used as:
-#
-#      test_expect_success 'Merge with d/f conflicts' '
-#              test_expect_code 1 git merge "merge msg" B master
-#      '
-
-test_expect_code () {
-       want_code=$1
-       shift
-       "$@"
-       exit_code=$?
-       if test $exit_code = $want_code
-       then
-               return 0
-       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.
-# You can use it like:
-#
-#      test_expect_success 'foo works' '
-#              echo expected >expected &&
-#              foo >actual &&
-#              test_cmp expected actual
-#      '
-#
-# This could be written as either "cmp" or "diff -u", but:
-# - cmp's output is not nearly as easy to read as diff -u
-# - not all diff versions understand "-u"
-
-test_cmp() {
-       $GIT_TEST_CMP "$@"
-}
-
-# This function can be used to schedule some commands to be run
-# unconditionally at the end of the test to restore sanity:
-#
-#      test_expect_success 'test core.capslock' '
-#              git config core.capslock true &&
-#              test_when_finished "git config --unset core.capslock" &&
-#              hello world
-#      '
-#
-# That would be roughly equivalent to
-#
-#      test_expect_success 'test core.capslock' '
-#              git config core.capslock true &&
-#              hello world
-#              git config --unset core.capslock
-#      '
-#
-# 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="{ $*
-               } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup"
-}
-
-# Most tests can use the created repository, but some may need to create more.
-# Usage: test_create_repo <directory>
-test_create_repo () {
-       test "$#" = 1 ||
-       error "bug in the test script: not 1 parameter to test-create-repo"
-       repo="$1"
-       mkdir -p "$repo"
-       (
-               cd "$repo" || error "Cannot setup test environment"
-               "$GIT_EXEC_PATH/git-init" "--template=$GIT_BUILD_DIR/templates/blt/" >&3 2>&4 ||
-               error "cannot run git init -- have you built things yet?"
-               mv .git/hooks .git/hooks-disabled
-       ) || exit
+# stub; perf-lib overrides it
+test_at_end_hook_ () {
+       :
 }
 
 test_done () {
        GIT_EXIT_OK=t
 
        if test -z "$HARNESS_ACTIVE"; then
-               test_results_dir="$TEST_DIRECTORY/test-results"
+               test_results_dir="$TEST_OUTPUT_DIRECTORY/test-results"
                mkdir -p "$test_results_dir"
                test_results_path="$test_results_dir/${0%.sh}-$$.counts"
 
@@ -902,6 +364,8 @@ test_done () {
                cd "$(dirname "$remove_trash")" &&
                rm -rf "$(basename "$remove_trash")"
 
+               test_at_end_hook_
+
                exit 0 ;;
 
        *)
@@ -924,6 +388,12 @@ then
        # itself.
        TEST_DIRECTORY=$(pwd)
 fi
+if test -z "$TEST_OUTPUT_DIRECTORY"
+then
+       # Similarly, override this to store the test-results subdir
+       # elsewhere
+       TEST_OUTPUT_DIRECTORY=$TEST_DIRECTORY
+fi
 GIT_BUILD_DIR="$TEST_DIRECTORY"/..
 
 if test -n "$valgrind"
@@ -1059,7 +529,7 @@ test="trash directory.$(basename "$0" .sh)"
 test -n "$root" && test="$root/$test"
 case "$test" in
 /*) TRASH_DIRECTORY="$test" ;;
- *) TRASH_DIRECTORY="$TEST_DIRECTORY/$test" ;;
+ *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$test" ;;
 esac
 test ! -z "$debug" || remove_trash=$TRASH_DIRECTORY
 rm -fr "$test" || {
@@ -1071,7 +541,11 @@ rm -fr "$test" || {
 HOME="$TRASH_DIRECTORY"
 export HOME
 
-test_create_repo "$test"
+if test -z "$TEST_NO_CREATE_REPO"; then
+       test_create_repo "$test"
+else
+       mkdir -p "$test"
+fi
 # Use -P to resolve symlinks in our working directory so that the cwd
 # in subprocesses like git equals our $PWD (for pathname comparisons).
 cd -P "$test" || exit 1
index 6f227e253bf638de37ce74347213657c23185afa..f6b3b1fb79468ef85eb9581c7d44ff04c1bb61bc 100644 (file)
@@ -9,6 +9,7 @@
 #include "remote.h"
 #include "string-list.h"
 #include "thread-utils.h"
+#include "sigchain.h"
 
 static int debug;
 
@@ -220,15 +221,21 @@ static struct child_process *get_helper(struct transport *transport)
 static int disconnect_helper(struct transport *transport)
 {
        struct helper_data *data = transport->data;
-       struct strbuf buf = STRBUF_INIT;
        int res = 0;
 
        if (data->helper) {
                if (debug)
                        fprintf(stderr, "Debug: Disconnecting.\n");
                if (!data->no_disconnect_req) {
-                       strbuf_addf(&buf, "\n");
-                       sendline(data, &buf);
+                       /*
+                        * Ignore write errors; there's nothing we can do,
+                        * since we're about to close the pipe anyway. And the
+                        * most likely error is EPIPE due to the helper dying
+                        * to report an error itself.
+                        */
+                       sigchain_push(SIGPIPE, SIG_IGN);
+                       xwrite(data->helper->in, "\n", 1);
+                       sigchain_pop(SIGPIPE);
                }
                close(data->helper->in);
                close(data->helper->out);
index cac0c065ff9f82011b204f932932283b01a5d034..181f8f24d14e91c106b1d36133292a7ee99333fb 100644 (file)
@@ -993,11 +993,15 @@ void transport_set_verbosity(struct transport *transport, int verbosity,
         * Rules used to determine whether to report progress (processing aborts
         * when a rule is satisfied):
         *
-        *   1. Report progress, if force_progress is 1 (ie. --progress).
-        *   2. Don't report progress, if verbosity < 0 (ie. -q/--quiet ).
-        *   3. Report progress if isatty(2) is 1.
+        *   . Report progress, if force_progress is 1 (ie. --progress).
+        *   . Don't report progress, if force_progress is 0 (ie. --no-progress).
+        *   . Don't report progress, if verbosity < 0 (ie. -q/--quiet ).
+        *   . Report progress if isatty(2) is 1.
         **/
-       transport->progress = force_progress || (verbosity >= 0 && isatty(2));
+       if (force_progress >= 0)
+               transport->progress = !!force_progress;
+       else
+               transport->progress = verbosity >= 0 && isatty(2);
 }
 
 int transport_push(struct transport *transport,
@@ -1028,6 +1032,8 @@ int transport_push(struct transport *transport,
                        match_flags |= MATCH_REFS_ALL;
                if (flags & TRANSPORT_PUSH_MIRROR)
                        match_flags |= MATCH_REFS_MIRROR;
+               if (flags & TRANSPORT_PUSH_PRUNE)
+                       match_flags |= MATCH_REFS_PRUNE;
 
                if (match_push_refs(local_refs, &remote_refs,
                                    refspec_nr, refspec, match_flags)) {
index 059b3303e20f8335cea388dfccfc2740d3c3d43e..ce99ef8b7e1692b6b77bd7fbdee5858ce4bfc408 100644 (file)
@@ -102,6 +102,7 @@ struct transport {
 #define TRANSPORT_PUSH_PORCELAIN 16
 #define TRANSPORT_PUSH_SET_UPSTREAM 32
 #define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
+#define TRANSPORT_PUSH_PRUNE 128
 
 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)