Code

Merge branch 'master' into git-po
authorJiang Xin <worldhello.net@gmail.com>
Tue, 28 Feb 2012 04:23:26 +0000 (12:23 +0800)
committerJiang Xin <worldhello.net@gmail.com>
Tue, 28 Feb 2012 04:23:26 +0000 (12:23 +0800)
201 files changed:
Documentation/CodingGuidelines
Documentation/RelNotes/1.7.10.txt
Documentation/RelNotes/1.7.8.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.9.1.txt
Documentation/RelNotes/1.7.9.2.txt [new file with mode: 0644]
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-send-email.txt
Documentation/git-tag.txt
Documentation/git.txt
Documentation/gitcore-tutorial.txt
Documentation/gittutorial-2.txt
Documentation/merge-options.txt
Documentation/technical/api-config.txt [new file with mode: 0644]
Documentation/technical/api-strbuf.txt
INSTALL
Makefile
README
builtin/apply.c
builtin/blame.c
builtin/cat-file.c
builtin/checkout.c
builtin/clone.c
builtin/commit.c
builtin/config.c
builtin/fetch-pack.c
builtin/fetch.c
builtin/grep.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-tree.c
cache-tree.h
cache.h
compat/inet_ntop.c
compat/inet_pton.c
config.c
config.mak.in
configure.ac
contrib/completion/git-completion.bash
contrib/diff-highlight/README
contrib/diff-highlight/diff-highlight
contrib/fast-import/git-p4
contrib/hooks/post-receive-email
convert.c
convert.h
ctype.c
diff.c
diff.h
git-am.sh
git-compat-util.h
git-pull.sh
git-rebase--merge.sh
git-svn.perl
gitweb/gitweb.perl
grep.c
grep.h
help.c
mailmap.c
merge-recursive.c
mergetools/meld
pager.c
path.c
prompt.c
read-cache.c
refs.c
refs.h
remote.c
remote.h
revision.c
setup.c
sha1_file.c
strbuf.c
strbuf.h
t/Makefile
t/README
t/harness [deleted file]
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/t1200-tutorial.sh
t/t1300-repo-config.sh
t/t1305-config-include.sh [new file with mode: 0755]
t/t1450-fsck.sh
t/t2015-checkout-unborn.sh
t/t2203-add-intent.sh
t/t3300-funny-names.sh
t/t3508-cherry-pick-many-commits.sh
t/t3700-add.sh
t/t3903-stash.sh
t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_master
t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_side
t/t4013/diff.diff-tree_--cc_--patch-with-stat_master
t/t4013/diff.diff-tree_--cc_--stat_--summary_master
t/t4013/diff.diff-tree_--cc_--stat_--summary_side
t/t4013/diff.diff-tree_--cc_--stat_master
t/t4013/diff.diff-tree_--pretty=oneline_--root_--patch-with-stat_initial
t/t4013/diff.diff-tree_--pretty_--patch-with-stat_side
t/t4013/diff.diff-tree_--pretty_--root_--patch-with-stat_initial
t/t4013/diff.diff-tree_--pretty_--root_--stat_--summary_initial
t/t4013/diff.diff-tree_--pretty_--root_--stat_initial
t/t4013/diff.diff-tree_--root_--patch-with-stat_initial
t/t4013/diff.diff-tree_-c_--stat_--summary_master
t/t4013/diff.diff-tree_-c_--stat_--summary_side
t/t4013/diff.diff-tree_-c_--stat_master
t/t4013/diff.diff_--patch-with-stat_-r_initial..side
t/t4013/diff.diff_--patch-with-stat_initial..side
t/t4013/diff.diff_--stat_initial..side
t/t4013/diff.diff_-r_--stat_initial..side
t/t4013/diff.format-patch_--attach_--stdout_--suffix=.diff_initial..side
t/t4013/diff.format-patch_--attach_--stdout_initial..master
t/t4013/diff.format-patch_--attach_--stdout_initial..master^
t/t4013/diff.format-patch_--attach_--stdout_initial..side
t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..master
t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master
t/t4013/diff.format-patch_--inline_--stdout_initial..master
t/t4013/diff.format-patch_--inline_--stdout_initial..master^
t/t4013/diff.format-patch_--inline_--stdout_initial..side
t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^
t/t4013/diff.format-patch_--stdout_--no-numbered_initial..master
t/t4013/diff.format-patch_--stdout_--numbered_initial..master
t/t4013/diff.format-patch_--stdout_initial..master
t/t4013/diff.format-patch_--stdout_initial..master^
t/t4013/diff.format-patch_--stdout_initial..side
t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_
t/t4013/diff.log_--patch-with-stat_master
t/t4013/diff.log_--patch-with-stat_master_--_dir_
t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master
t/t4013/diff.log_--root_--patch-with-stat_--summary_master
t/t4013/diff.log_--root_--patch-with-stat_master
t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master
t/t4013/diff.show_--patch-with-stat_--summary_side
t/t4013/diff.show_--patch-with-stat_side
t/t4013/diff.show_--stat_--summary_side
t/t4013/diff.show_--stat_side
t/t4013/diff.whatchanged_--patch-with-stat_--summary_master_--_dir_
t/t4013/diff.whatchanged_--patch-with-stat_master
t/t4013/diff.whatchanged_--patch-with-stat_master_--_dir_
t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master
t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_master
t/t4013/diff.whatchanged_--root_--patch-with-stat_master
t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master
t/t4014-format-patch.sh
t/t4030-diff-textconv.sh
t/t4045-diff-relative.sh
t/t4049-diff-stat-count.sh
t/t4100/t-apply-8.expect
t/t4100/t-apply-9.expect
t/t4150-am.sh
t/t5150-request-pull.sh
t/t5300-pack-object.sh
t/t5302-pack-index.sh
t/t5504-fetch-receive-strict.sh
t/t5516-fetch-push.sh
t/t5523-push-upstream.sh
t/t5541-http-push.sh
t/t5560-http-backend-noserver.sh
t/t5700-clone-reference.sh
t/t5704-bundle.sh
t/t5900-repo-selection.sh [new file with mode: 0755]
t/t6032-merge-large-rename.sh
t/t7004-tag.sh
t/t7008-grep-binary.sh
t/t7600-merge.sh
t/t7602-merge-octopus-many.sh
t/t7810-grep.sh
t/t9100-git-svn-basic.sh
t/t9130-git-svn-authors-file.sh
t/t9200-git-cvsexportcommit.sh
t/t9500-gitweb-standalone-no-errors.sh
t/t9800-git-p4-basic.sh
t/t9804-git-p4-label.sh
t/t9808-git-p4-chdir.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
test-dump-cache-tree.c
transport-helper.c
transport.c
transport.h
userdiff.c

index 483008699f923be17926e8ed938ae17868f6ddf5..45577117c2a02dd4a4f9e63e78139b3df665b8f2 100644 (file)
@@ -35,10 +35,22 @@ For shell scripts specifically (not exhaustive):
 
  - Case arms are indented at the same depth as case and esac lines.
 
+ - Redirection operators should be written with space before, but no
+   space after them.  In other words, write 'echo test >"$file"'
+   instead of 'echo test> $file' or 'echo test > $file'.  Note that
+   even though it is not required by POSIX to double-quote the
+   redirection target in a variable (as shown above), our code does so
+   because some versions of bash issue a warning without the quotes.
+
  - We prefer $( ... ) for command substitution; unlike ``, it
    properly nests.  It should have been the way Bourne spelled
    it from day one, but unfortunately isn't.
 
+ - If you want to find out if a command is available on the user's
+   $PATH, you should use 'type <command>', instead of 'which <command>'.
+   The output of 'which' is not machine parseable and its exit code
+   is not reliable across platforms.
+
  - We use POSIX compliant parameter substitutions and avoid bashisms;
    namely:
 
index e2559013566d3a503112f9ff8a1e3a08c171fe9c..89edfdafc75eb335c60a199626f3a65e49069e8c 100644 (file)
@@ -8,9 +8,20 @@ UI, Workflows & Features
 
  * Improved handling of views, labels and branches in git-p4 (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.
 
@@ -24,31 +35,52 @@ UI, Workflows & Features
    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 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.
+   can export GIT_MERGE_AUTOEDIT=no to retain the historical behavior.
+   Both "git merge" and "git pull" can be given --no-edit from the
+   command line to accept the auto-generated merge message.
+
+ * "git push" learned the "--prune" option, similar to "git fetch".
+
+ * "git tag --list" can be given "--points-at <object>" to limit its
+   output to those that point at the given object.
 
  * "gitweb" allows intermediate entries in the directory hierarchy
    that leads to a projects to be clicked, which in turn shows the
    list of projects inside that directory.
 
+ * "gitweb" learned to read various pieces of information for the
+   repositories lazily, instead of reading everything that could be
+   needed (including the ones that are not necessary for a specific
+   task).
+
 Performance
 
- * During "git upload-pack" in respose to "git fetch", unnecessary calls
+ * During "git upload-pack" in response to "git fetch", unnecessary calls
    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.
 
@@ -62,30 +94,35 @@ 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).
 
- * "add -e" learned not to show a diff for an otherwise unmodified
-   submodule that only has uncommitted local changes in the patch
-   prepared by for the user to edit.
-   (merge 701825d js/add-e-submodule-fix later to maint).
+ * The bulk check-in codepath streamed contents that needs
+   smudge/clean filters without running them, instead of punting and
+   delegating to the codepath to run filters after slurping everything
+   to core.
+   (merge 4f22b10 jk/maint-avoid-streaming-filtered-contents later to maint).
 
- * "rebase" and "commit --amend" failed to work on commits with ancient
-   timestamps near year 1970.
-   (merge 2c733fb jc/parse-date-raw later to maint).
+ * When the filter driver exits before reading the content before the
+   main git process writes the contents to be filtered to the pipe to
+   it, the latter could be killed with SIGPIPE instead of ignoring
+   such an event as an error.
+   (merge 6424c2a jb/filter-ignore-sigpipe later to maint).
 
- * "git merge --ff-only $tag" failed because it cannot record the
-   required mergetag without creating a merge, but this is so common
-   operation for branch that is used _only_ to follow the upstream, so
-   it is allowed to fast-forward without recording the mergetag.
-   (merge b5c9f1c jc/merge-ff-only-stronger-than-signed-merge later to maint).
+ * When a remote helper exits before reading the blank line from the
+   main git process to signal the end of commands, the latter could be
+   killed with SIGPIPE. Instead we should ignore such event as a
+   non-error.
+   (merge c34fe63 sp/smart-http-failure-to-push later to maint).
 
- * Typo in "git branch --edit-description my-tpoic" was not diagnosed.
-   (merge c2d17ba jc/branch-desc-typoavoidance later to maint).
+ * "git bundle create" produced a corrupt bundle file upon seeing
+   commits with excessively long subject line.
+   (merge 8a557bb tr/maint-bundle-long-subject later to maint).
 
- * rpmbuild noticed an unpackaged but installed *.mo file and failed.
-   (merge 3a9f58c jn/rpm-spec 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-208-gee8d52f
+O=v1.7.9.2-301-g507fba2
 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 a6c15e5d52b2ae5958990d2fd8a113d6e893396b..6957183dbb005388e8b7d5fc2263a42841428375 100644 (file)
@@ -4,9 +4,24 @@ Git v1.7.9.1 Release Notes
 Fixes since v1.7.9
 ------------------
 
+ * The makefile allowed environment variable X seep into it result in
+   command names suffixed with unnecessary strings.
+
+ * The set of included header files in compat/inet-{ntop,pton}
+   wrappers was updated for Windows some time ago, but in a way that
+   broke Solaris build.
+
+ * rpmbuild noticed an unpackaged but installed *.mo file and failed.
+
  * Subprocesses spawned from various git programs were often left running
    to completion even when the top-level process was killed.
 
+ * "git add -e" learned not to show a diff for an otherwise unmodified
+   submodule that only has uncommitted local changes in the patch
+   prepared by for the user to edit.
+
+ * Typo in "git branch --edit-description my-tpoic" was not diagnosed.
+
  * Using "git grep -l/-L" together with options -W or --break may not
    make much sense as the output is to only count the number of hits
    and there is no place for file breaks, but the latter options made
@@ -16,15 +31,25 @@ Fixes since v1.7.9
    chain and veered into side branch from which the whole change to the
    specified paths came.
 
- * "git push -q" was not sufficiently quiet.
+ * "git merge --no-edit $tag" failed to honor the --no-edit option.
 
- * When "git push" fails to update any refs, the client side did not
-   report an error correctly to the end user.
+ * "git merge --ff-only $tag" failed because it cannot record the
+   required mergetag without creating a merge, but this is so common
+   operation for branch that is used _only_ to follow the upstream, so
+   it was changed to allow fast-forwarding without recording the mergetag.
 
  * "git mergetool" now gives an empty file as the common base version
    to the backend when dealing with the "both sides added, differently"
    case.
 
+ * "git push -q" was not sufficiently quiet.
+
+ * When "git push" fails to update any refs, the client side did not
+   report an error correctly to the end user.
+
+ * "rebase" and "commit --amend" failed to work on commits with ancient
+   timestamps near year 1970.
+
  * When asking for a tag to be pulled, "request-pull" did not show the
    name of the tag prefixed with "tags/", which would have helped older
    clients.
@@ -33,4 +58,6 @@ Fixes since v1.7.9
    in .gitmodules when the submodule at $path was once added to the
    superproject and already initialized.
 
+ * Many small corner case bugs on "git tag -n" was corrected.
+
 Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.9.2.txt b/Documentation/RelNotes/1.7.9.2.txt
new file mode 100644 (file)
index 0000000..e500da7
--- /dev/null
@@ -0,0 +1,69 @@
+Git v1.7.9.2 Release Notes
+==========================
+
+Fixes since v1.7.9.1
+--------------------
+
+ * Bash completion script (in contrib/) did not like a pattern that
+   begins with a dash to be passed to __git_ps1 helper function.
+
+ * 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.
+
+ * The build procedure for profile-directed optimized binary was not
+   working very well.
+
+ * Some systems need to explicitly link -lcharset to get locale_charset().
+
+ * t5541 ignored user-supplied port number used for HTTP server testing.
+
+ * 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..d7be177
--- /dev/null
@@ -0,0 +1,24 @@
+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.
+
+ * The commit log template given with "git merge --edit" did not have
+   a short instructive text like what "git commit" gives.
+
+ * "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.
+
+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 327233c85b4cb9af07b8866b055a83d25b21b569..324117072d55f831b07f96ca23d27be88b2abaa4 100644 (file)
@@ -198,6 +198,10 @@ must be used for each option.
        if a username is not specified (with '--smtp-user' or 'sendemail.smtpuser'),
        then authentication is not attempted.
 
+--smtp-debug=0|1::
+       Enable (1) or disable (0) debug output. If enabled, SMTP
+       commands and replies will be printed. Useful to debug TLS
+       connection and authentication problems.
 
 Automating
 ~~~~~~~~~~
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 f7e201fae30940ac00d1685bfd9411de9d81ca28..22fadeb114b56249920a3338a6ceb7bd71615a79 100644 (file)
@@ -9,11 +9,11 @@ git - the stupid content tracker
 SYNOPSIS
 --------
 [verse]
-'git' [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
+'git' [--version] [--help] [-c <name>=<value>]
+    [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
     [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
     [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
-    [-c <name>=<value>]
-    [--help] <command> [<args>]
+    <command> [<args>]
 
 DESCRIPTION
 -----------
@@ -44,9 +44,11 @@ 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/git.html[documentation for release 1.7.9]
+* 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].
 
 * link:v1.7.8.4/git.html[documentation for release 1.7.8.4]
index c27d086f68432d094a0fa7c1f9da353d6d3a0513..fb0d5692a4d2e898da0dea9c346420467722badc 100644 (file)
@@ -1004,7 +1004,7 @@ Updating from ae3a2da... to a80b4aa....
 Fast-forward (no commit created; -m option ignored)
  example |    1 +
  hello   |    1 +
- 2 files changed, 2 insertions(+), 0 deletions(-)
+ 2 files changed, 2 insertions(+)
 ----------------
 
 Because your branch did not contain anything more than what had
index f1e4422acc4ddba515da5617759f818b52cb151d..e00a4d21709118ef352bd02538e28569f00b738d 100644 (file)
@@ -34,12 +34,12 @@ $ echo 'hello world' > file.txt
 $ git add .
 $ git commit -a -m "initial commit"
 [master (root-commit) 54196cc] initial commit
- 1 files changed, 1 insertions(+), 0 deletions(-)
+ 1 file changed, 1 insertion(+)
  create mode 100644 file.txt
 $ echo 'hello world!' >file.txt
 $ git commit -a -m "add emphasis"
 [master c4d59f3] add emphasis
- 1 files changed, 1 insertions(+), 1 deletions(-)
+ 1 file changed, 1 insertion(+), 1 deletion(-)
 ------------------------------------------------
 
 What are the 7 digits of hex that git responded to the commit with?
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`::
 
diff --git a/INSTALL b/INSTALL
index 6fa83fe2b85f2611fc0253a90b7b238bf74745b7..58b2b86ccf93d045d4c406fe422e94db75533607 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -28,16 +28,25 @@ set up install paths (via config.mak.autogen), so you can write instead
 If you're willing to trade off (much) longer build time for a later
 faster git you can also do a profile feedback build with
 
-       $ make profile-all
-       # make prefix=... install
+       $ make prefix=/usr PROFILE=BUILD all
+       # make prefix=/usr PROFILE=BUILD install
 
 This will run the complete test suite as training workload and then
 rebuild git with the generated profile feedback. This results in a git
 which is a few percent faster on CPU intensive workloads.  This
 may be a good tradeoff for distribution packagers.
 
-Note that the profile feedback build stage currently generates
-a lot of additional compiler warnings.
+Or if you just want to install a profile-optimized version of git into
+your home directory, you could run:
+
+       $ make PROFILE=BUILD install
+
+As a caveat: a profile-optimized build takes a *lot* longer since the
+git tree must be built twice, and in order for the profiling
+measurements to work properly, ccache must be disabled and the test
+suite has to be run using only a single CPU.  In addition, the profile
+feedback build stage currently generates a lot of additional compiler
+warnings.
 
 Issues of note:
 
index 88268571261b72280bfd5af9477cdb7857f0d71f..be1957a5e986d2e0581123dfe4e0eeb6702c13dc 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -56,6 +56,10 @@ all::
 # FreeBSD can use either, but MinGW and some others need to use
 # libcharset.h's locale_charset() instead.
 #
+# Define CHARSET_LIB to you need to link with library other than -liconv to
+# use locale_charset() function.  On some platforms this needs to set to
+# -lcharset
+#
 # Define LIBC_CONTAINS_LIBINTL if your gettext implementation doesn't
 # need -lintl when linking.
 #
@@ -342,7 +346,7 @@ pathsep = :
 
 export prefix bindir sharedir sysconfdir gitwebdir localedir
 
-CC = gcc
+CC = cc
 AR = ar
 RM = rm -f
 DIFF = diff
@@ -460,6 +464,9 @@ PROGRAM_OBJS += http-backend.o
 PROGRAM_OBJS += sh-i18n--envsubst.o
 PROGRAM_OBJS += credential-store.o
 
+# Binary suffix, set to .exe for Windows builds
+X =
+
 PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
 
 TEST_PROGRAMS_NEED_X += test-chmtime
@@ -613,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
@@ -1698,6 +1706,7 @@ endif
 
 ifdef HAVE_LIBCHARSET_H
        BASIC_CFLAGS += -DHAVE_LIBCHARSET_H
+       EXTLIBS += $(CHARSET_LIB)
 endif
 
 ifdef HAVE_DEV_TTY
@@ -1774,6 +1783,26 @@ ifdef ASCIIDOC7
        export ASCIIDOC7
 endif
 
+### profile feedback build
+#
+
+# Can adjust this to be a global directory if you want to do extended
+# data gathering
+PROFILE_DIR := $(CURDIR)
+
+ifeq ("$(PROFILE)","GEN")
+       CFLAGS += -fprofile-generate=$(PROFILE_DIR) -DNO_NORETURN=1
+       EXTLIBS += -lgcov
+       export CCACHE_DISABLE=t
+       V=1
+else
+ifneq ("$(PROFILE)","")
+       CFLAGS += -fprofile-use=$(PROFILE_DIR) -fprofile-correction -DNO_NORETURN=1
+       export CCACHE_DISABLE=t
+       V=1
+endif
+endif
+
 # Shell quote (do not use $(call) to accommodate ancient setups);
 
 SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER))
@@ -1830,7 +1859,17 @@ export DIFF TAR INSTALL DESTDIR SHELL_PATH
 
 SHELL = $(SHELL_PATH)
 
-all:: shell_compatibility_test $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
+all:: shell_compatibility_test
+
+ifeq "$(PROFILE)" "BUILD"
+ifeq ($(filter all,$(MAKECMDGOALS)),all)
+all:: profile-clean
+       $(MAKE) PROFILE=GEN all
+       $(MAKE) PROFILE=GEN -j1 test
+endif
+endif
+
+all:: $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
 ifneq (,$X)
        $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';)
 endif
@@ -2323,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
@@ -2331,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
@@ -2367,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
@@ -2577,7 +2636,11 @@ dist-doc:
 distclean: clean
        $(RM) configure
 
-clean:
+profile-clean:
+       $(RM) $(addsuffix *.gcda,$(addprefix $(PROFILE_DIR)/, $(object_dirs)))
+       $(RM) $(addsuffix *.gcno,$(addprefix $(PROFILE_DIR)/, $(object_dirs)))
+
+clean: profile-clean
        $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o vcs-svn/*.o \
                builtin/*.o $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB)
        $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
@@ -2607,7 +2670,7 @@ ifndef NO_TCLTK
 endif
        $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-LDFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS
 
-.PHONY: all install clean strip
+.PHONY: all install profile-clean clean strip
 .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell
 .PHONY: FORCE cscope
 
@@ -2717,18 +2780,3 @@ cover_db: coverage-report
 cover_db_html: cover_db
        cover -report html -outputdir cover_db_html cover_db
 
-### profile feedback build
-#
-.PHONY: profile-all profile-clean
-
-PROFILE_GEN_CFLAGS := $(CFLAGS) -fprofile-generate -DNO_NORETURN=1
-PROFILE_USE_CFLAGS := $(CFLAGS) -fprofile-use -fprofile-correction -DNO_NORETURN=1
-
-profile-clean:
-       $(RM) $(addsuffix *.gcda,$(object_dirs))
-       $(RM) $(addsuffix *.gcno,$(object_dirs))
-
-profile-all: profile-clean
-       $(MAKE) CFLAGS="$(PROFILE_GEN_CFLAGS)" all
-       $(MAKE) CFLAGS="$(PROFILE_GEN_CFLAGS)" -j1 test
-       $(MAKE) CFLAGS="$(PROFILE_USE_CFLAGS)" all
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 c24dc546d0cc3f223c40c12aa20dc75eff13d4f9..389898f13364eb640077c1d82fefea98d9d3755f 100644 (file)
@@ -14,6 +14,7 @@
 #include "builtin.h"
 #include "string-list.h"
 #include "dir.h"
+#include "diff.h"
 #include "parse-options.h"
 
 /*
@@ -3241,7 +3242,7 @@ static void stat_patch_list(struct patch *patch)
                show_stats(patch);
        }
 
-       printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels);
+       print_stat_summary(stdout, files, adds, dels);
 }
 
 static void numstat_patch_list(struct patch *patch)
index 5a67c202f06abeaa90a7547d78b536f7f2b9db24..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);
 }
 
 /*
@@ -2050,14 +2038,8 @@ static int git_blame_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
-       switch (userdiff_config(var, value)) {
-       case 0:
-               break;
-       case -1:
+       if (userdiff_config(var, value) < 0)
                return -1;
-       default:
-               return 0;
-       }
 
        return git_default_config(var, value, cb);
 }
index 07bd984084fbbfbb826ef5b784fc68069675e73c..8ed501f220424976cc30f4a4dbf3d59f979902be 100644 (file)
@@ -226,14 +226,8 @@ static const char * const cat_file_usage[] = {
 
 static int git_cat_file_config(const char *var, const char *value, void *cb)
 {
-       switch (userdiff_config(var, value)) {
-       case 0:
-               break;
-       case -1:
+       if (userdiff_config(var, value) < 0)
                return -1;
-       default:
-               return 0;
-       }
 
        return git_default_config(var, value, cb);
 }
index 5bf96ba4d41cc9c16bb5ccabe9857724ecfe2c12..6b9061f26f5f33ae1ded811891e933441c210fb0 100644 (file)
@@ -908,6 +908,17 @@ static int parse_branchname_arg(int argc, const char **argv,
        return argcount;
 }
 
+static int switch_unborn_to_new_branch(struct checkout_opts *opts)
+{
+       int status;
+       struct strbuf branch_ref = STRBUF_INIT;
+
+       strbuf_addf(&branch_ref, "refs/heads/%s", opts->new_branch);
+       status = create_symref("HEAD", branch_ref.buf, "checkout -b");
+       strbuf_release(&branch_ref);
+       return status;
+}
+
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
        struct checkout_opts opts;
@@ -1079,5 +1090,13 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        if (opts.writeout_stage)
                die(_("--ours/--theirs is incompatible with switching branches."));
 
+       if (!new.commit) {
+               unsigned char rev[20];
+               int flag;
+
+               if (!read_ref_full("HEAD", rev, 0, &flag) &&
+                   (flag & REF_ISSYMREF) && is_null_sha1(rev))
+                       return switch_unborn_to_new_branch(&opts);
+       }
        return switch_branches(&opts, &new);
 }
index c62d4b5737ed41f3c7a2d9cbec3b77651236a467..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"),
@@ -107,7 +107,7 @@ static const char *argv_submodule[] = {
 
 static char *get_repo_path(const char *repo, int *is_bundle)
 {
-       static char *suffix[] = { "/.git", ".git", "" };
+       static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
        static char *bundle_suffix[] = { ".bundle", "" };
        struct stat st;
        int i;
@@ -117,7 +117,7 @@ static char *get_repo_path(const char *repo, int *is_bundle)
                path = mkpath("%s%s", repo, suffix[i]);
                if (stat(path, &st))
                        continue;
-               if (S_ISDIR(st.st_mode)) {
+               if (S_ISDIR(st.st_mode) && is_git_directory(path)) {
                        *is_bundle = 0;
                        return xstrdup(absolute_path(path));
                } else if (S_ISREG(st.st_mode) && st.st_size > 8) {
@@ -232,9 +232,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
 {
        char *ref_git;
        struct strbuf alternate = STRBUF_INIT;
-       struct remote *remote;
-       struct transport *transport;
-       const struct ref *extra;
 
        /* Beware: real_path() and mkpath() return static buffer */
        ref_git = xstrdup(real_path(item->string));
@@ -249,14 +246,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
        strbuf_addf(&alternate, "%s/objects", ref_git);
        add_to_alternates_file(alternate.buf);
        strbuf_release(&alternate);
-
-       remote = remote_get(ref_git);
-       transport = transport_get(remote, ref_git);
-       for (extra = transport_get_remote_refs(transport); extra;
-            extra = extra->next)
-               add_extra_ref(extra->name, extra->old_sha1, 0);
-
-       transport_disconnect(transport);
        free(ref_git);
        return 0;
 }
@@ -500,7 +489,6 @@ static void update_remote_refs(const struct ref *refs,
                               const char *msg)
 {
        if (refs) {
-               clear_extra_refs();
                write_remote_refs(mapped_refs);
                if (option_single_branch)
                        write_followtags(refs, msg);
@@ -813,28 +801,28 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        }
 
        refs = transport_get_remote_refs(transport);
-       mapped_refs = refs ? wanted_peer_refs(refs, refspec) : NULL;
 
-       /*
-        * transport_get_remote_refs() may return refs with null sha-1
-        * in mapped_refs (see struct transport->get_refs_list
-        * comment). In that case we need fetch it early because
-        * remote_head code below relies on it.
-        *
-        * for normal clones, transport_get_remote_refs() should
-        * return reliable ref set, we can delay cloning until after
-        * remote HEAD check.
-        */
-       for (ref = refs; ref; ref = ref->next)
-               if (is_null_sha1(ref->old_sha1)) {
-                       complete_refs_before_fetch = 0;
-                       break;
-               }
+       if (refs) {
+               mapped_refs = wanted_peer_refs(refs, refspec);
+               /*
+                * transport_get_remote_refs() may return refs with null sha-1
+                * in mapped_refs (see struct transport->get_refs_list
+                * comment). In that case we need fetch it early because
+                * remote_head code below relies on it.
+                *
+                * for normal clones, transport_get_remote_refs() should
+                * return reliable ref set, we can delay cloning until after
+                * remote HEAD check.
+                */
+               for (ref = refs; ref; ref = ref->next)
+                       if (is_null_sha1(ref->old_sha1)) {
+                               complete_refs_before_fetch = 0;
+                               break;
+                       }
 
-       if (!is_local && !complete_refs_before_fetch && refs)
-               transport_fetch_refs(transport, mapped_refs);
+               if (!is_local && !complete_refs_before_fetch)
+                       transport_fetch_refs(transport, mapped_refs);
 
-       if (refs) {
                remote_head = find_ref_by_name(refs, "HEAD");
                remote_head_points_at =
                        guess_remote_head(remote_head, mapped_refs, 0);
@@ -852,6 +840,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        }
        else {
                warning(_("You appear to have cloned an empty repository."));
+               mapped_refs = NULL;
                our_head_points_at = NULL;
                remote_head_points_at = NULL;
                remote_head = NULL;
index eae5a29aeb4248b972f97632026993c25d5e03da..3714582e1988f7c286412afb779cbfefe4849657 100644 (file)
@@ -400,7 +400,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
                fd = hold_locked_index(&index_lock, 1);
                add_files_to_cache(also ? prefix : NULL, pathspec, 0);
                refresh_cache_or_die(refresh_flags);
-               update_main_cache_tree(1);
+               update_main_cache_tree(WRITE_TREE_SILENT);
                if (write_cache(fd, active_cache, active_nr) ||
                    close_lock_file(&index_lock))
                        die(_("unable to write new_index file"));
@@ -421,7 +421,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
                fd = hold_locked_index(&index_lock, 1);
                refresh_cache_or_die(refresh_flags);
                if (active_cache_changed) {
-                       update_main_cache_tree(1);
+                       update_main_cache_tree(WRITE_TREE_SILENT);
                        if (write_cache(fd, active_cache, active_nr) ||
                            commit_locked_index(&index_lock))
                                die(_("unable to write new_index file"));
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 6207ecd2982761a47474b57cc945a2fc66ed84a1..7124c4b49cfba7985c5ba2046296f006de04e3bb 100644 (file)
@@ -58,9 +58,9 @@ static void rev_list_push(struct commit *commit, int mark)
        }
 }
 
-static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int rev_list_insert_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
-       struct object *o = deref_tag(parse_object(sha1), path, 0);
+       struct object *o = deref_tag(parse_object(sha1), refname, 0);
 
        if (o && o->type == OBJ_COMMIT)
                rev_list_push((struct commit *)o, SEEN);
@@ -68,9 +68,9 @@ static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int
        return 0;
 }
 
-static int clear_marks(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int clear_marks(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
-       struct object *o = deref_tag(parse_object(sha1), path, 0);
+       struct object *o = deref_tag(parse_object(sha1), refname, 0);
 
        if (o && o->type == OBJ_COMMIT)
                clear_commit_marks((struct commit *)o,
@@ -256,11 +256,6 @@ static void insert_one_alternate_ref(const struct ref *ref, void *unused)
        rev_list_insert_ref(NULL, ref->old_sha1, 0, NULL);
 }
 
-static void insert_alternate_refs(void)
-{
-       for_each_alternate_ref(insert_one_alternate_ref, NULL);
-}
-
 #define INITIAL_FLUSH 16
 #define PIPESAFE_FLUSH 32
 #define LARGE_FLUSH 1024
@@ -295,7 +290,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
        marked = 1;
 
        for_each_ref(rev_list_insert_ref, NULL);
-       insert_alternate_refs();
+       for_each_alternate_ref(insert_one_alternate_ref, NULL);
 
        fetching = 0;
        for ( ; refs ; refs = refs->next) {
@@ -493,7 +488,7 @@ done:
 
 static struct commit_list *complete;
 
-static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int mark_complete(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct object *o = parse_object(sha1);
 
@@ -586,6 +581,11 @@ static void filter_refs(struct ref **refs, int nr_match, char **match)
        *refs = newlist;
 }
 
+static void mark_alternate_complete(const struct ref *ref, void *unused)
+{
+       mark_complete(NULL, ref->old_sha1, 0, NULL);
+}
+
 static int everything_local(struct ref **refs, int nr_match, char **match)
 {
        struct ref *ref;
@@ -614,6 +614,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
 
        if (!args.depth) {
                for_each_ref(mark_complete, NULL);
+               for_each_alternate_ref(mark_alternate_complete, NULL);
                if (cutoff)
                        mark_recent_complete_commits(cutoff);
        }
@@ -736,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 5c2ae94e5576f2e8af1f8509b789a67851db2598..e4ea90078384f198f5985596c64deb5f423e9e45 100644 (file)
@@ -29,25 +29,12 @@ static int use_threads = 1;
 #define THREADS 8
 static pthread_t threads[THREADS];
 
-static void *load_sha1(const unsigned char *sha1, unsigned long *size,
-                      const char *name);
-static void *load_file(const char *filename, size_t *sz);
-
-enum work_type {WORK_SHA1, WORK_FILE};
-
 /* We use one producer thread and THREADS consumer
  * threads. The producer adds struct work_items to 'todo' and the
  * consumers pick work items from the same array.
  */
 struct work_item {
-       enum work_type type;
-       char *name;
-
-       /* if type == WORK_SHA1, then 'identifier' is a SHA1,
-        * otherwise type == WORK_FILE, and 'identifier' is a NUL
-        * terminated filename.
-        */
-       void *identifier;
+       struct grep_source source;
        char done;
        struct strbuf out;
 };
@@ -85,21 +72,6 @@ static inline void grep_unlock(void)
                pthread_mutex_unlock(&grep_mutex);
 }
 
-/* Used to serialize calls to read_sha1_file. */
-static pthread_mutex_t read_sha1_mutex;
-
-static inline void read_sha1_lock(void)
-{
-       if (use_threads)
-               pthread_mutex_lock(&read_sha1_mutex);
-}
-
-static inline void read_sha1_unlock(void)
-{
-       if (use_threads)
-               pthread_mutex_unlock(&read_sha1_mutex);
-}
-
 /* Signalled when a new work_item is added to todo. */
 static pthread_cond_t cond_add;
 
@@ -113,7 +85,8 @@ static pthread_cond_t cond_result;
 
 static int skip_first_line;
 
-static void add_work(enum work_type type, char *name, void *id)
+static void add_work(struct grep_opt *opt, enum grep_source_type type,
+                    const char *name, const void *id)
 {
        grep_lock();
 
@@ -121,9 +94,9 @@ static void add_work(enum work_type type, char *name, void *id)
                pthread_cond_wait(&cond_write, &grep_mutex);
        }
 
-       todo[todo_end].type = type;
-       todo[todo_end].name = name;
-       todo[todo_end].identifier = id;
+       grep_source_init(&todo[todo_end].source, type, name, id);
+       if (opt->binary != GREP_BINARY_TEXT)
+               grep_source_load_driver(&todo[todo_end].source);
        todo[todo_end].done = 0;
        strbuf_reset(&todo[todo_end].out);
        todo_end = (todo_end + 1) % ARRAY_SIZE(todo);
@@ -151,21 +124,6 @@ static struct work_item *get_work(void)
        return ret;
 }
 
-static void grep_sha1_async(struct grep_opt *opt, char *name,
-                           const unsigned char *sha1)
-{
-       unsigned char *s;
-       s = xmalloc(20);
-       memcpy(s, sha1, 20);
-       add_work(WORK_SHA1, name, s);
-}
-
-static void grep_file_async(struct grep_opt *opt, char *name,
-                           const char *filename)
-{
-       add_work(WORK_FILE, name, xstrdup(filename));
-}
-
 static void work_done(struct work_item *w)
 {
        int old_done;
@@ -192,8 +150,7 @@ static void work_done(struct work_item *w)
 
                        write_or_die(1, p, len);
                }
-               free(w->name);
-               free(w->identifier);
+               grep_source_clear(&w->source);
        }
 
        if (old_done != todo_done)
@@ -216,25 +173,8 @@ static void *run(void *arg)
                        break;
 
                opt->output_priv = w;
-               if (w->type == WORK_SHA1) {
-                       unsigned long sz;
-                       void* data = load_sha1(w->identifier, &sz, w->name);
-
-                       if (data) {
-                               hit |= grep_buffer(opt, w->name, data, sz);
-                               free(data);
-                       }
-               } else if (w->type == WORK_FILE) {
-                       size_t sz;
-                       void* data = load_file(w->identifier, &sz);
-                       if (data) {
-                               hit |= grep_buffer(opt, w->name, data, sz);
-                               free(data);
-                       }
-               } else {
-                       assert(0);
-               }
-
+               hit |= grep_source(opt, &w->source);
+               grep_source_clear_data(&w->source);
                work_done(w);
        }
        free_grep_patterns(arg);
@@ -254,11 +194,12 @@ static void start_threads(struct grep_opt *opt)
        int i;
 
        pthread_mutex_init(&grep_mutex, NULL);
-       pthread_mutex_init(&read_sha1_mutex, NULL);
+       pthread_mutex_init(&grep_read_mutex, NULL);
        pthread_mutex_init(&grep_attr_mutex, NULL);
        pthread_cond_init(&cond_add, NULL);
        pthread_cond_init(&cond_write, NULL);
        pthread_cond_init(&cond_result, NULL);
+       grep_use_locks = 1;
 
        for (i = 0; i < ARRAY_SIZE(todo); i++) {
                strbuf_init(&todo[i].out, 0);
@@ -302,17 +243,16 @@ static int wait_all(void)
        }
 
        pthread_mutex_destroy(&grep_mutex);
-       pthread_mutex_destroy(&read_sha1_mutex);
+       pthread_mutex_destroy(&grep_read_mutex);
        pthread_mutex_destroy(&grep_attr_mutex);
        pthread_cond_destroy(&cond_add);
        pthread_cond_destroy(&cond_write);
        pthread_cond_destroy(&cond_result);
+       grep_use_locks = 0;
 
        return hit;
 }
 #else /* !NO_PTHREADS */
-#define read_sha1_lock()
-#define read_sha1_unlock()
 
 static int wait_all(void)
 {
@@ -325,11 +265,8 @@ static int grep_config(const char *var, const char *value, void *cb)
        struct grep_opt *opt = cb;
        char *color = NULL;
 
-       switch (userdiff_config(var, value)) {
-       case 0: break;
-       case -1: return -1;
-       default: return 0;
-       }
+       if (userdiff_config(var, value) < 0)
+               return -1;
 
        if (!strcmp(var, "grep.extendedregexp")) {
                if (git_config_bool(var, value))
@@ -374,21 +311,9 @@ static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type
 {
        void *data;
 
-       read_sha1_lock();
+       grep_read_lock();
        data = read_sha1_file(sha1, type, size);
-       read_sha1_unlock();
-       return data;
-}
-
-static void *load_sha1(const unsigned char *sha1, unsigned long *size,
-                      const char *name)
-{
-       enum object_type type;
-       void *data = lock_and_read_sha1_file(sha1, &type, size);
-
-       if (!data)
-               error(_("'%s': unable to read %s"), name, sha1_to_hex(sha1));
-
+       grep_read_unlock();
        return data;
 }
 
@@ -396,7 +321,6 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
                     const char *filename, int tree_name_len)
 {
        struct strbuf pathbuf = STRBUF_INIT;
-       char *name;
 
        if (opt->relative && opt->prefix_length) {
                quote_path_relative(filename + tree_name_len, -1, &pathbuf,
@@ -406,87 +330,51 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
                strbuf_addstr(&pathbuf, filename);
        }
 
-       name = strbuf_detach(&pathbuf, NULL);
-
 #ifndef NO_PTHREADS
        if (use_threads) {
-               grep_sha1_async(opt, name, sha1);
+               add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, sha1);
+               strbuf_release(&pathbuf);
                return 0;
        } else
 #endif
        {
+               struct grep_source gs;
                int hit;
-               unsigned long sz;
-               void *data = load_sha1(sha1, &sz, name);
-               if (!data)
-                       hit = 0;
-               else
-                       hit = grep_buffer(opt, name, data, sz);
 
-               free(data);
-               free(name);
-               return hit;
-       }
-}
+               grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, sha1);
+               strbuf_release(&pathbuf);
+               hit = grep_source(opt, &gs);
 
-static void *load_file(const char *filename, size_t *sz)
-{
-       struct stat st;
-       char *data;
-       int i;
-
-       if (lstat(filename, &st) < 0) {
-       err_ret:
-               if (errno != ENOENT)
-                       error(_("'%s': %s"), filename, strerror(errno));
-               return NULL;
-       }
-       if (!S_ISREG(st.st_mode))
-               return NULL;
-       *sz = xsize_t(st.st_size);
-       i = open(filename, O_RDONLY);
-       if (i < 0)
-               goto err_ret;
-       data = xmalloc(*sz + 1);
-       if (st.st_size != read_in_full(i, data, *sz)) {
-               error(_("'%s': short read %s"), filename, strerror(errno));
-               close(i);
-               free(data);
-               return NULL;
+               grep_source_clear(&gs);
+               return hit;
        }
-       close(i);
-       data[*sz] = 0;
-       return data;
 }
 
 static int grep_file(struct grep_opt *opt, const char *filename)
 {
        struct strbuf buf = STRBUF_INIT;
-       char *name;
 
        if (opt->relative && opt->prefix_length)
                quote_path_relative(filename, -1, &buf, opt->prefix);
        else
                strbuf_addstr(&buf, filename);
-       name = strbuf_detach(&buf, NULL);
 
 #ifndef NO_PTHREADS
        if (use_threads) {
-               grep_file_async(opt, name, filename);
+               add_work(opt, GREP_SOURCE_FILE, buf.buf, filename);
+               strbuf_release(&buf);
                return 0;
        } else
 #endif
        {
+               struct grep_source gs;
                int hit;
-               size_t sz;
-               void *data = load_file(filename, &sz);
-               if (!data)
-                       hit = 0;
-               else
-                       hit = grep_buffer(opt, name, data, sz);
 
-               free(data);
-               free(name);
+               grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename);
+               strbuf_release(&buf);
+               hit = grep_source(opt, &gs);
+
+               grep_source_clear(&gs);
                return hit;
        }
 }
@@ -615,10 +503,10 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
                struct strbuf base;
                int hit, len;
 
-               read_sha1_lock();
+               grep_read_lock();
                data = read_object_with_reference(obj->sha1, tree_type,
                                                  &size, NULL);
-               read_sha1_unlock();
+               grep_read_unlock();
 
                if (!data)
                        die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1));
@@ -1030,8 +918,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        use_threads = 0;
 #endif
 
-       opt.use_threads = use_threads;
-
 #ifndef NO_PTHREADS
        if (use_threads) {
                if (!(opt.name_only || opt.unmatch_name_only || opt.count)
index 7e6db3bb35517b78f288202a635fcaa9a2353235..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);
@@ -1324,7 +1324,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                    merge_remote_util(commit) &&
                    merge_remote_util(commit)->obj &&
                    merge_remote_util(commit)->obj->type == OBJ_TAG) {
-                       option_edit = 1;
+                       if (option_edit < 0)
+                               option_edit = 1;
                        allow_fast_forward = 0;
                }
        }
index 0f2e7b8f5cb26910679c39550d59ef23353a278f..7b07c092cc5550d6784531c3137f05f54cfec258 100644 (file)
 #include "refs.h"
 #include "thread-utils.h"
 
-static const char pack_usage[] =
-  "git pack-objects [ -q | --progress | --all-progress ]\n"
-  "        [--all-progress-implied]\n"
-  "        [--max-pack-size=<n>] [--local] [--incremental]\n"
-  "        [--window=<n>] [--window-memory=<n>] [--depth=<n>]\n"
-  "        [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset]\n"
-  "        [--threads=<n>] [--non-empty] [--revs [--unpacked | --all]]\n"
-  "        [--reflog] [--stdout | base-name] [--include-tag]\n"
-  "        [--keep-unreachable | --unpack-unreachable]\n"
-  "        [< ref-list | < object-list]";
+static const char *pack_usage[] = {
+       "git pack-objects --stdout [options...] [< ref-list | < object-list]",
+       "git pack-objects [options...] base-name [< ref-list | < object-list]",
+       NULL
+};
 
 struct object_entry {
        struct pack_idx_entry idx;
@@ -2305,204 +2300,159 @@ static void get_object_list(int ac, const char **av)
                loosen_unused_packed_objects(&revs);
 }
 
+static int option_parse_index_version(const struct option *opt,
+                                     const char *arg, int unset)
+{
+       char *c;
+       const char *val = arg;
+       pack_idx_opts.version = strtoul(val, &c, 10);
+       if (pack_idx_opts.version > 2)
+               die(_("unsupported index version %s"), val);
+       if (*c == ',' && c[1])
+               pack_idx_opts.off32_limit = strtoul(c+1, &c, 0);
+       if (*c || pack_idx_opts.off32_limit & 0x80000000)
+               die(_("bad index version '%s'"), val);
+       return 0;
+}
+
+static int option_parse_ulong(const struct option *opt,
+                             const char *arg, int unset)
+{
+       if (unset)
+               die(_("option %s does not accept negative form"),
+                   opt->long_name);
+
+       if (!git_parse_ulong(arg, opt->value))
+               die(_("unable to parse value '%s' for option %s"),
+                   arg, opt->long_name);
+       return 0;
+}
+
+#define OPT_ULONG(s, l, v, h) \
+       { OPTION_CALLBACK, (s), (l), (v), "n", (h),     \
+         PARSE_OPT_NONEG, option_parse_ulong }
+
 int cmd_pack_objects(int argc, const char **argv, const char *prefix)
 {
        int use_internal_rev_list = 0;
        int thin = 0;
        int all_progress_implied = 0;
-       uint32_t i;
-       const char **rp_av;
-       int rp_ac_alloc = 64;
-       int rp_ac;
+       const char *rp_av[6];
+       int rp_ac = 0;
+       int rev_list_unpacked = 0, rev_list_all = 0, rev_list_reflog = 0;
+       struct option pack_objects_options[] = {
+               OPT_SET_INT('q', "quiet", &progress,
+                           "do not show progress meter", 0),
+               OPT_SET_INT(0, "progress", &progress,
+                           "show progress meter", 1),
+               OPT_SET_INT(0, "all-progress", &progress,
+                           "show progress meter during object writing phase", 2),
+               OPT_BOOL(0, "all-progress-implied",
+                        &all_progress_implied,
+                        "similar to --all-progress when progress meter is shown"),
+               { OPTION_CALLBACK, 0, "index-version", NULL, "version[,offset]",
+                 "write the pack index file in the specified idx format version",
+                 0, option_parse_index_version },
+               OPT_ULONG(0, "max-pack-size", &pack_size_limit,
+                         "maximum size of each output pack file"),
+               OPT_BOOL(0, "local", &local,
+                        "ignore borrowed objects from alternate object store"),
+               OPT_BOOL(0, "incremental", &incremental,
+                        "ignore packed objects"),
+               OPT_INTEGER(0, "window", &window,
+                           "limit pack window by objects"),
+               OPT_ULONG(0, "window-memory", &window_memory_limit,
+                         "limit pack window by memory in addition to object limit"),
+               OPT_INTEGER(0, "depth", &depth,
+                           "maximum length of delta chain allowed in the resulting pack"),
+               OPT_BOOL(0, "reuse-delta", &reuse_delta,
+                        "reuse existing deltas"),
+               OPT_BOOL(0, "reuse-object", &reuse_object,
+                        "reuse existing objects"),
+               OPT_BOOL(0, "delta-base-offset", &allow_ofs_delta,
+                        "use OFS_DELTA objects"),
+               OPT_INTEGER(0, "threads", &delta_search_threads,
+                           "use threads when searching for best delta matches"),
+               OPT_BOOL(0, "non-empty", &non_empty,
+                        "do not create an empty pack output"),
+               OPT_BOOL(0, "revs", &use_internal_rev_list,
+                        "read revision arguments from standard input"),
+               { OPTION_SET_INT, 0, "unpacked", &rev_list_unpacked, NULL,
+                 "limit the objects to those that are not yet packed",
+                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+               { OPTION_SET_INT, 0, "all", &rev_list_all, NULL,
+                 "include objects reachable from any reference",
+                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+               { OPTION_SET_INT, 0, "reflog", &rev_list_reflog, NULL,
+                 "include objects referred by reflog entries",
+                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+               OPT_BOOL(0, "stdout", &pack_to_stdout,
+                        "output pack to stdout"),
+               OPT_BOOL(0, "include-tag", &include_tag,
+                        "include tag objects that refer to objects to be packed"),
+               OPT_BOOL(0, "keep-unreachable", &keep_unreachable,
+                        "keep unreachable objects"),
+               OPT_BOOL(0, "unpack-unreachable", &unpack_unreachable,
+                        "unpack unreachable objects"),
+               OPT_BOOL(0, "thin", &thin,
+                        "create thin packs"),
+               OPT_BOOL(0, "honor-pack-keep", &ignore_packed_keep,
+                        "ignore packs that have companion .keep file"),
+               OPT_INTEGER(0, "compression", &pack_compression_level,
+                           "pack compression level"),
+               OPT_SET_INT(0, "keep-true-parents", &grafts_replace_parents,
+                           "do not hide commits by grafts", 0),
+               OPT_END(),
+       };
 
        read_replace_refs = 0;
 
-       rp_av = xcalloc(rp_ac_alloc, sizeof(*rp_av));
-
-       rp_av[0] = "pack-objects";
-       rp_av[1] = "--objects"; /* --thin will make it --objects-edge */
-       rp_ac = 2;
-
        reset_pack_idx_option(&pack_idx_opts);
        git_config(git_pack_config, NULL);
        if (!pack_compression_seen && core_compression_seen)
                pack_compression_level = core_compression_level;
 
        progress = isatty(2);
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (*arg != '-')
-                       break;
+       argc = parse_options(argc, argv, prefix, pack_objects_options,
+                            pack_usage, 0);
 
-               if (!strcmp("--non-empty", arg)) {
-                       non_empty = 1;
-                       continue;
-               }
-               if (!strcmp("--local", arg)) {
-                       local = 1;
-                       continue;
-               }
-               if (!strcmp("--incremental", arg)) {
-                       incremental = 1;
-                       continue;
-               }
-               if (!strcmp("--honor-pack-keep", arg)) {
-                       ignore_packed_keep = 1;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--compression=")) {
-                       char *end;
-                       int level = strtoul(arg+14, &end, 0);
-                       if (!arg[14] || *end)
-                               usage(pack_usage);
-                       if (level == -1)
-                               level = Z_DEFAULT_COMPRESSION;
-                       else if (level < 0 || level > Z_BEST_COMPRESSION)
-                               die("bad pack compression level %d", level);
-                       pack_compression_level = level;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--max-pack-size=")) {
-                       pack_size_limit_cfg = 0;
-                       if (!git_parse_ulong(arg+16, &pack_size_limit))
-                               usage(pack_usage);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--window=")) {
-                       char *end;
-                       window = strtoul(arg+9, &end, 0);
-                       if (!arg[9] || *end)
-                               usage(pack_usage);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--window-memory=")) {
-                       if (!git_parse_ulong(arg+16, &window_memory_limit))
-                               usage(pack_usage);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--threads=")) {
-                       char *end;
-                       delta_search_threads = strtoul(arg+10, &end, 0);
-                       if (!arg[10] || *end || delta_search_threads < 0)
-                               usage(pack_usage);
-#ifdef NO_PTHREADS
-                       if (delta_search_threads != 1)
-                               warning("no threads support, "
-                                       "ignoring %s", arg);
-#endif
-                       continue;
-               }
-               if (!prefixcmp(arg, "--depth=")) {
-                       char *end;
-                       depth = strtoul(arg+8, &end, 0);
-                       if (!arg[8] || *end)
-                               usage(pack_usage);
-                       continue;
-               }
-               if (!strcmp("--progress", arg)) {
-                       progress = 1;
-                       continue;
-               }
-               if (!strcmp("--all-progress", arg)) {
-                       progress = 2;
-                       continue;
-               }
-               if (!strcmp("--all-progress-implied", arg)) {
-                       all_progress_implied = 1;
-                       continue;
-               }
-               if (!strcmp("-q", arg)) {
-                       progress = 0;
-                       continue;
-               }
-               if (!strcmp("--no-reuse-delta", arg)) {
-                       reuse_delta = 0;
-                       continue;
-               }
-               if (!strcmp("--no-reuse-object", arg)) {
-                       reuse_object = reuse_delta = 0;
-                       continue;
-               }
-               if (!strcmp("--delta-base-offset", arg)) {
-                       allow_ofs_delta = 1;
-                       continue;
-               }
-               if (!strcmp("--stdout", arg)) {
-                       pack_to_stdout = 1;
-                       continue;
-               }
-               if (!strcmp("--revs", arg)) {
-                       use_internal_rev_list = 1;
-                       continue;
-               }
-               if (!strcmp("--keep-unreachable", arg)) {
-                       keep_unreachable = 1;
-                       continue;
-               }
-               if (!strcmp("--unpack-unreachable", arg)) {
-                       unpack_unreachable = 1;
-                       continue;
-               }
-               if (!strcmp("--include-tag", arg)) {
-                       include_tag = 1;
-                       continue;
-               }
-               if (!strcmp("--unpacked", arg) ||
-                   !strcmp("--reflog", arg) ||
-                   !strcmp("--all", arg)) {
-                       use_internal_rev_list = 1;
-                       if (rp_ac >= rp_ac_alloc - 1) {
-                               rp_ac_alloc = alloc_nr(rp_ac_alloc);
-                               rp_av = xrealloc(rp_av,
-                                                rp_ac_alloc * sizeof(*rp_av));
-                       }
-                       rp_av[rp_ac++] = arg;
-                       continue;
-               }
-               if (!strcmp("--thin", arg)) {
-                       use_internal_rev_list = 1;
-                       thin = 1;
-                       rp_av[1] = "--objects-edge";
-                       continue;
-               }
-               if (!prefixcmp(arg, "--index-version=")) {
-                       char *c;
-                       pack_idx_opts.version = strtoul(arg + 16, &c, 10);
-                       if (pack_idx_opts.version > 2)
-                               die("bad %s", arg);
-                       if (*c == ',')
-                               pack_idx_opts.off32_limit = strtoul(c+1, &c, 0);
-                       if (*c || pack_idx_opts.off32_limit & 0x80000000)
-                               die("bad %s", arg);
-                       continue;
-               }
-               if (!strcmp(arg, "--keep-true-parents")) {
-                       grafts_replace_parents = 0;
-                       continue;
-               }
-               usage(pack_usage);
-       }
-
-       /* Traditionally "pack-objects [options] base extra" failed;
-        * we would however want to take refs parameter that would
-        * have been given to upstream rev-list ourselves, which means
-        * we somehow want to say what the base name is.  So the
-        * syntax would be:
-        *
-        * pack-objects [options] base <refs...>
-        *
-        * in other words, we would treat the first non-option as the
-        * base_name and send everything else to the internal revision
-        * walker.
-        */
+       if (argc) {
+               base_name = argv[0];
+               argc--;
+       }
+       if (pack_to_stdout != !base_name || argc)
+               usage_with_options(pack_usage, pack_objects_options);
 
-       if (!pack_to_stdout)
-               base_name = argv[i++];
+       rp_av[rp_ac++] = "pack-objects";
+       if (thin) {
+               use_internal_rev_list = 1;
+               rp_av[rp_ac++] = "--objects-edge";
+       } else
+               rp_av[rp_ac++] = "--objects";
 
-       if (pack_to_stdout != !base_name)
-               usage(pack_usage);
+       if (rev_list_all) {
+               use_internal_rev_list = 1;
+               rp_av[rp_ac++] = "--all";
+       }
+       if (rev_list_reflog) {
+               use_internal_rev_list = 1;
+               rp_av[rp_ac++] = "--reflog";
+       }
+       if (rev_list_unpacked) {
+               use_internal_rev_list = 1;
+               rp_av[rp_ac++] = "--unpacked";
+       }
 
+       if (!reuse_object)
+               reuse_delta = 0;
+       if (pack_compression_level == -1)
+               pack_compression_level = Z_DEFAULT_COMPRESSION;
+       else if (pack_compression_level < 0 || pack_compression_level > Z_BEST_COMPRESSION)
+               die("bad pack compression level %d", pack_compression_level);
+#ifdef NO_PTHREADS
+       if (delta_search_threads != 1)
+               warning("no threads support, ignoring --threads");
+#endif
        if (!pack_to_stdout && !pack_size_limit)
                pack_size_limit = pack_size_limit_cfg;
        if (pack_to_stdout && pack_size_limit)
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 31f02e80f6b8fc22ed7ca7ae142c8d2f99084e8b..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)
@@ -83,18 +105,51 @@ static int contains(struct commit *candidate, const struct commit_list *want)
        return contains_recurse(candidate, want);
 }
 
+static void show_tag_lines(const unsigned char *sha1, int lines)
+{
+       int i;
+       unsigned long size;
+       enum object_type type;
+       char *buf, *sp, *eol;
+       size_t len;
+
+       buf = read_sha1_file(sha1, &type, &size);
+       if (!buf)
+               die_errno("unable to read object %s", sha1_to_hex(sha1));
+       if (type != OBJ_COMMIT && type != OBJ_TAG)
+               goto free_return;
+       if (!size)
+               die("an empty %s object %s?",
+                   typename(type), sha1_to_hex(sha1));
+
+       /* skip header */
+       sp = strstr(buf, "\n\n");
+       if (!sp)
+               goto free_return;
+
+       /* only take up to "lines" lines, and strip the signature from a tag */
+       if (type == OBJ_TAG)
+               size = parse_signature(buf, size);
+       for (i = 0, sp += 2; i < lines && sp < buf + size; i++) {
+               if (i)
+                       printf("\n    ");
+               eol = memchr(sp, '\n', size - (sp - buf));
+               len = eol ? eol - sp : size - (sp - buf);
+               fwrite(sp, len, 1, stdout);
+               if (!eol)
+                       break;
+               sp = eol + 1;
+       }
+free_return:
+       free(buf);
+}
+
 static int show_reference(const char *refname, const unsigned char *sha1,
                          int flag, void *cb_data)
 {
        struct tag_filter *filter = cb_data;
 
        if (match_pattern(filter->patterns, refname)) {
-               int i;
-               unsigned long size;
-               enum object_type type;
-               char *buf, *sp, *eol;
-               size_t len;
-
                if (filter->with_commit) {
                        struct commit *commit;
 
@@ -105,38 +160,16 @@ 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;
                }
                printf("%-15s ", refname);
-
-               buf = read_sha1_file(sha1, &type, &size);
-               if (!buf || !size)
-                       return 0;
-
-               /* skip header */
-               sp = strstr(buf, "\n\n");
-               if (!sp) {
-                       free(buf);
-                       return 0;
-               }
-               /* only take up to "lines" lines, and strip the signature */
-               size = parse_signature(buf, size);
-               for (i = 0, sp += 2;
-                               i < filter->lines && sp < buf + size;
-                               i++) {
-                       if (i)
-                               printf("\n    ");
-                       eol = memchr(sp, '\n', size - (sp - buf));
-                       len = eol ? eol - sp : size - (sp - buf);
-                       fwrite(sp, len, 1, stdout);
-                       if (!eol)
-                               break;
-                       sp = eol + 1;
-               }
+               show_tag_lines(sha1, filter->lines);
                putchar('\n');
-               free(buf);
        }
 
        return 0;
@@ -375,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;
@@ -417,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()
        };
 
@@ -448,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");
index 8de39590d57e14d08ee4d04b74965191aa905b29..28ed6574a2faf3151056aa20e681e06a157e4a2d 100644 (file)
@@ -150,15 +150,16 @@ void cache_tree_invalidate_path(struct cache_tree *it, const char *path)
 }
 
 static int verify_cache(struct cache_entry **cache,
-                       int entries, int silent)
+                       int entries, int flags)
 {
        int i, funny;
+       int silent = flags & WRITE_TREE_SILENT;
 
        /* Verify that the tree is merged */
        funny = 0;
        for (i = 0; i < entries; i++) {
                struct cache_entry *ce = cache[i];
-               if (ce_stage(ce) || (ce->ce_flags & CE_INTENT_TO_ADD)) {
+               if (ce_stage(ce)) {
                        if (silent)
                                return -1;
                        if (10 < ++funny) {
@@ -241,10 +242,11 @@ static int update_one(struct cache_tree *it,
                      int entries,
                      const char *base,
                      int baselen,
-                     int missing_ok,
-                     int dryrun)
+                     int flags)
 {
        struct strbuf buffer;
+       int missing_ok = flags & WRITE_TREE_MISSING_OK;
+       int dryrun = flags & WRITE_TREE_DRY_RUN;
        int i;
 
        if (0 <= it->entry_count && has_sha1_file(it->sha1))
@@ -288,8 +290,7 @@ static int update_one(struct cache_tree *it,
                                    cache + i, entries - i,
                                    path,
                                    baselen + sublen + 1,
-                                   missing_ok,
-                                   dryrun);
+                                   flags);
                if (subcnt < 0)
                        return subcnt;
                i += subcnt - 1;
@@ -338,8 +339,8 @@ static int update_one(struct cache_tree *it,
                                mode, sha1_to_hex(sha1), entlen+baselen, path);
                }
 
-               if (ce->ce_flags & CE_REMOVE)
-                       continue; /* entry being removed */
+               if (ce->ce_flags & (CE_REMOVE | CE_INTENT_TO_ADD))
+                       continue; /* entry being removed or placeholder */
 
                strbuf_grow(&buffer, entlen + 100);
                strbuf_addf(&buffer, "%o %.*s%c", mode, entlen, path + baselen, '\0');
@@ -371,15 +372,13 @@ static int update_one(struct cache_tree *it,
 int cache_tree_update(struct cache_tree *it,
                      struct cache_entry **cache,
                      int entries,
-                     int missing_ok,
-                     int dryrun,
-                     int silent)
+                     int flags)
 {
        int i;
-       i = verify_cache(cache, entries, silent);
+       i = verify_cache(cache, entries, flags);
        if (i)
                return i;
-       i = update_one(it, cache, entries, "", 0, missing_ok, dryrun);
+       i = update_one(it, cache, entries, "", 0, flags);
        if (i < 0)
                return i;
        return 0;
@@ -572,11 +571,9 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)
 
        was_valid = cache_tree_fully_valid(active_cache_tree);
        if (!was_valid) {
-               int missing_ok = flags & WRITE_TREE_MISSING_OK;
-
                if (cache_tree_update(active_cache_tree,
                                      active_cache, active_nr,
-                                     missing_ok, 0, 0) < 0)
+                                     flags) < 0)
                        return WRITE_TREE_UNMERGED_INDEX;
                if (0 <= newfd) {
                        if (!write_cache(newfd, active_cache, active_nr) &&
@@ -672,10 +669,10 @@ int cache_tree_matches_traversal(struct cache_tree *root,
        return 0;
 }
 
-int update_main_cache_tree (int silent)
+int update_main_cache_tree(int flags)
 {
        if (!the_index.cache_tree)
                the_index.cache_tree = cache_tree();
        return cache_tree_update(the_index.cache_tree,
-                                the_index.cache, the_index.cache_nr, 0, 0, silent);
+                                the_index.cache, the_index.cache_nr, flags);
 }
index 0ec0b2a159dfd352ca621322a9ce3715328ab2d0..d8cb2e9e39d2be9c4dad67db44a464c2a1004d2f 100644 (file)
@@ -29,13 +29,15 @@ void cache_tree_write(struct strbuf *, struct cache_tree *root);
 struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);
 
 int cache_tree_fully_valid(struct cache_tree *);
-int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int, int);
+int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int);
 
 int update_main_cache_tree(int);
 
 /* bitmasks to write_cache_as_tree flags */
 #define WRITE_TREE_MISSING_OK 1
 #define WRITE_TREE_IGNORE_CACHE_TREE 2
+#define WRITE_TREE_DRY_RUN 4
+#define WRITE_TREE_SILENT 8
 
 /* error return codes */
 #define WRITE_TREE_UNREADABLE_INDEX (-1)
diff --git a/cache.h b/cache.h
index 9bd8c2d06c80c958b5f654fe16e7294883e49a30..e12b15f4b92da906c50f7f51df95b126347e9441 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -432,6 +432,7 @@ extern char *git_work_tree_cfg;
 extern int is_inside_work_tree(void);
 extern int have_git_dir(void);
 extern const char *get_git_dir(void);
+extern int is_git_directory(const char *path);
 extern char *get_object_directory(void);
 extern char *get_index_file(void);
 extern char *get_graft_file(void);
@@ -1114,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 *);
@@ -1129,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);
@@ -1139,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];
@@ -1176,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 60b5a1d0f8262baca70247923a399fdb4ee3cac1..90b7cc45f33411bbc39cd731ca3e43288cb40b69 100644 (file)
  * SOFTWARE.
  */
 
-#include <errno.h>
-#include <sys/types.h>
-
 #include "../git-compat-util.h"
 
-#include <stdio.h>
-#include <string.h>
-
 #ifndef NS_INADDRSZ
 #define NS_INADDRSZ    4
 #endif
index 2ec995e63d0cdef6dcd8b2fd6ea7aa544f4d1b33..2b9a0a4e22fa5256575b6b184a16fc93ee47869e 100644 (file)
  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <errno.h>
-#include <sys/types.h>
-
 #include "../git-compat-util.h"
 
-#include <stdio.h>
-#include <string.h>
-
 #ifndef NS_INT16SZ
 #define NS_INT16SZ       2
 #endif
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 @@ write_err_out:
 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 @@ unlock_and_out:
        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 10698c8292e639fc5adc4492a4bcac0c82c403bc..b2ba7104ebc87c0f2fe261f16461793fcc8b8cd3 100644 (file)
@@ -74,3 +74,4 @@ SNPRINTF_RETURNS_BOGUS=@SNPRINTF_RETURNS_BOGUS@
 NO_PTHREADS=@NO_PTHREADS@
 PTHREAD_CFLAGS=@PTHREAD_CFLAGS@
 PTHREAD_LIBS=@PTHREAD_LIBS@
+CHARSET_LIB=@CHARSET_LIB@
index 630dbdd19d74fc6ffab529d8d5854043a8f18fd2..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,18 +835,21 @@ 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],
 [HAVE_LIBCHARSET_H=])
 AC_SUBST(HAVE_LIBCHARSET_H)
+# Define CHARSET_LIB if libiconv does not export the locale_charset symbol
+# and libcharset does
+CHARSET_LIB=
+AC_CHECK_LIB([iconv], [locale_charset],
+       [],
+       [AC_CHECK_LIB([charset], [locale_charset],
+                     [CHARSET_LIB=-lcharset])
+       ]
+)
+AC_SUBST(CHARSET_LIB)
 #
 # Define NO_STRCASESTR if you don't have strcasestr.
 GIT_CHECK_FUNC(strcasestr,
index a2965f7c6da087acc60b901befe2529a44446647..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
 }
 
@@ -495,11 +483,8 @@ fi
 # 4: A suffix to be appended to each possible completion word (optional).
 __gitcomp ()
 {
-       local cur_="$cur"
+       local cur_="${3-$cur}"
 
-       if [ $# -gt 2 ]; then
-               cur_="$3"
-       fi
        case "$cur_" in
        --*=)
                COMPREPLY=()
@@ -524,18 +509,8 @@ __gitcomp ()
 #    appended.
 __gitcomp_nl ()
 {
-       local s=$'\n' IFS=' '$'\t'$'\n'
-       local cur_="$cur" suffix=" "
-
-       if [ $# -gt 2 ]; then
-               cur_="$3"
-               if [ $# -gt 3 ]; then
-                       suffix="$4"
-               fi
-       fi
-
-       IFS=$s
-       COMPREPLY=($(compgen -P "${2-}" -S "$suffix" -W "$1" -- "$cur_"))
+       local IFS=$'\n'
+       COMPREPLY=($(compgen -P "${2-}" -S "${4- }" -W "$1" -- "${3-$cur}"))
 }
 
 __git_heads ()
@@ -643,13 +618,8 @@ __git_refs_remotes ()
 
 __git_remotes ()
 {
-       local i ngoff IFS=$'\n' d="$(__gitdir)"
-       __git_shopt -q nullglob || ngoff=1
-       __git_shopt -s nullglob
-       for i in "$d/remotes"/*; do
-               echo ${i#$d/remotes/}
-       done
-       [ "$ngoff" ] && __git_shopt -u nullglob
+       local i IFS=$'\n' d="$(__gitdir)"
+       test -d "$d/remotes" && ls -1 "$d/remotes"
        for i in $(git --git-dir="$d" config --get-regexp 'remote\..*\.url' 2>/dev/null); do
                i="${i#remote.}"
                echo "${i/.url*/}"
@@ -676,7 +646,8 @@ __git_merge_strategies=
 # is needed.
 __git_compute_merge_strategies ()
 {
-       : ${__git_merge_strategies:=$(__git_list_merge_strategies)}
+       test -n "$__git_merge_strategies" ||
+       __git_merge_strategies=$(__git_list_merge_strategies)
 }
 
 __git_complete_revlist_file ()
@@ -854,7 +825,8 @@ __git_list_all_commands ()
 __git_all_commands=
 __git_compute_all_commands ()
 {
-       : ${__git_all_commands:=$(__git_list_all_commands)}
+       test -n "$__git_all_commands" ||
+       __git_all_commands=$(__git_list_all_commands)
 }
 
 __git_list_porcelain_commands ()
@@ -947,7 +919,8 @@ __git_porcelain_commands=
 __git_compute_porcelain_commands ()
 {
        __git_compute_all_commands
-       : ${__git_porcelain_commands:=$(__git_list_porcelain_commands)}
+       test -n "$__git_porcelain_commands" ||
+       __git_porcelain_commands=$(__git_list_porcelain_commands)
 }
 
 __git_pretty_aliases ()
@@ -1152,7 +1125,7 @@ _git_branch ()
                __gitcomp "
                        --color --no-color --verbose --abbrev= --no-abbrev
                        --track --no-track --contains --merged --no-merged
-                       --set-upstream --edit-description
+                       --set-upstream --edit-description --list
                        "
                ;;
        *)
@@ -2527,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,--*)
@@ -2733,33 +2706,3 @@ if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
 complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \
        || complete -o default -o nospace -F _git git.exe
 fi
-
-if [[ -n ${ZSH_VERSION-} ]]; then
-       __git_shopt () {
-               local option
-               if [ $# -ne 2 ]; then
-                       echo "USAGE: $0 (-q|-s|-u) <option>" >&2
-                       return 1
-               fi
-               case "$2" in
-               nullglob)
-                       option="$2"
-                       ;;
-               *)
-                       echo "$0: invalid option: $2" >&2
-                       return 1
-               esac
-               case "$1" in
-               -q)     setopt | grep -q "$option" ;;
-               -u)     unsetopt "$option" ;;
-               -s)     setopt "$option" ;;
-               *)
-                       echo "$0: invalid flag: $1" >&2
-                       return 1
-               esac
-       }
-else
-       __git_shopt () {
-               shopt "$@"
-       }
-fi
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 ba077c13f96779d5b29237e83974b0910b4bbdde..01af9df15e180288723caadf42b500fe78a8c694 100755 (executable)
@@ -85,7 +85,6 @@ prep_for_email()
        oldrev=$(git rev-parse $1)
        newrev=$(git rev-parse $2)
        refname="$3"
-       maxlines=$4
 
        # --- Interpret
        # 0000->1234 (create)
@@ -461,7 +460,7 @@ generate_delete_branch_email()
 {
        echo "       was  $oldrev"
        echo ""
-       echo $LOGEND
+       echo $LOGBEGIN
        git show -s --pretty=oneline $oldrev
        echo $LOGEND
 }
@@ -561,7 +560,7 @@ generate_delete_atag_email()
 {
        echo "       was  $oldrev"
        echo ""
-       echo $LOGEND
+       echo $LOGBEGIN
        git show -s --pretty=oneline $oldrev
        echo $LOGEND
 }
@@ -626,7 +625,7 @@ generate_delete_general_email()
 {
        echo "       was  $oldrev"
        echo ""
-       echo $LOGEND
+       echo $LOGBEGIN
        git show -s --pretty=oneline $oldrev
        echo $LOGEND
 }
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/ctype.c b/ctype.c
index b5d856fd26bd892a5f18202b054fc53e7c953429..af722f957fa5a1899dc906196d9b0372acab946d 100644 (file)
--- a/ctype.c
+++ b/ctype.c
@@ -3,7 +3,7 @@
  *
  * No surprises, and works with signed and unsigned chars.
  */
-#include "cache.h"
+#include "git-compat-util.h"
 
 enum {
        S = GIT_SPACE,
diff --git a/diff.c b/diff.c
index 7e154265f778c645192cbf17c65b9bea2a507402..a1c06b554b80e433dcb735c8e0adf186b6252ae5 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -177,11 +177,8 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
-       switch (userdiff_config(var, value)) {
-               case 0: break;
-               case -1: return -1;
-               default: return 0;
-       }
+       if (userdiff_config(var, value) < 0)
+               return -1;
 
        if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
@@ -1276,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,
@@ -1322,6 +1321,55 @@ static void fill_print_name(struct diffstat_file *file)
        file->print_name = pname;
 }
 
+int print_stat_summary(FILE *fp, int files, int insertions, int deletions)
+{
+       struct strbuf sb = STRBUF_INIT;
+       int ret;
+
+       if (!files) {
+               assert(insertions == 0 && deletions == 0);
+               return fputs(_(" 0 files changed\n"), fp);
+       }
+
+       strbuf_addf(&sb,
+                   Q_(" %d file changed", " %d files changed", files),
+                   files);
+
+       /*
+        * For binary diff, the caller may want to print "x files
+        * changed" with insertions == 0 && deletions == 0.
+        *
+        * Not omitting "0 insertions(+), 0 deletions(-)" in this case
+        * is probably less confusing (i.e skip over "2 files changed
+        * but nothing about added/removed lines? Is this a bug in Git?").
+        */
+       if (insertions || deletions == 0) {
+               /*
+                * TRANSLATORS: "+" in (+) is a line addition marker;
+                * do not translate it.
+                */
+               strbuf_addf(&sb,
+                           Q_(", %d insertion(+)", ", %d insertions(+)",
+                              insertions),
+                           insertions);
+       }
+
+       if (deletions || insertions == 0) {
+               /*
+                * TRANSLATORS: "-" in (-) is a line removal marker;
+                * do not translate it.
+                */
+               strbuf_addf(&sb,
+                           Q_(", %d deletion(-)", ", %d deletions(-)",
+                              deletions),
+                           deletions);
+       }
+       strbuf_addch(&sb, '\n');
+       ret = fputs(sb.buf, fp);
+       strbuf_release(&sb);
+       return ret;
+}
+
 static void show_stats(struct diffstat_t *data, struct diff_options *options)
 {
        int i, len, add, del, adds = 0, dels = 0;
@@ -1449,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);
@@ -1475,9 +1534,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                extra_shown = 1;
        }
        fprintf(options->file, "%s", line_prefix);
-       fprintf(options->file,
-              " %d files changed, %d insertions(+), %d deletions(-)\n",
-              total_files, adds, dels);
+       print_stat_summary(options->file, total_files, adds, dels);
 }
 
 static void show_shortstats(struct diffstat_t *data, struct diff_options *options)
@@ -1507,8 +1564,7 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option
                                options->output_prefix_data);
                fprintf(options->file, "%s", msg->buf);
        }
-       fprintf(options->file, " %d files changed, %d insertions(+), %d deletions(-)\n",
-              total_files, adds, dels);
+       print_stat_summary(options->file, total_files, adds, dels);
 }
 
 static void show_numstat(struct diffstat_t *data, struct diff_options *options)
diff --git a/diff.h b/diff.h
index ae71f4ccf94369b31b1a77373861b02d478d705a..7af5f1e2a7af3fde64ac945f8b2a9ed88829895a 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -324,4 +324,7 @@ extern struct userdiff_driver *get_textconv(struct diff_filespec *one);
 
 extern int parse_rename_score(const char **cp_p);
 
+extern int print_stat_summary(FILE *fp, int files,
+                             int insertions, int deletions);
+
 #endif /* DIFF_H */
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 8f3972cd3295665b8b1f69b5db7aff67c8c61613..426ae43be9cd7aa4230888ff0aa28ed2fd165ce0 100644 (file)
@@ -463,6 +463,8 @@ static inline int has_extension(const char *filename, const char *ext)
 #undef isdigit
 #undef isalpha
 #undef isalnum
+#undef islower
+#undef isupper
 #undef tolower
 #undef toupper
 extern unsigned char sane_ctype[256];
@@ -478,6 +480,8 @@ extern unsigned char sane_ctype[256];
 #define isdigit(x) sane_istest(x,GIT_DIGIT)
 #define isalpha(x) sane_istest(x,GIT_ALPHA)
 #define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT)
+#define islower(x) sane_iscase(x, 1)
+#define isupper(x) sane_iscase(x, 0)
 #define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL)
 #define is_regex_special(x) sane_istest(x,GIT_GLOB_SPECIAL | GIT_REGEX_SPECIAL)
 #define tolower(x) sane_case((unsigned char)(x), 0x20)
@@ -491,6 +495,17 @@ static inline int sane_case(int x, int high)
        return x;
 }
 
+static inline int sane_iscase(int x, int is_lower)
+{
+       if (!sane_istest(x, GIT_ALPHA))
+               return 0;
+
+       if (is_lower)
+               return (x & 0x20) != 0;
+       else
+               return (x & 0x20) == 0;
+}
+
 static inline int strtoul_ui(char const *s, int base, unsigned int *result)
 {
        unsigned long ul;
index d8b64d7a67a19f1821a26c3ec82c0953db717be6..434c139f077ed5b03fef2b70f2eabb66f7147f23 100755 (executable)
@@ -40,7 +40,7 @@ test -f "$GIT_DIR/MERGE_HEAD" && die_merge
 
 strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
 log_arg= verbosity= progress= recurse_submodules=
-merge_args=
+merge_args= edit=
 curr_branch=$(git symbolic-ref -q HEAD)
 curr_branch_short="${curr_branch#refs/heads/}"
 rebase=$(git config --bool branch.$curr_branch_short.rebase)
@@ -70,6 +70,10 @@ do
                no_commit=--no-commit ;;
        --c|--co|--com|--comm|--commi|--commit)
                no_commit=--commit ;;
+       -e|--edit)
+               edit=--edit ;;
+       --no-edit)
+               edit=--no-edit ;;
        --sq|--squ|--squa|--squas|--squash)
                squash=--squash ;;
        --no-sq|--no-squ|--no-squa|--no-squas|--no-squash)
@@ -278,7 +282,7 @@ true)
        eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
        ;;
 *)
-       eval="git-merge $diffstat $no_commit $squash $no_ff $ff_only"
+       eval="git-merge $diffstat $no_commit $edit $squash $no_ff $ff_only"
        eval="$eval  $log_arg $strategy_args $merge_args $verbosity $progress"
        eval="$eval \"\$merge_name\" HEAD $merge_head"
        ;;
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 @@ use Carp qw/croak/;
 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 9ee58114d946a4fb751590b7bd74d15d1894aef2..eaf5f942502dd6593fa44724ea18d0153e725aea 100755 (executable)
@@ -52,7 +52,7 @@ sub evaluate_uri {
        # as base URL.
        # Therefore, if we needed to strip PATH_INFO, then we know that we have
        # to build the base URL ourselves:
-       our $path_info = $ENV{"PATH_INFO"};
+       our $path_info = decode_utf8($ENV{"PATH_INFO"});
        if ($path_info) {
                if ($my_url =~ s,\Q$path_info\E$,, &&
                    $my_uri =~ s,\Q$path_info\E$,, &&
@@ -817,9 +817,9 @@ sub evaluate_query_params {
 
        while (my ($name, $symbol) = each %cgi_param_mapping) {
                if ($symbol eq 'opt') {
-                       $input_params{$name} = [ $cgi->param($symbol) ];
+                       $input_params{$name} = [ map { decode_utf8($_) } $cgi->param($symbol) ];
                } else {
-                       $input_params{$name} = $cgi->param($symbol);
+                       $input_params{$name} = decode_utf8($cgi->param($symbol));
                }
        }
 }
@@ -2775,7 +2775,7 @@ sub git_populate_project_tagcloud {
        }
 
        my $cloud;
-       my $matched = $cgi->param('by_tag');
+       my $matched = $input_params{'ctag'};
        if (eval { require HTML::TagCloud; 1; }) {
                $cloud = HTML::TagCloud->new;
                foreach my $ctag (sort keys %ctags_lc) {
@@ -2987,6 +2987,10 @@ sub search_projects_list {
        return @$projlist
                unless ($tagfilter || $searchtext);
 
+       # searching projects require filling to be run before it;
+       fill_project_list_info($projlist,
+                              $tagfilter  ? 'ctags' : (),
+                              $searchtext ? ('path', 'descr') : ());
        my @projects;
  PROJECT:
        foreach my $pr (@$projlist) {
@@ -3744,7 +3748,7 @@ sub get_page_title {
 
        unless (defined $project) {
                if (defined $project_filter) {
-                       $title .= " - " . to_utf8($project_filter);
+                       $title .= " - projects in '" . esc_path($project_filter) . "'";
                }
                return $title;
        }
@@ -3906,7 +3910,7 @@ sub print_search_form {
                               -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
              $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
              " search:\n",
-             $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
+             $cgi->textfield(-name => "s", -value => $searchtext, -override => 1) . "\n" .
              "<span title=\"Extended regular expression\">" .
              $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
                             -checked => $search_use_regexp) .
@@ -5188,35 +5192,70 @@ sub git_project_search_form {
        print "</div>\n";
 }
 
-# fills project list info (age, description, owner, category, forks)
+# entry for given @keys needs filling if at least one of keys in list
+# is not present in %$project_info
+sub project_info_needs_filling {
+       my ($project_info, @keys) = @_;
+
+       # return List::MoreUtils::any { !exists $project_info->{$_} } @keys;
+       foreach my $key (@keys) {
+               if (!exists $project_info->{$key}) {
+                       return 1;
+               }
+       }
+       return;
+}
+
+# fills project list info (age, description, owner, category, forks, etc.)
 # for each project in the list, removing invalid projects from
-# returned list
+# returned list, or fill only specified info.
+#
+# Invalid projects are removed from the returned list if and only if you
+# ask 'age' or 'age_string' to be filled, because they are the only fields
+# that run unconditionally git command that requires repository, and
+# therefore do always check if project repository is invalid.
+#
+# USAGE:
+# * fill_project_list_info(\@project_list, 'descr_long', 'ctags')
+#   ensures that 'descr_long' and 'ctags' fields are filled
+# * @project_list = fill_project_list_info(\@project_list)
+#   ensures that all fields are filled (and invalid projects removed)
+#
 # NOTE: modifies $projlist, but does not remove entries from it
 sub fill_project_list_info {
-       my $projlist = shift;
+       my ($projlist, @wanted_keys) = @_;
        my @projects;
+       my $filter_set = sub { return @_; };
+       if (@wanted_keys) {
+               my %wanted_keys = map { $_ => 1 } @wanted_keys;
+               $filter_set = sub { return grep { $wanted_keys{$_} } @_; };
+       }
 
        my $show_ctags = gitweb_check_feature('ctags');
  PROJECT:
        foreach my $pr (@$projlist) {
-               my (@activity) = git_get_last_activity($pr->{'path'});
-               unless (@activity) {
-                       next PROJECT;
+               if (project_info_needs_filling($pr, $filter_set->('age', 'age_string'))) {
+                       my (@activity) = git_get_last_activity($pr->{'path'});
+                       unless (@activity) {
+                               next PROJECT;
+                       }
+                       ($pr->{'age'}, $pr->{'age_string'}) = @activity;
                }
-               ($pr->{'age'}, $pr->{'age_string'}) = @activity;
-               if (!defined $pr->{'descr'}) {
+               if (project_info_needs_filling($pr, $filter_set->('descr', 'descr_long'))) {
                        my $descr = git_get_project_description($pr->{'path'}) || "";
                        $descr = to_utf8($descr);
                        $pr->{'descr_long'} = $descr;
                        $pr->{'descr'} = chop_str($descr, $projects_list_description_width, 5);
                }
-               if (!defined $pr->{'owner'}) {
+               if (project_info_needs_filling($pr, $filter_set->('owner'))) {
                        $pr->{'owner'} = git_get_project_owner("$pr->{'path'}") || "";
                }
-               if ($show_ctags) {
+               if ($show_ctags &&
+                   project_info_needs_filling($pr, $filter_set->('ctags'))) {
                        $pr->{'ctags'} = git_get_project_ctags($pr->{'path'});
                }
-               if ($projects_list_group_categories && !defined $pr->{'category'}) {
+               if ($projects_list_group_categories &&
+                   project_info_needs_filling($pr, $filter_set->('category'))) {
                        my $cat = git_get_project_category($pr->{'path'}) ||
                                                           $project_list_default_category;
                        $pr->{'category'} = to_utf8($cat);
@@ -5345,19 +5384,20 @@ sub git_project_list_body {
 
        my $check_forks = gitweb_check_feature('forks');
        my $show_ctags  = gitweb_check_feature('ctags');
-       my $tagfilter = $show_ctags ? $cgi->param('by_tag') : undef;
+       my $tagfilter = $show_ctags ? $input_params{'ctag'} : undef;
        $check_forks = undef
                if ($tagfilter || $searchtext);
 
        # filtering out forks before filling info allows to do less work
        @projects = filter_forks_from_projects_list(\@projects)
                if ($check_forks);
-       @projects = fill_project_list_info(\@projects);
-       # searching projects require filling to be run before it
+       # search_projects_list pre-fills required info
        @projects = search_projects_list(\@projects,
                                         'searchtext' => $searchtext,
                                         'tagfilter'  => $tagfilter)
                if ($tagfilter || $searchtext);
+       # fill the rest
+       @projects = fill_project_list_info(\@projects);
 
        $order ||= $default_projects_order;
        $from = 0 unless defined $from;
@@ -5633,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);
 
@@ -5642,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 {
@@ -5915,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;
@@ -6261,7 +6302,7 @@ sub git_tag {
 
 sub git_blame_common {
        my $format = shift || 'porcelain';
-       if ($format eq 'porcelain' && $cgi->param('js')) {
+       if ($format eq 'porcelain' && $input_params{'javascript'}) {
                $format = 'incremental';
                $action = 'blame_incremental'; # for page title etc
        }
diff --git a/grep.c b/grep.c
index 486230b511952dcf975199c82a5265d1906999cd..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;
@@ -807,38 +807,43 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
 }
 
 #ifndef NO_PTHREADS
+int grep_use_locks;
+
 /*
  * This lock protects access to the gitattributes machinery, which is
  * not thread-safe.
  */
 pthread_mutex_t grep_attr_mutex;
 
-static inline void grep_attr_lock(struct grep_opt *opt)
+static inline void grep_attr_lock(void)
 {
-       if (opt->use_threads)
+       if (grep_use_locks)
                pthread_mutex_lock(&grep_attr_mutex);
 }
 
-static inline void grep_attr_unlock(struct grep_opt *opt)
+static inline void grep_attr_unlock(void)
 {
-       if (opt->use_threads)
+       if (grep_use_locks)
                pthread_mutex_unlock(&grep_attr_mutex);
 }
+
+/*
+ * Same as git_attr_mutex, but protecting the thread-unsafe object db access.
+ */
+pthread_mutex_t grep_read_mutex;
+
 #else
-#define grep_attr_lock(opt)
-#define grep_attr_unlock(opt)
+#define grep_attr_lock()
+#define grep_attr_unlock()
 #endif
 
-static int match_funcname(struct grep_opt *opt, const char *name, char *bol, char *eol)
+static int match_funcname(struct grep_opt *opt, struct grep_source *gs, char *bol, char *eol)
 {
        xdemitconf_t *xecfg = opt->priv;
        if (xecfg && !xecfg->find_func) {
-               struct userdiff_driver *drv;
-               grep_attr_lock(opt);
-               drv = userdiff_find_by_path(name);
-               grep_attr_unlock(opt);
-               if (drv && drv->funcname.pattern) {
-                       const struct userdiff_funcname *pe = &drv->funcname;
+               grep_source_load_driver(gs);
+               if (gs->driver->funcname.pattern) {
+                       const struct userdiff_funcname *pe = &gs->driver->funcname;
                        xdiff_set_find_func(xecfg, pe->pattern, pe->cflags);
                } else {
                        xecfg = opt->priv = NULL;
@@ -858,33 +863,33 @@ static int match_funcname(struct grep_opt *opt, const char *name, char *bol, cha
        return 0;
 }
 
-static void show_funcname_line(struct grep_opt *opt, const char *name,
-                              char *buf, char *bol, unsigned lno)
+static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs,
+                              char *bol, unsigned lno)
 {
-       while (bol > buf) {
+       while (bol > gs->buf) {
                char *eol = --bol;
 
-               while (bol > buf && bol[-1] != '\n')
+               while (bol > gs->buf && bol[-1] != '\n')
                        bol--;
                lno--;
 
                if (lno <= opt->last_shown)
                        break;
 
-               if (match_funcname(opt, name, bol, eol)) {
-                       show_line(opt, bol, eol, name, lno, '=');
+               if (match_funcname(opt, gs, bol, eol)) {
+                       show_line(opt, bol, eol, gs->name, lno, '=');
                        break;
                }
        }
 }
 
-static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
+static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
                             char *bol, char *end, unsigned lno)
 {
        unsigned cur = lno, from = 1, funcname_lno = 0;
        int funcname_needed = !!opt->funcname;
 
-       if (opt->funcbody && !match_funcname(opt, name, bol, end))
+       if (opt->funcbody && !match_funcname(opt, gs, bol, end))
                funcname_needed = 2;
 
        if (opt->pre_context < lno)
@@ -893,14 +898,14 @@ static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
                from = opt->last_shown + 1;
 
        /* Rewind. */
-       while (bol > buf &&
+       while (bol > gs->buf &&
               cur > (funcname_needed == 2 ? opt->last_shown + 1 : from)) {
                char *eol = --bol;
 
-               while (bol > buf && bol[-1] != '\n')
+               while (bol > gs->buf && bol[-1] != '\n')
                        bol--;
                cur--;
-               if (funcname_needed && match_funcname(opt, name, bol, eol)) {
+               if (funcname_needed && match_funcname(opt, gs, bol, eol)) {
                        funcname_lno = cur;
                        funcname_needed = 0;
                }
@@ -908,7 +913,7 @@ static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
 
        /* We need to look even further back to find a function signature. */
        if (opt->funcname && funcname_needed)
-               show_funcname_line(opt, name, buf, bol, cur);
+               show_funcname_line(opt, gs, bol, cur);
 
        /* Back forward. */
        while (cur < lno) {
@@ -916,7 +921,7 @@ static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
 
                while (*eol != '\n')
                        eol++;
-               show_line(opt, bol, eol, name, cur, sign);
+               show_line(opt, bol, eol, gs->name, cur, sign);
                bol = eol + 1;
                cur++;
        }
@@ -983,11 +988,10 @@ static void std_output(struct grep_opt *opt, const void *buf, size_t size)
        fwrite(buf, size, 1, stdout);
 }
 
-static int grep_buffer_1(struct grep_opt *opt, const char *name,
-                        char *buf, unsigned long size, int collect_hits)
+static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int collect_hits)
 {
-       char *bol = buf;
-       unsigned long left = size;
+       char *bol;
+       unsigned long left;
        unsigned lno = 1;
        unsigned last_hit = 0;
        int binary_match_only = 0;
@@ -1017,11 +1021,11 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
 
        switch (opt->binary) {
        case GREP_BINARY_DEFAULT:
-               if (buffer_is_binary(buf, size))
+               if (grep_source_is_binary(gs))
                        binary_match_only = 1;
                break;
        case GREP_BINARY_NOMATCH:
-               if (buffer_is_binary(buf, size))
+               if (grep_source_is_binary(gs))
                        return 0; /* Assume unmatch */
                break;
        case GREP_BINARY_TEXT:
@@ -1035,6 +1039,11 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
 
        try_lookahead = should_lookahead(opt);
 
+       if (grep_source_load(gs) < 0)
+               return 0;
+
+       bol = gs->buf;
+       left = gs->size;
        while (left) {
                char *eol, ch;
                int hit;
@@ -1083,14 +1092,14 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                        if (opt->status_only)
                                return 1;
                        if (opt->name_only) {
-                               show_name(opt, name);
+                               show_name(opt, gs->name);
                                return 1;
                        }
                        if (opt->count)
                                goto next_line;
                        if (binary_match_only) {
                                opt->output(opt, "Binary file ", 12);
-                               output_color(opt, name, strlen(name),
+                               output_color(opt, gs->name, strlen(gs->name),
                                             opt->color_filename);
                                opt->output(opt, " matches\n", 9);
                                return 1;
@@ -1099,23 +1108,23 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                         * pre-context lines, we would need to show them.
                         */
                        if (opt->pre_context || opt->funcbody)
-                               show_pre_context(opt, name, buf, bol, eol, lno);
+                               show_pre_context(opt, gs, bol, eol, lno);
                        else if (opt->funcname)
-                               show_funcname_line(opt, name, buf, bol, lno);
-                       show_line(opt, bol, eol, name, lno, ':');
+                               show_funcname_line(opt, gs, bol, lno);
+                       show_line(opt, bol, eol, gs->name, lno, ':');
                        last_hit = lno;
                        if (opt->funcbody)
                                show_function = 1;
                        goto next_line;
                }
-               if (show_function && match_funcname(opt, name, bol, eol))
+               if (show_function && match_funcname(opt, gs, bol, eol))
                        show_function = 0;
                if (show_function ||
                    (last_hit && lno <= last_hit + opt->post_context)) {
                        /* If the last hit is within the post context,
                         * we need to show this line.
                         */
-                       show_line(opt, bol, eol, name, lno, '-');
+                       show_line(opt, bol, eol, gs->name, lno, '-');
                }
 
        next_line:
@@ -1133,7 +1142,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                return 0;
        if (opt->unmatch_name_only) {
                /* We did not see any hit, so we want to show this */
-               show_name(opt, name);
+               show_name(opt, gs->name);
                return 1;
        }
 
@@ -1147,7 +1156,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
         */
        if (opt->count && count) {
                char buf[32];
-               output_color(opt, name, strlen(name), opt->color_filename);
+               output_color(opt, gs->name, strlen(gs->name), opt->color_filename);
                output_sep(opt, ':');
                snprintf(buf, sizeof(buf), "%u\n", count);
                opt->output(opt, buf, strlen(buf));
@@ -1182,23 +1191,174 @@ static int chk_hit_marker(struct grep_expr *x)
        }
 }
 
-int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size)
+int grep_source(struct grep_opt *opt, struct grep_source *gs)
 {
        /*
         * we do not have to do the two-pass grep when we do not check
         * buffer-wide "all-match".
         */
        if (!opt->all_match)
-               return grep_buffer_1(opt, name, buf, size, 0);
+               return grep_source_1(opt, gs, 0);
 
        /* Otherwise the toplevel "or" terms hit a bit differently.
         * We first clear hit markers from them.
         */
        clr_hit_marker(opt->pattern_expression);
-       grep_buffer_1(opt, name, buf, size, 1);
+       grep_source_1(opt, gs, 1);
 
        if (!chk_hit_marker(opt->pattern_expression))
                return 0;
 
-       return grep_buffer_1(opt, name, buf, size, 0);
+       return grep_source_1(opt, gs, 0);
+}
+
+int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size)
+{
+       struct grep_source gs;
+       int r;
+
+       grep_source_init(&gs, GREP_SOURCE_BUF, NULL, NULL);
+       gs.buf = buf;
+       gs.size = size;
+
+       r = grep_source(opt, &gs);
+
+       grep_source_clear(&gs);
+       return r;
+}
+
+void grep_source_init(struct grep_source *gs, enum grep_source_type type,
+                     const char *name, const void *identifier)
+{
+       gs->type = type;
+       gs->name = name ? xstrdup(name) : NULL;
+       gs->buf = NULL;
+       gs->size = 0;
+       gs->driver = NULL;
+
+       switch (type) {
+       case GREP_SOURCE_FILE:
+               gs->identifier = xstrdup(identifier);
+               break;
+       case GREP_SOURCE_SHA1:
+               gs->identifier = xmalloc(20);
+               memcpy(gs->identifier, identifier, 20);
+               break;
+       case GREP_SOURCE_BUF:
+               gs->identifier = NULL;
+       }
+}
+
+void grep_source_clear(struct grep_source *gs)
+{
+       free(gs->name);
+       gs->name = NULL;
+       free(gs->identifier);
+       gs->identifier = NULL;
+       grep_source_clear_data(gs);
+}
+
+void grep_source_clear_data(struct grep_source *gs)
+{
+       switch (gs->type) {
+       case GREP_SOURCE_FILE:
+       case GREP_SOURCE_SHA1:
+               free(gs->buf);
+               gs->buf = NULL;
+               gs->size = 0;
+               break;
+       case GREP_SOURCE_BUF:
+               /* leave user-provided buf intact */
+               break;
+       }
+}
+
+static int grep_source_load_sha1(struct grep_source *gs)
+{
+       enum object_type type;
+
+       grep_read_lock();
+       gs->buf = read_sha1_file(gs->identifier, &type, &gs->size);
+       grep_read_unlock();
+
+       if (!gs->buf)
+               return error(_("'%s': unable to read %s"),
+                            gs->name,
+                            sha1_to_hex(gs->identifier));
+       return 0;
+}
+
+static int grep_source_load_file(struct grep_source *gs)
+{
+       const char *filename = gs->identifier;
+       struct stat st;
+       char *data;
+       size_t size;
+       int i;
+
+       if (lstat(filename, &st) < 0) {
+       err_ret:
+               if (errno != ENOENT)
+                       error(_("'%s': %s"), filename, strerror(errno));
+               return -1;
+       }
+       if (!S_ISREG(st.st_mode))
+               return -1;
+       size = xsize_t(st.st_size);
+       i = open(filename, O_RDONLY);
+       if (i < 0)
+               goto err_ret;
+       data = xmalloc(size + 1);
+       if (st.st_size != read_in_full(i, data, size)) {
+               error(_("'%s': short read %s"), filename, strerror(errno));
+               close(i);
+               free(data);
+               return -1;
+       }
+       close(i);
+       data[size] = 0;
+
+       gs->buf = data;
+       gs->size = size;
+       return 0;
+}
+
+int grep_source_load(struct grep_source *gs)
+{
+       if (gs->buf)
+               return 0;
+
+       switch (gs->type) {
+       case GREP_SOURCE_FILE:
+               return grep_source_load_file(gs);
+       case GREP_SOURCE_SHA1:
+               return grep_source_load_sha1(gs);
+       case GREP_SOURCE_BUF:
+               return gs->buf ? 0 : -1;
+       }
+       die("BUG: invalid grep_source type");
+}
+
+void grep_source_load_driver(struct grep_source *gs)
+{
+       if (gs->driver)
+               return;
+
+       grep_attr_lock();
+       gs->driver = userdiff_find_by_path(gs->name);
+       if (!gs->driver)
+               gs->driver = userdiff_find_by_name("default");
+       grep_attr_unlock();
+}
+
+int grep_source_is_binary(struct grep_source *gs)
+{
+       grep_source_load_driver(gs);
+       if (gs->driver->binary != -1)
+               return gs->driver->binary;
+
+       if (!grep_source_load(gs))
+               return buffer_is_binary(gs->buf, gs->size);
+
+       return 0;
 }
diff --git a/grep.h b/grep.h
index fb205f354231c0e50026c6e7fbfae5e288620611..36e49d8255e2952828f367a0e35d4fcb027d9772 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -9,6 +9,7 @@ typedef int pcre_extra;
 #endif
 #include "kwset.h"
 #include "thread-utils.h"
+#include "userdiff.h"
 
 enum grep_pat_token {
        GREP_PATTERN,
@@ -116,7 +117,6 @@ struct grep_opt {
        int show_hunk_mark;
        int file_break;
        int heading;
-       int use_threads;
        void *priv;
 
        void (*output)(struct grep_opt *opt, const void *data, size_t size);
@@ -128,7 +128,33 @@ extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const cha
 extern void append_header_grep_pattern(struct grep_opt *, enum grep_header_field, const char *);
 extern void compile_grep_patterns(struct grep_opt *opt);
 extern void free_grep_patterns(struct grep_opt *opt);
-extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size);
+extern int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size);
+
+struct grep_source {
+       char *name;
+
+       enum grep_source_type {
+               GREP_SOURCE_SHA1,
+               GREP_SOURCE_FILE,
+               GREP_SOURCE_BUF,
+       } type;
+       void *identifier;
+
+       char *buf;
+       unsigned long size;
+
+       struct userdiff_driver *driver;
+};
+
+void grep_source_init(struct grep_source *gs, enum grep_source_type type,
+                     const char *name, const void *identifier);
+int grep_source_load(struct grep_source *gs);
+void grep_source_clear_data(struct grep_source *gs);
+void grep_source_clear(struct grep_source *gs);
+void grep_source_load_driver(struct grep_source *gs);
+int grep_source_is_binary(struct grep_source *gs);
+
+int grep_source(struct grep_opt *opt, struct grep_source *gs);
 
 extern struct grep_opt *grep_opt_dup(const struct grep_opt *opt);
 extern int grep_threads_ok(const struct grep_opt *opt);
@@ -138,7 +164,25 @@ extern int grep_threads_ok(const struct grep_opt *opt);
  * Mutex used around access to the attributes machinery if
  * opt->use_threads.  Must be initialized/destroyed by callers!
  */
+extern int grep_use_locks;
 extern pthread_mutex_t grep_attr_mutex;
+extern pthread_mutex_t grep_read_mutex;
+
+static inline void grep_read_lock(void)
+{
+       if (grep_use_locks)
+               pthread_mutex_lock(&grep_read_mutex);
+}
+
+static inline void grep_read_unlock(void)
+{
+       if (grep_use_locks)
+               pthread_mutex_unlock(&grep_read_mutex);
+}
+
+#else
+#define grep_read_lock()
+#define grep_read_unlock()
 #endif
 
 #endif
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);
index 8c3196c7d76ff90e90a9fb4ff569aff6a429861a..47aa41924507f7603aab5e35abb51b3956a33dad 100644 (file)
--- a/mailmap.c
+++ b/mailmap.c
@@ -190,27 +190,27 @@ void clear_mailmap(struct string_list *map)
 int map_user(struct string_list *map,
             char *email, int maxlen_email, char *name, int maxlen_name)
 {
-       char *p;
+       char *end_of_email;
        struct string_list_item *item;
        struct mailmap_entry *me;
        char buf[1024], *mailbuf;
        int i;
 
        /* figure out space requirement for email */
-       p = strchr(email, '>');
-       if (!p) {
+       end_of_email = strchr(email, '>');
+       if (!end_of_email) {
                /* email passed in might not be wrapped in <>, but end with a \0 */
-               p = memchr(email, '\0', maxlen_email);
-               if (!p)
+               end_of_email = memchr(email, '\0', maxlen_email);
+               if (!end_of_email)
                        return 0;
        }
-       if (p - email + 1 < sizeof(buf))
+       if (end_of_email - email + 1 < sizeof(buf))
                mailbuf = buf;
        else
-               mailbuf = xmalloc(p - email + 1);
+               mailbuf = xmalloc(end_of_email - email + 1);
 
        /* downcase the email address */
-       for (i = 0; i < p - email; i++)
+       for (i = 0; i < end_of_email - email; i++)
                mailbuf[i] = tolower(email[i]);
        mailbuf[i] = 0;
 
@@ -236,6 +236,8 @@ int map_user(struct string_list *map,
                }
                if (maxlen_email && mi->email)
                        strlcpy(email, mi->email, maxlen_email);
+               else
+                       *end_of_email = '\0';
                if (maxlen_name && mi->name)
                        strlcpy(name, mi->name, maxlen_name);
                debug_mm("map_user:  to '%s' <%s>\n", name, mi->email ? mi->email : "");
index d83cd6c662847fb51641d7b8bf16739e588f67a2..6479a60cd112c5b06b354b1a251c60bb4bce972a 100644 (file)
@@ -264,7 +264,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
 
        if (!cache_tree_fully_valid(active_cache_tree) &&
            cache_tree_update(active_cache_tree,
-                             active_cache, active_nr, 0, 0, 0) < 0)
+                             active_cache, active_nr, 0) < 0)
                die("error building trees");
 
        result = lookup_tree(active_cache_tree->sha1);
index eaa115ccb7a1154715c1d9dd9b7d4d360234aa01..cb672a55192cb44335aa2cbe1f10004bb26d86ff 100644 (file)
@@ -23,7 +23,7 @@ check_meld_for_output_version () {
        meld_path="$(git config mergetool.meld.path)"
        meld_path="${meld_path:-meld}"
 
-       if "$meld_path" --output /dev/null --help >/dev/null 2>&1
+       if "$meld_path" --help 2>&1 | grep -e --output >/dev/null
        then
                meld_has_output_option=true
        else
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;
+}
diff --git a/path.c b/path.c
index b6f71d1086981dc41bdbbc8954eccd9e9b719f98..6f2aa699ad63c2f7c65632761efbaebb7f461868 100644 (file)
--- a/path.c
+++ b/path.c
@@ -293,7 +293,7 @@ const char *enter_repo(const char *path, int strict)
 
        if (!strict) {
                static const char *suffix[] = {
-                       ".git/.git", "/.git", ".git", "", NULL,
+                       "/.git", "", ".git/.git", ".git", NULL,
                };
                const char *gitfile;
                int len = strlen(path);
@@ -324,8 +324,11 @@ const char *enter_repo(const char *path, int strict)
                        return NULL;
                len = strlen(used_path);
                for (i = 0; suffix[i]; i++) {
+                       struct stat st;
                        strcpy(used_path + len, suffix[i]);
-                       if (!access(used_path, F_OK)) {
+                       if (!stat(used_path, &st) &&
+                           (S_ISREG(st.st_mode) ||
+                           (S_ISDIR(st.st_mode) && is_git_directory(used_path)))) {
                                strcat(validated_path, suffix[i]);
                                break;
                        }
index 72ab9de2f94be30eef9e41ced97580f7de48e0fc..d851807feb9849813635471d7099e67ed743a2d7 100644 (file)
--- a/prompt.c
+++ b/prompt.c
@@ -9,6 +9,7 @@ static char *do_askpass(const char *cmd, const char *prompt)
        struct child_process pass;
        const char *args[3];
        static struct strbuf buffer = STRBUF_INIT;
+       int err = 0;
 
        args[0] = cmd;
        args[1] = prompt;
@@ -19,25 +20,30 @@ static char *do_askpass(const char *cmd, const char *prompt)
        pass.out = -1;
 
        if (start_command(&pass))
-               exit(1);
+               return NULL;
 
-       strbuf_reset(&buffer);
        if (strbuf_read(&buffer, pass.out, 20) < 0)
-               die("failed to get '%s' from %s\n", prompt, cmd);
+               err = 1;
 
        close(pass.out);
 
        if (finish_command(&pass))
-               exit(1);
+               err = 1;
+
+       if (err) {
+               error("unable to read askpass response from '%s'", cmd);
+               strbuf_release(&buffer);
+               return NULL;
+       }
 
        strbuf_setlen(&buffer, strcspn(buffer.buf, "\r\n"));
 
-       return buffer.buf;
+       return strbuf_detach(&buffer, NULL);
 }
 
 char *git_prompt(const char *prompt, int flags)
 {
-       char *r;
+       char *r = NULL;
 
        if (flags & PROMPT_ASKPASS) {
                const char *askpass;
@@ -48,12 +54,15 @@ char *git_prompt(const char *prompt, int flags)
                if (!askpass)
                        askpass = getenv("SSH_ASKPASS");
                if (askpass && *askpass)
-                       return do_askpass(askpass, prompt);
+                       r = do_askpass(askpass, prompt);
        }
 
-       r = git_terminal_prompt(prompt, flags & PROMPT_ECHO);
        if (!r)
-               die_errno("could not read '%s'", prompt);
+               r = git_terminal_prompt(prompt, flags & PROMPT_ECHO);
+       if (!r) {
+               /* prompts already contain ": " at the end */
+               die("could not read %s%s", prompt, strerror(errno));
+       }
        return r;
 }
 
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);
diff --git a/refs.c b/refs.c
index b8843bb4769a2c5e3496d962f8d6b85a483fa92e..c9f68353517bb6dcd4b19114a5191fdf9075a8d1 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -183,12 +183,6 @@ static struct ref_cache {
 
 static struct ref_entry *current_ref;
 
-/*
- * Never call sort_ref_array() on the extra_refs, because it is
- * allowed to contain entries with duplicate names.
- */
-static struct ref_array extra_refs;
-
 static void clear_ref_array(struct ref_array *array)
 {
        int i;
@@ -289,16 +283,6 @@ static void read_packed_refs(FILE *f, struct ref_array *array)
        }
 }
 
-void add_extra_ref(const char *refname, const unsigned char *sha1, int flag)
-{
-       add_ref(&extra_refs, create_ref_entry(refname, sha1, flag, 0));
-}
-
-void clear_extra_refs(void)
-{
-       clear_ref_array(&extra_refs);
-}
-
 static struct ref_array *get_packed_refs(struct ref_cache *refs)
 {
        if (!refs->did_packed) {
@@ -733,16 +717,11 @@ fallback:
 static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn,
                           int trim, int flags, void *cb_data)
 {
-       int retval = 0, i, p = 0, l = 0;
+       int retval = 0, p = 0, l = 0;
        struct ref_cache *refs = get_ref_cache(submodule);
        struct ref_array *packed = get_packed_refs(refs);
        struct ref_array *loose = get_loose_refs(refs);
 
-       struct ref_array *extra = &extra_refs;
-
-       for (i = 0; i < extra->nr; i++)
-               retval = do_one_ref(base, fn, trim, flags, cb_data, extra->refs[i]);
-
        sort_ref_array(packed);
        sort_ref_array(loose);
        while (p < packed->nr && l < loose->nr) {
diff --git a/refs.h b/refs.h
index 00ba1e2813b2c2fff74300e3b2036a04f9e28065..33202b0d4c85cafdaf60b568a6f728dd83462c46 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -56,14 +56,6 @@ extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refn
  */
 extern void add_packed_ref(const char *refname, const unsigned char *sha1);
 
-/*
- * Extra refs will be listed by for_each_ref() before any actual refs
- * for the duration of this process or until clear_extra_refs() is
- * called. Only extra refs added before for_each_ref() is called will
- * be listed on a given call of for_each_ref().
- */
-extern void add_extra_ref(const char *refname, const unsigned char *sha1, int flags);
-extern void clear_extra_refs(void);
 extern int ref_exists(const char *);
 
 extern int peel_ref(const char *refname, unsigned char *sha1);
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 c97d83448426c317d82c08faf6150b8fe3bd4326..819ff012ff046fee0da1fee16f965b3059ebc31b 100644 (file)
@@ -2149,7 +2149,6 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
        if (!opt->grep_filter.pattern_list && !opt->grep_filter.header_list)
                return 1;
        return grep_buffer(&opt->grep_filter,
-                          NULL, /* we say nothing, not even filename */
                           commit->buffer, strlen(commit->buffer));
 }
 
diff --git a/setup.c b/setup.c
index 61c22e6becc1e49f1e92c916a4b8badd30a9cb2f..7a3618fab774c0c26a99c6e23b4fcf726e5dc1ad 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -247,7 +247,7 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
  *    a proper "ref:", or a regular file HEAD that has a properly
  *    formatted sha1 object name.
  */
-static int is_git_directory(const char *suspect)
+int is_git_directory(const char *suspect)
 {
        char path[PATH_MAX];
        size_t len = strlen(suspect);
index 88f2151ff31870138a53e40f99f5ae9928b09545..4f06a0e450359744528d3b125fb09eacebf1eb4a 100644 (file)
@@ -54,6 +54,8 @@ static struct cached_object empty_tree = {
        0
 };
 
+static struct packed_git *last_found_pack;
+
 static struct cached_object *find_cached_object(const unsigned char *sha1)
 {
        int i;
@@ -720,6 +722,8 @@ void free_pack_by_name(const char *pack_name)
                        close_pack_index(p);
                        free(p->bad_object_sha1);
                        *pp = p->next;
+                       if (last_found_pack == p)
+                               last_found_pack = NULL;
                        free(p);
                        return;
                }
@@ -1202,6 +1206,11 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
 
                if (!fstat(fd, &st)) {
                        *size = xsize_t(st.st_size);
+                       if (!*size) {
+                               /* mmap() is forbidden on empty files */
+                               error("object file %s is empty", sha1_file_name(sha1));
+                               return NULL;
+                       }
                        map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0);
                }
                close(fd);
@@ -2010,54 +2019,58 @@ int is_pack_valid(struct packed_git *p)
        return !open_packed_git(p);
 }
 
+static int fill_pack_entry(const unsigned char *sha1,
+                          struct pack_entry *e,
+                          struct packed_git *p)
+{
+       off_t offset;
+
+       if (p->num_bad_objects) {
+               unsigned i;
+               for (i = 0; i < p->num_bad_objects; i++)
+                       if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i))
+                               return 0;
+       }
+
+       offset = find_pack_entry_one(sha1, p);
+       if (!offset)
+               return 0;
+
+       /*
+        * We are about to tell the caller where they can locate the
+        * requested object.  We better make sure the packfile is
+        * still here and can be accessed before supplying that
+        * answer, as it may have been deleted since the index was
+        * loaded!
+        */
+       if (!is_pack_valid(p)) {
+               warning("packfile %s cannot be accessed", p->pack_name);
+               return 0;
+       }
+       e->offset = offset;
+       e->p = p;
+       hashcpy(e->sha1, sha1);
+       return 1;
+}
+
 static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e)
 {
-       static struct packed_git *last_found = (void *)1;
        struct packed_git *p;
-       off_t offset;
 
        prepare_packed_git();
        if (!packed_git)
                return 0;
-       p = (last_found == (void *)1) ? packed_git : last_found;
 
-       do {
-               if (p->num_bad_objects) {
-                       unsigned i;
-                       for (i = 0; i < p->num_bad_objects; i++)
-                               if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i))
-                                       goto next;
-               }
+       if (last_found_pack && fill_pack_entry(sha1, e, last_found_pack))
+               return 1;
 
-               offset = find_pack_entry_one(sha1, p);
-               if (offset) {
-                       /*
-                        * We are about to tell the caller where they can
-                        * locate the requested object.  We better make
-                        * sure the packfile is still here and can be
-                        * accessed before supplying that answer, as
-                        * it may have been deleted since the index
-                        * was loaded!
-                        */
-                       if (!is_pack_valid(p)) {
-                               warning("packfile %s cannot be accessed", p->pack_name);
-                               goto next;
-                       }
-                       e->offset = offset;
-                       e->p = p;
-                       hashcpy(e->sha1, sha1);
-                       last_found = p;
-                       return 1;
-               }
+       for (p = packed_git; p; p = p->next) {
+               if (p == last_found_pack || !fill_pack_entry(sha1, e, p))
+                       continue;
 
-               next:
-               if (p == last_found)
-                       p = packed_git;
-               else
-                       p = p->next;
-               if (p == last_found)
-                       p = p->next;
-       } while (p);
+               last_found_pack = p;
+               return 1;
+       }
        return 0;
 }
 
@@ -2687,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,
@@ -2707,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 66ceefefccac693d29077ef8020fc4f9dbe7a0bc..6091211f1009679aee741f3f6c2b290033aa5dc6 100644 (file)
@@ -73,6 +73,9 @@ gitweb-test:
 valgrind:
        $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --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')
@@ -111,4 +114,4 @@ smoke_report: smoke
                http://smoke.git.nix.is/app/projects/process_add_report/1 \
        | grep -v ^Redirecting
 
-.PHONY: pre-clean $(T) aggregate-results clean valgrind smoke smoke_report
+.PHONY: pre-clean $(T) aggregate-results clean valgrind perf
index c09c582c163dd547ed5b1d7a65e13e3345bb7eca..3534f43d016ae56fdd20a824cf76e2b63339d66d 100644 (file)
--- a/t/README
+++ b/t/README
@@ -671,76 +671,3 @@ Then, at the top-level:
 That'll generate a detailed cover report in the "cover_db_html"
 directory, which you can then copy to a webserver, or inspect locally
 in a browser.
-
-Smoke testing
--------------
-
-The Git test suite has support for smoke testing. Smoke testing is
-when you submit the results of a test run to a central server for
-analysis and aggregation.
-
-Running a smoke tester is an easy and valuable way of contributing to
-Git development, particularly if you have access to an uncommon OS on
-obscure hardware.
-
-After building Git you can generate a smoke report like this in the
-"t" directory:
-
-    make clean smoke
-
-You can also pass arguments via the environment. This should make it
-faster:
-
-    GIT_TEST_OPTS='--root=/dev/shm' TEST_JOBS=10 make clean smoke
-
-The "smoke" target will run the Git test suite with Perl's
-"TAP::Harness" module, and package up the results in a .tar.gz archive
-with "TAP::Harness::Archive". The former is included with Perl v5.10.1
-or later, but you'll need to install the latter from the CPAN. See the
-"Test coverage" section above for how you might do that.
-
-Once the "smoke" target finishes you'll see a message like this:
-
-    TAP Archive created at <path to git>/t/test-results/git-smoke.tar.gz
-
-To upload the smoke report you need to have curl(1) installed, then
-do:
-
-    make smoke_report
-
-To upload the report anonymously. Hopefully that'll return something
-like "Reported #7 added.".
-
-If you're going to be uploading reports frequently please request a
-user account by E-Mailing gitsmoke@v.nix.is. Once you have a username
-and password you'll be able to do:
-
-    SMOKE_USERNAME=<username> SMOKE_PASSWORD=<password> make smoke_report
-
-You can also add an additional comment to attach to the report, and/or
-a comma separated list of tags:
-
-    SMOKE_USERNAME=<username> SMOKE_PASSWORD=<password> \
-        SMOKE_COMMENT=<comment> SMOKE_TAGS=<tags> \
-        make smoke_report
-
-Once the report is uploaded it'll be made available at
-http://smoke.git.nix.is, here's an overview of Recent Smoke Reports
-for Git:
-
-    http://smoke.git.nix.is/app/projects/smoke_reports/1
-
-The reports will also be mirrored to GitHub every few hours:
-
-    http://github.com/gitsmoke/smoke-reports
-
-The Smolder SQLite database is also mirrored and made available for
-download:
-
-    http://github.com/gitsmoke/smoke-database
-
-Note that the database includes hashed (with crypt()) user passwords
-and E-Mail addresses. Don't use a valuable password for the smoke
-service if you have an account, or an E-Mail address you don't want to
-be publicly known. The user accounts are just meant to be convenient
-labels, they're not meant to be secure.
diff --git a/t/harness b/t/harness
deleted file mode 100755 (executable)
index f5c02f4..0000000
--- a/t/harness
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-use Getopt::Long ();
-use TAP::Harness::Archive;
-
-Getopt::Long::Parser->new(
-       config => [ qw/ pass_through / ],
-)->getoptions(
-       'jobs:1'    => \(my $jobs = $ENV{TEST_JOBS}),
-       'archive=s' => \my $archive,
-) or die "$0: Couldn't getoptions()";
-
-TAP::Harness::Archive->new({
-       jobs             => $jobs,
-       archive          => $archive,
-       ($ENV{GIT_TEST_OPTS}
-        ? (test_args    => [ split /\s+/, $ENV{GIT_TEST_OPTS} ])
-        : ()),
-       extra_properties => {},
-})->runtests(@ARGV);
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 5e29e13782ffad74915128adf163d57eef16f4e8..9356beaf4be45c32d10209c5063b7a9c5aa06d42 100755 (executable)
@@ -156,7 +156,7 @@ Updating VARIABLE..VARIABLE
 FASTFORWARD (no commit created; -m option ignored)
  example |    1 +
  hello   |    1 +
- 2 files changed, 2 insertions(+), 0 deletions(-)
+ 2 files changed, 2 insertions(+)
 EOF
 
 test_expect_success 'git resolve' '
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 c551d39a66c19514e260a7c938b840ce2870d45a..6352b74e2e54e5e08941d8d5d90ac30b202b56c1 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='checkout from unborn branch protects contents'
+test_description='checkout from unborn branch'
 . ./test-lib.sh
 
 test_expect_success 'setup' '
@@ -37,4 +37,13 @@ test_expect_success 'checkout from unborn merges identical index contents' '
        git checkout -b new origin
 '
 
+test_expect_success 'checking out another branch from unborn state' '
+       git checkout --orphan newroot &&
+       git checkout -b anothername &&
+       test_must_fail git show-ref --verify refs/heads/newroot &&
+       git symbolic-ref HEAD >actual &&
+       echo refs/heads/anothername >expect &&
+       test_cmp expect actual
+'
+
 test_done
index 25435290a71ae8031762a3e63537fd3a23a8fc90..ec35409f9cbe9bcdd3d21a6a5a15996b165d2fff 100755 (executable)
@@ -32,7 +32,7 @@ test_expect_success 'intent to add does not clobber existing paths' '
        ! grep "$empty" actual
 '
 
-test_expect_success 'cannot commit with i-t-a entry' '
+test_expect_success 'i-t-a entry is simply ignored' '
        test_tick &&
        git commit -a -m initial &&
        git reset --hard &&
@@ -41,12 +41,14 @@ test_expect_success 'cannot commit with i-t-a entry' '
        echo frotz >nitfol &&
        git add rezrov &&
        git add -N nitfol &&
-       test_must_fail git commit -m initial
+       git commit -m second &&
+       test $(git ls-tree HEAD -- nitfol | wc -l) = 0 &&
+       test $(git diff --name-only HEAD -- nitfol | wc -l) = 1
 '
 
 test_expect_success 'can commit with an unrelated i-t-a entry in index' '
        git reset --hard &&
-       echo xyzzy >rezrov &&
+       echo bozbar >rezrov &&
        echo frotz >nitfol &&
        git add rezrov &&
        git add -N nitfol &&
index 5e29a052599bc28c0edaf56dfedb5f069b5ff01e..9f00ada5f7776b2377993cc9392b89e5312a513f 100755 (executable)
@@ -167,7 +167,7 @@ test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny' \
 test_expect_success TABS_IN_FILENAMES 'setup expect' '
 cat >expected <<\EOF
  "tabs\t,\" (dq) and spaces"
- 1 files changed, 0 insertions(+), 0 deletions(-)
+ 1 file changed, 0 insertions(+), 0 deletions(-)
 EOF
 '
 
index 8e09fd0319c95cbd4d30c461f00fee5f52e27cbd..1b3a344158aa8077c1e5b47f9ab8bd6394e153ed 100755 (executable)
@@ -38,13 +38,13 @@ test_expect_success 'cherry-pick first..fourth works' '
        cat <<-\EOF >expected &&
        [master OBJID] second
         Author: A U Thor <author@example.com>
-        1 files changed, 1 insertions(+), 0 deletions(-)
+        1 file changed, 1 insertion(+)
        [master OBJID] third
         Author: A U Thor <author@example.com>
-        1 files changed, 1 insertions(+), 0 deletions(-)
+        1 file changed, 1 insertion(+)
        [master OBJID] fourth
         Author: A U Thor <author@example.com>
-        1 files changed, 1 insertions(+), 0 deletions(-)
+        1 file changed, 1 insertion(+)
        EOF
 
        git checkout -f master &&
@@ -64,15 +64,15 @@ test_expect_success 'cherry-pick --strategy resolve first..fourth works' '
        Trying simple merge.
        [master OBJID] second
         Author: A U Thor <author@example.com>
-        1 files changed, 1 insertions(+), 0 deletions(-)
+        1 file changed, 1 insertion(+)
        Trying simple merge.
        [master OBJID] third
         Author: A U Thor <author@example.com>
-        1 files changed, 1 insertions(+), 0 deletions(-)
+        1 file changed, 1 insertion(+)
        Trying simple merge.
        [master OBJID] fourth
         Author: A U Thor <author@example.com>
-        1 files changed, 1 insertions(+), 0 deletions(-)
+        1 file changed, 1 insertion(+)
        EOF
 
        git checkout -f master &&
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 dbe2ac179dead1ea825f62b7c26e3ceb6c0d638d..663c60a12e82c96065e60fd448a6583c91e5a2cd 100755 (executable)
@@ -444,7 +444,7 @@ test_expect_success 'stash show - stashes on stack, stash-like argument' '
        git reset --hard &&
        cat >expected <<-EOF &&
         file |    1 +
-        1 files changed, 1 insertions(+), 0 deletions(-)
+        1 file changed, 1 insertion(+)
        EOF
        git stash show ${STASH_ID} >actual &&
        test_cmp expected actual
@@ -482,7 +482,7 @@ test_expect_success 'stash show - no stashes on stack, stash-like argument' '
        git reset --hard &&
        cat >expected <<-EOF &&
         file |    1 +
-        1 files changed, 1 insertions(+), 0 deletions(-)
+        1 file changed, 1 insertion(+)
        EOF
        git stash show ${STASH_ID} >actual &&
        test_cmp expected actual
index 3a9f78a09df1bb12aefbb2b5fb75fd0b9455ed06..2f8560c369c01e72b8ec11d43e04df0a0e9ce60c 100644 (file)
@@ -2,7 +2,7 @@ $ git diff-tree --cc --patch-with-stat --summary master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --cc dir/sub
 index cead32e,7289e35..992913c
index a61ad8cb130093ed63bba5335a0b283f9136177e..72e03c14fb4edb2812202da8acd837b064131510 100644 (file)
@@ -3,7 +3,7 @@ c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
index 49f23b9215c3179d8923915ee2e497ccbc53875a..8b357d964b4773c3078e2c32bd3e765f5749841c 100644 (file)
@@ -2,7 +2,7 @@ $ git diff-tree --cc --patch-with-stat master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --cc dir/sub
 index cead32e,7289e35..992913c
index cc6eb3b3d50f2d620df7116bf64992a4dd42b736..e0568d688311059ad04558f50bb3ddacd21fd403 100644 (file)
@@ -2,5 +2,5 @@ $ git diff-tree --cc --stat --summary master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 $
index 50362be7bf851759885ad17847f5913e5dfb0b3c..5afc8239a11f7f7fc20a3fabfad5213eb49fd153 100644 (file)
@@ -3,6 +3,6 @@ c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 $
index fae7f33255faef186aa7b987c29adfe33d0365f1..f48367a89a64945875a81024e444b35771800855 100644 (file)
@@ -2,5 +2,5 @@ $ git diff-tree --cc --stat master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 $
index d5c333a378c9407abf179d6cc3f5233ca8b5f116..590864c2d798472a3fed9fd6bf1fa41545fd572d 100644 (file)
@@ -3,7 +3,7 @@ $ git diff-tree --pretty=oneline --root --patch-with-stat initial
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 new file mode 100644
index 4d30e7eddc05aa561b43519b031f89de8a30fd98..e05e77875ccdaa8a258310f85f5753ae84b4c6c4 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
index 7dfa6af3c97b1ad469135bb9a9f3c1f8d6d4b60d..0e2c956633d28897e7bbddb58ad1cc053e55a8a6 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 new file mode 100644
index 43bfce253eaf451e2f0a19042c9cbb8879c6fef1..384fa44ddd1ab9dbf0d38caf1f445fed1a0b5030 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
  create mode 100644 file2
index 9154aa4d4700b4a7c1e4c205f43694c198419ece..10384a83d31da363cec05f2fecb9def43ae158d7 100644 (file)
@@ -8,5 +8,5 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
 $
index 1562b627085ff0ad8ffaea61cc42ed39a9d94fc8..f57062ea07ac63aa7c700e470aa43d1ede20e09b 100644 (file)
@@ -3,7 +3,7 @@ $ git diff-tree --root --patch-with-stat initial
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 new file mode 100644
index ac9f641fb48272f25f3385dfbbd05777564f16bc..7088683444e54a026e201849ef5d635fab66fc3b 100644 (file)
@@ -2,5 +2,5 @@ $ git diff-tree -c --stat --summary master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 $
index 2afcca11f4d0c4db6e259e85540e4d90f8c76a10..ef216abb1dcaa91d1aa521599b25faf16f704124 100644 (file)
@@ -3,6 +3,6 @@ c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 $
index c2fe6a98c5f9818f9caf5e0a314aee45831e5329..ad19f103eb3323d1c84719cec13a09885e3b47f4 100644 (file)
@@ -2,5 +2,5 @@ $ git diff-tree -c --stat master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 $
index 9ed317a198c71fcec0b44a7b736b61550eed9394..ddad917ae80a5c80b2c7b7f65a85a19404b59945 100644 (file)
@@ -2,7 +2,7 @@ $ git diff --patch-with-stat -r initial..side
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
index 8b50629e668a385179eb9586b472c51237492232..bdbd114d8eaf5dd6f40502300c479839a61f0d6b 100644 (file)
@@ -2,7 +2,7 @@ $ git diff --patch-with-stat initial..side
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
index 0517b5d63129f44275b16f51819d9c08bf37d430..6d08f3d3550413a23c08e264b4707dafeba21acf 100644 (file)
@@ -2,5 +2,5 @@ $ git diff --stat initial..side
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 $
index 245220d3f9b475b4281395639d85122feaa7948d..2ddb2540e640584532dd7b22d1618b35b322a20d 100644 (file)
@@ -2,5 +2,5 @@ $ git diff -r --stat initial..side
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 $
index 52116d3eadc3bc0eed1428d76fefe44c26d75780..3cab049f7d98bfe9988c9f19fe477d0d29c8c2d5 100644 (file)
@@ -15,7 +15,7 @@ Content-Transfer-Encoding: 8bit
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 
index ce49bd676e1a59eb015efc77e9963caa6a57a419..564a4d38f229946ea08e9c0110a5ad6a20acce0b 100644 (file)
@@ -75,7 +75,7 @@ Content-Transfer-Encoding: 8bit
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 
@@ -124,7 +124,7 @@ Content-Transfer-Encoding: 8bit
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 
index 5f1b23863bd286a946dda09a8a7f3beb022b1f66..4f28460b83dd47a1446fc5c110025816611e2a54 100644 (file)
@@ -75,7 +75,7 @@ Content-Transfer-Encoding: 8bit
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 
index 4a2364abc2263e0e8925053acdd7c3c98282df2e..b10cc2e251cf7e38592ae26279b9613758a47b35 100644 (file)
@@ -15,7 +15,7 @@ Content-Transfer-Encoding: 8bit
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 
index 43b81eba5440a1f365d0d5ee81ea263c2b81d686..a976a8aaf42b2fa03fd35d215de9cafee99b946c 100644 (file)
@@ -75,7 +75,7 @@ Content-Transfer-Encoding: 8bit
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 
@@ -124,7 +124,7 @@ Content-Transfer-Encoding: 8bit
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 
index ca3f60bf0ed3858903fc6c86b99309211c340d13..b4fd66477aa251c56da1e532d83b7d56ec79d25b 100644 (file)
@@ -75,7 +75,7 @@ Content-Transfer-Encoding: 8bit
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 
@@ -124,7 +124,7 @@ Content-Transfer-Encoding: 8bit
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 
index 08f23014bc15792946160c4ef45fdcfeba7e933d..0d31036e7f573d70f0ec3c00b67b61291a663671 100644 (file)
@@ -75,7 +75,7 @@ Content-Transfer-Encoding: 8bit
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 
@@ -124,7 +124,7 @@ Content-Transfer-Encoding: 8bit
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 
index 07f1230d31f4cdd9f712bd2a69fea035a6998eb2..18d47144231ab36e6f020071b1b8599dd2e76ace 100644 (file)
@@ -75,7 +75,7 @@ Content-Transfer-Encoding: 8bit
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 
index 67633d424a466507015c5a02e721be9b8e6fdfe4..3572f20b5d4a1834d1c60ca05eca11efd8b46375 100644 (file)
@@ -15,7 +15,7 @@ Content-Transfer-Encoding: 8bit
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 
index 3b4e113012568dc936bd230fe33c251ab8bff3dd..54cdcdab40f321559038c954fab572e532801b8b 100644 (file)
@@ -75,7 +75,7 @@ Subject: [DIFFERENT_PREFIX 2/2] Third
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
index f7752ebbea7c735faf38fe6f3c2c963f4a4b0f40..23194ebdaa8defaca544a0f6d18d449a44fcd5f1 100644 (file)
@@ -53,7 +53,7 @@ Subject: [PATCH] Third
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -88,7 +88,7 @@ Subject: [PATCH] Side
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
index 8e67dbf76fa508ae498e17834f963506b4740438..78f1a80a976ac54cc53602f203cae96f12be65dc 100644 (file)
@@ -53,7 +53,7 @@ Subject: [PATCH 2/3] Third
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -88,7 +88,7 @@ Subject: [PATCH 3/3] Side
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
index 7b89978e321bc20c55a7016f7111275bb553b1f0..a3dab7f773d71da1f1b1ade10b24cf9562ff7d1b 100644 (file)
@@ -53,7 +53,7 @@ Subject: [PATCH 2/3] Third
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -88,7 +88,7 @@ Subject: [PATCH 3/3] Side
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
index b7f9725dc4158d4cc6131203d3a6ee1b7fd8c456..39f4a3f2d1f50ce49792f4fcc6373d003ebf971b 100644 (file)
@@ -53,7 +53,7 @@ Subject: [PATCH 2/2] Third
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
index e7650884755b993661ef14fbc812edd7c2710702..88109209db005188c9e70fdd40b906756119dd69 100644 (file)
@@ -8,7 +8,7 @@ Subject: [PATCH] Side
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
index bd7f5c0f70571d0aa0f9b9c50a343a322831976f..4085bbde870d0810c69e7d78ef94052df385826c 100644 (file)
@@ -13,7 +13,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
     Side
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
@@ -32,7 +32,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
     Third
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
@@ -54,7 +54,7 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     This is the second commit.
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
index 14595a614c362da4515f8d5b783bb0e0f079c72b..458627953e29c80b8bed0e640db1c580946a0194 100644 (file)
@@ -15,7 +15,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
@@ -56,7 +56,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
index 5a4e72765d316b855a8c2beee8f417cd050de323..6e172cfadd04785269e0060fa55e86b3df117195 100644 (file)
@@ -13,7 +13,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
     Side
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
@@ -32,7 +32,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
     Third
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
@@ -54,7 +54,7 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     This is the second commit.
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
index df0aaa9f2ca780ce62951a5667ed9fe3aa005e41..48b0d4b91dfa427c1f0971bedbad61931e103bb7 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:04:00 2006 +0000
 
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --cc dir/sub
 index cead32e,7289e35..992913c
@@ -47,7 +47,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
@@ -89,7 +89,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -165,7 +165,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
  create mode 100644 file2
index c11b5f2c7f3e6846643b7d7e0e214993f4a2cf0d..f9dc5122e2a436a62fa80bdf820b4d44cacdfe8e 100644 (file)
@@ -15,7 +15,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
@@ -57,7 +57,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -133,7 +133,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
  create mode 100644 file2
index 5f0c98f9ce3d9d067cfcb662084b975a19c90f03..0807ece2347ac54f87c860904ef257c766da5dd0 100644 (file)
@@ -15,7 +15,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
@@ -56,7 +56,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
@@ -130,7 +130,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 new file mode 100644
index e62c368dc64dae87c6c168462b175712f197e7af..84f5ef69119edf44e967fa09e3c6acd0bdc6b715 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:04:00 2006 +0000
 
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --combined dir/sub
 index cead32e,7289e35..992913c
@@ -47,7 +47,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
@@ -89,7 +89,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -165,7 +165,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
  create mode 100644 file2
index 377f2b7b7a34a1e83bbcf0f2d72981685a678c24..e60384d24de317e9f9d6dac222d8123ae4d68e02 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
index fb14c530d2dd79730320badff2547ebf755bd350..a3a3255fd3f508ddd47a92cb47552dde3ab8156a 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
index 5bd597762869443cfd91ba8ebdaeff6e8b515ebf..d16f464aca8f83264f53f0355bb3be84eddf1bd8 100644 (file)
@@ -8,6 +8,6 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 $
index 3b22327e48122187af12db46027f17526c8e9caf..6300c0535fff7e4a82fdfb6511392b006f5dfa0c 100644 (file)
@@ -8,5 +8,5 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 $
index 6a467cccc190449049b7baed2dde0a2f9027e663..16ae54345f6a1e93fae3ad7269829b32eaab3739 100644 (file)
@@ -6,7 +6,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
     Side
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
@@ -25,7 +25,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
     Third
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
@@ -47,7 +47,7 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     This is the second commit.
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
index 1e1bbe19638eeeffb4e656239545ea91fda3dbf2..f3e45ec270ace186d58f40a21a9763598d6dc0ac 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
@@ -49,7 +49,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
index 13789f169b6803ba71680470197c50b51795e4e4..c77f0bc320f0241550e9027f7ecb191d77f85b43 100644 (file)
@@ -6,7 +6,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
     Side
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
@@ -25,7 +25,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
     Third
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
@@ -47,7 +47,7 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     This is the second commit.
 ---
  dir/sub |    2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ 1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
index e96ff1fb8c11ff8c6774cb249f45744ed05a97c3..8d03efea6ce33bb52018eeedc02629c6bd9253aa 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:04:00 2006 +0000
 
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --cc dir/sub
 index cead32e,7289e35..992913c
@@ -47,7 +47,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
@@ -89,7 +89,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -165,7 +165,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
  create mode 100644 file2
index 02911535870b012720c9ae095499c61e6e242c7e..1874d0616c4d6cf308a6fcc0f59a0d552664777f 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
@@ -50,7 +50,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -126,7 +126,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
  create mode 100644 file2
index 9b0349cd555eb17836b8c056d7aae8cf1eabe537..5211ff2a757b8db234ec1f4b14ae33300c21296a 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
@@ -49,7 +49,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
@@ -123,7 +123,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
 new file mode 100644
index c0aff68ef68c2d956492e7f9817b9bf5afe58005..ad30245a59b94ed18bf9e1bfca3eb0077cd3a14a 100644 (file)
@@ -8,7 +8,7 @@ Date:   Mon Jun 26 00:04:00 2006 +0000
 
  dir/sub |    2 ++
  file0   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
 
 diff --combined dir/sub
 index cead32e,7289e35..992913c
@@ -47,7 +47,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file3   |    4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ 3 files changed, 9 insertions(+)
  create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
@@ -89,7 +89,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 ---
  dir/sub |    2 ++
  file1   |    3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ 2 files changed, 5 insertions(+)
  create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
@@ -165,7 +165,7 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
  dir/sub |    2 ++
  file0   |    3 +++
  file2   |    3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ 3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
  create mode 100644 file2
index 67975129bc3703e16fe52eb916c2e08b7908ff87..7dfe716cf9ed63f08f512cfa123d9bf93fa92839 100755 (executable)
@@ -520,7 +520,7 @@ test_expect_success 'shortlog of cover-letter wraps overly-long onelines' '
 cat > expect << EOF
 ---
  file |   16 ++++++++++++++++
- 1 files changed, 16 insertions(+), 0 deletions(-)
+ 1 file changed, 16 insertions(+)
 
 diff --git a/file b/file
 index 40f36c6..2dc5c23 100644
index 88c5619ae7471ab0d3286259d88c437ae3953b4a..4ac162cfcf891e5fe0a36a3074db0d486ffa5cf0 100755 (executable)
@@ -86,7 +86,7 @@ test_expect_success 'status -v produces text' '
 
 cat >expect.stat <<'EOF'
  file |  Bin 2 -> 4 bytes
- 1 files changed, 0 insertions(+), 0 deletions(-)
+ 1 file changed, 0 insertions(+), 0 deletions(-)
 EOF
 test_expect_success 'diffstat does not run textconv' '
        echo file diff=fail >.gitattributes &&
index 8a3c63b9e2d8d83b78bd8c9a606b8d774ecf53b3..bd119be106f1a2d6d91f982a1bf8afcbf6b8c831 100755 (executable)
@@ -33,7 +33,7 @@ check_stat() {
 expect=$1; shift
 cat >expected <<EOF
  $expect |    1 +
- 1 files changed, 1 insertions(+), 0 deletions(-)
+ 1 file changed, 1 insertion(+)
 EOF
 test_expect_success "--stat $*" "
        git diff --stat $* HEAD^ >actual &&
index 641e70d14d82b2e3870af058f09d063c99812825..a6d1887536e240e89b8e2263e5f0a643e9a55f71 100755 (executable)
@@ -16,7 +16,7 @@ test_expect_success setup '
        cat >expect <<-\EOF
         a |    1 +
         b |    1 +
-        2 files changed, 2 insertions(+), 0 deletions(-)
+        2 files changed, 2 insertions(+)
        EOF
        git diff --stat --stat-count=2 >actual &&
        test_cmp expect actual
index eef7f2e65cc3e2192935b9bde3ea001244b6db18..55a55c3cc77931fef9c3850992fcbcebc62eec0c 100644 (file)
@@ -1,2 +1,2 @@
  t/t4100-apply-stat.sh |    2 +-
- 1 files changed, 1 insertions(+), 1 deletions(-)
+ 1 file changed, 1 insertion(+), 1 deletion(-)
index eef7f2e65cc3e2192935b9bde3ea001244b6db18..55a55c3cc77931fef9c3850992fcbcebc62eec0c 100644 (file)
@@ -1,2 +1,2 @@
  t/t4100-apply-stat.sh |    2 +-
- 1 files changed, 1 insertions(+), 1 deletions(-)
+ 1 file changed, 1 insertion(+), 1 deletion(-)
index 8807b602a51c58cd6fcc313441a0cc7339fc487c..6f77fffee60b5e37140dc8952c95144035ed457a 100755 (executable)
@@ -136,7 +136,7 @@ test_expect_success setup '
        git format-patch -M --stdout lorem^ >rename-add.patch &&
 
        # reset time
-       unset test_tick &&
+       sane_unset test_tick &&
        test_tick
 '
 
@@ -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 7c1dc641dec809fe314dcba7a4946154e8422f9b..2af8947eebb3e9ee45f83acb398335ec163a521c 100755 (executable)
@@ -95,7 +95,7 @@ test_expect_success 'setup: two scripts for reading pull requests' '
        b
        : diffstat
        n
-       / [0-9]* files changed/ {
+       / [0-9]* files* changed/ {
                a\\
        DIFFSTAT
                b
index 602806d09cda72c7bf0f407da35b2fb859404bd7..d9d856b87b2a896d4f80a3e62e6d1925b680a680 100755 (executable)
@@ -38,6 +38,10 @@ test_expect_success \
     'pack without delta' \
     'packname_1=$(git pack-objects --window=0 test-1 <obj-list)'
 
+test_expect_success \
+    'pack-objects with bogus arguments' \
+    'test_must_fail git pack-objects --window=0 test-1 blah blah <obj-list'
+
 rm -fr .git2
 mkdir .git2
 
index f8fa92446cfc46309468b4ecf142b74b1a812985..fe82025d4a0dbae0cee3d0c3943617ede5b5c7a5 100755 (executable)
@@ -73,6 +73,10 @@ test_expect_success 'index-pack --verify on index version 2' '
        git index-pack --verify "test-2-${pack2}.pack"
 '
 
+test_expect_success \
+    'pack-objects --index-version=2, is not accepted' \
+    'test_must_fail git pack-objects --index-version=2, test-3 <obj-list'
+
 test_expect_success \
     'index v2: force some 64-bit offsets with pack-objects' \
     'pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list)'
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 0ad7ce07c4550ed28a22743a3c543e16f4d4c558..ef98d95e00d7d25ee408b311339cf4f10c44a5ad 100755 (executable)
@@ -17,7 +17,7 @@ run_backend() {
 GET() {
        REQUEST_METHOD="GET" && export REQUEST_METHOD &&
        run_backend "/repo.git/$1" &&
-       unset REQUEST_METHOD &&
+       sane_unset REQUEST_METHOD &&
        if ! grep "Status" act.out >act
        then
                printf "Status: 200 OK\r\n" >act
@@ -30,8 +30,8 @@ POST() {
        REQUEST_METHOD="POST" && export REQUEST_METHOD &&
        CONTENT_TYPE="application/x-$1-request" && export CONTENT_TYPE &&
        run_backend "/repo.git/$1" "$2" &&
-       unset REQUEST_METHOD &&
-       unset CONTENT_TYPE &&
+       sane_unset REQUEST_METHOD &&
+       sane_unset CONTENT_TYPE &&
        if ! grep "Status" act.out >act
        then
                printf "Status: 200 OK\r\n" >act
index c4c375ac042bbb7f58998c87d8c9277d2f5004a6..bbc4691bd7ef1e3633d4a66440211179fae42a84 100755 (executable)
@@ -52,13 +52,13 @@ test_cmp expected current'
 
 cd "$base_dir"
 
-rm -f "$U"
+rm -f "$U.D"
 
 test_expect_success 'cloning with reference (no -l -s)' \
-'GIT_DEBUG_SEND_PACK=3 git clone --reference B "file://$(pwd)/A" D 3>"$U"'
+'GIT_DEBUG_SEND_PACK=3 git clone --reference B "file://$(pwd)/A" D 3>"$U.D"'
 
 test_expect_success 'fetched no objects' \
-'! grep "^want" "$U"'
+'! grep "^want" "$U.D"'
 
 cd "$base_dir"
 
@@ -153,4 +153,32 @@ test_expect_success 'clone with reference from a tagged repository' '
        git clone --reference=A A I
 '
 
+test_expect_success 'prepare branched repository' '
+       git clone A J &&
+       (
+               cd J &&
+               git checkout -b other master^ &&
+               echo other >otherfile &&
+               git add otherfile &&
+               git commit -m other &&
+               git checkout master
+       )
+'
+
+rm -f "$U.K"
+
+test_expect_success 'fetch with incomplete alternates' '
+       git init K &&
+       echo "$base_dir/A/.git/objects" >K/.git/objects/info/alternates &&
+       (
+               cd K &&
+               git remote add J "file://$base_dir/J" &&
+               GIT_DEBUG_SEND_PACK=3 git fetch J 3>"$U.K"
+       ) &&
+       master_object=$(cd A && git for-each-ref --format="%(objectname)" refs/heads/master) &&
+       ! grep "^want $master_object" "$U.K" &&
+       tag_object=$(cd A && git for-each-ref --format="%(objectname)" refs/tags/HEAD) &&
+       ! grep "^want $tag_object" "$U.K"
+'
+
 test_done
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
diff --git a/t/t5900-repo-selection.sh b/t/t5900-repo-selection.sh
new file mode 100755 (executable)
index 0000000..3d5b418
--- /dev/null
@@ -0,0 +1,100 @@
+#!/bin/sh
+
+test_description='selecting remote repo in ambiguous cases'
+. ./test-lib.sh
+
+reset() {
+       rm -rf foo foo.git fetch clone
+}
+
+make_tree() {
+       git init "$1" &&
+       (cd "$1" && test_commit "$1")
+}
+
+make_bare() {
+       git init --bare "$1" &&
+       (cd "$1" &&
+        tree=`git hash-object -w -t tree /dev/null` &&
+        commit=$(echo "$1" | git commit-tree $tree) &&
+        git update-ref HEAD $commit
+       )
+}
+
+get() {
+       git init --bare fetch &&
+       (cd fetch && git fetch "../$1") &&
+       git clone "$1" clone
+}
+
+check() {
+       echo "$1" >expect &&
+       (cd fetch && git log -1 --format=%s FETCH_HEAD) >actual.fetch &&
+       (cd clone && git log -1 --format=%s HEAD) >actual.clone &&
+       test_cmp expect actual.fetch &&
+       test_cmp expect actual.clone
+}
+
+test_expect_success 'find .git dir in worktree' '
+       reset &&
+       make_tree foo &&
+       get foo &&
+       check foo
+'
+
+test_expect_success 'automagically add .git suffix' '
+       reset &&
+       make_bare foo.git &&
+       get foo &&
+       check foo.git
+'
+
+test_expect_success 'automagically add .git suffix to worktree' '
+       reset &&
+       make_tree foo.git &&
+       get foo &&
+       check foo.git
+'
+
+test_expect_success 'prefer worktree foo over bare foo.git' '
+       reset &&
+       make_tree foo &&
+       make_bare foo.git &&
+       get foo &&
+       check foo
+'
+
+test_expect_success 'prefer bare foo over bare foo.git' '
+       reset &&
+       make_bare foo &&
+       make_bare foo.git &&
+       get foo &&
+       check foo
+'
+
+test_expect_success 'disambiguate with full foo.git' '
+       reset &&
+       make_bare foo &&
+       make_bare foo.git &&
+       get foo.git &&
+       check foo.git
+'
+
+test_expect_success 'we are not fooled by non-git foo directory' '
+       reset &&
+       make_bare foo.git &&
+       mkdir foo &&
+       get foo &&
+       check foo.git
+'
+
+test_expect_success 'prefer inner .git over outer bare' '
+       reset &&
+       make_tree foo &&
+       make_bare foo.git &&
+       mv foo/.git foo.git &&
+       get foo.git &&
+       check foo
+'
+
+test_done
index fdb6c253718e3d4c8acfcd1fc197607aac04b4c4..94f010be8a17b8aaa8099d1a54ad2bd4317b67dc 100755 (executable)
@@ -95,7 +95,7 @@ test_expect_success 'setup large simple rename' '
 '
 
 test_expect_success 'massive simple rename does not spam added files' '
-       unset GIT_MERGE_VERBOSITY &&
+       sane_unset GIT_MERGE_VERBOSITY &&
        git merge --no-stat simple-rename | grep -v Removing >output &&
        test 5 -gt "$(wc -l < output)"
 '
index e93ac73829f332cdbf53b05fcc611d4ea38c4c55..f8c247a7500d723e46796e7b9b76b9812e35db9b 100755 (executable)
@@ -586,6 +586,19 @@ test_expect_success \
        test_cmp expect actual
 '
 
+test_expect_success 'annotations for blobs are empty' '
+       blob=$(git hash-object -w --stdin <<-\EOF
+       Blob paragraph 1.
+
+       Blob paragraph 2.
+       EOF
+       ) &&
+       git tag tag-blob $blob &&
+       echo "tag-blob        " >expect &&
+       git tag -n1 -l tag-blob >actual &&
+       test_cmp expect actual
+'
+
 # trying to verify annotated non-signed tags:
 
 test_expect_success GPG \
@@ -1269,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 917a264eea6c6b3593e9f19caadbef715daace20..fd6410fc7149ed381d4d09120089859281e40696 100755 (executable)
@@ -99,4 +99,28 @@ test_expect_success 'git grep y<NUL>x a' "
        test_must_fail git grep -f f a
 "
 
+test_expect_success 'grep respects binary diff attribute' '
+       echo text >t &&
+       git add t &&
+       echo t:text >expect &&
+       git grep text t >actual &&
+       test_cmp expect actual &&
+       echo "t -diff" >.gitattributes &&
+       echo "Binary file t matches" >expect &&
+       git grep text t >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'grep respects not-binary diff attribute' '
+       echo binQary | q_to_nul >b &&
+       git add b &&
+       echo "Binary file b matches" >expect &&
+       git grep bin b >actual &&
+       test_cmp expect actual &&
+       echo "b diff" >.gitattributes &&
+       echo "b:binQary" >expect &&
+       git grep bin b | nul_to_q >actual &&
+       test_cmp expect actual
+'
+
 test_done
index a598dfa4777a8d459fd6e68b9a63c77d87e47c24..9e27bbf902e275671cb80310630a8bedcaab353c 100755 (executable)
@@ -683,4 +683,16 @@ test_expect_success GPG 'merge --ff-only tag' '
        test_cmp actual expect
 '
 
+test_expect_success GPG 'merge --no-edit tag should skip editor' '
+       git reset --hard c0 &&
+       git commit --allow-empty -m "A newer commit" &&
+       git tag -f -s -m "A newer commit" signed &&
+       git reset --hard c0 &&
+
+       EDITOR=false git merge --no-edit signed &&
+       git rev-parse signed^0 >expect &&
+       git rev-parse HEAD^2 >actual &&
+       test_cmp actual expect
+'
+
 test_done
index 61f36baa1f3459d68ac30e67680e096093cbe414..5783ebf3ab042d3c78633a89d842c432c96a0d4d 100755 (executable)
@@ -57,7 +57,7 @@ Merge made by the 'octopus' strategy.
  c2.c |    1 +
  c3.c |    1 +
  c4.c |    1 +
- 3 files changed, 3 insertions(+), 0 deletions(-)
+ 3 files changed, 3 insertions(+)
  create mode 100644 c2.c
  create mode 100644 c3.c
  create mode 100644 c4.c
@@ -74,7 +74,7 @@ Already up-to-date with c4
 Trying simple merge with c5
 Merge made by the 'octopus' strategy.
  c5.c |    1 +
- 1 files changed, 1 insertions(+), 0 deletions(-)
+ 1 file changed, 1 insertion(+)
  create mode 100644 c5.c
 EOF
 
@@ -89,7 +89,7 @@ Trying simple merge with c2
 Merge made by the 'octopus' strategy.
  c1.c |    1 +
  c2.c |    1 +
- 2 files changed, 2 insertions(+), 0 deletions(-)
+ 2 files changed, 2 insertions(+)
  create mode 100644 c1.c
  create mode 100644 c2.c
 EOF
index 75f4716d8cbca0c295668e181a557b186cb37432..d9ad633310a19a9ebbc2e5024875278d4631129e 100755 (executable)
@@ -47,6 +47,13 @@ test_expect_success setup '
        echo vvv >t/v &&
        mkdir t/a &&
        echo vvv >t/a/v &&
+       {
+               echo "line without leading space1"
+               echo " line with leading space1"
+               echo " line with leading space2"
+               echo " line with leading space3"
+               echo "line without leading space2"
+       } >space &&
        git add . &&
        test_tick &&
        git commit -m initial
@@ -893,4 +900,20 @@ test_expect_success 'mimic ack-grep --group' '
        test_cmp expected actual
 '
 
+cat >expected <<EOF
+space: line with leading space1
+space: line with leading space2
+space: line with leading space3
+EOF
+
+test_expect_success LIBPCRE 'grep -E "^ "' '
+       git grep -E "^ " space >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success LIBPCRE 'grep -P "^ "' '
+       git grep -P "^ " space >actual &&
+       test_cmp expected 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 b324c491c52eb0fa39d713cc5f227d134c1adb01..c3443ceb251f87806312f4e6dadb54881b52a8fe 100755 (executable)
@@ -96,8 +96,8 @@ test_expect_success 'fresh clone with svn.authors-file in config' '
                rm -r "$GIT_DIR" &&
                test x = x"$(git config svn.authorsfile)" &&
                test_config="$HOME"/.gitconfig &&
-               unset GIT_DIR &&
-               unset GIT_CONFIG &&
+               sane_unset GIT_DIR &&
+               sane_unset GIT_CONFIG &&
                git config --global \
                  svn.authorsfile "$HOME"/svn-authors &&
                test x"$HOME"/svn-authors = x"$(git config svn.authorsfile)" &&
index 518358aa64790bd65d0b46789dfdaa80285863ed..b59be9a894b2481ece7dc5ea9038bc6c88d29f15 100755 (executable)
@@ -321,7 +321,7 @@ test_expect_success 'use the same checkout for Git and CVS' '
 
        (mkdir shared &&
         cd shared &&
-        unset GIT_DIR &&
+        sane_unset GIT_DIR &&
         cvs co . &&
         git init &&
         git add " space" &&
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 80d01ea438f26c8d51e3cde548592c35db95f711..a9e04efb889c07037b6800171f9675c8afb68e18 100755 (executable)
@@ -1,3 +1,5 @@
+#!/bin/sh
+
 test_description='git-p4 p4 label tests'
 
 . ./lib-git-p4.sh
index eb8cc9523e682518464d26e9ec721508f99dfd04..f0022839c76ede4b5f135a7318b9c53ede4a944a 100755 (executable)
@@ -25,7 +25,7 @@ test_expect_success 'P4CONFIG and absolute dir clone' '
        test_when_finished cleanup_git &&
        (
                P4CONFIG=p4config && export P4CONFIG &&
-               unset P4PORT P4CLIENT &&
+               sane_unset P4PORT P4CLIENT &&
                "$GITP4" clone --verbose --dest="$git" //depot
        )
 '
@@ -37,7 +37,7 @@ test_expect_success 'P4CONFIG and relative dir clone' '
        test_when_finished cleanup_git &&
        (
                P4CONFIG=p4config && export P4CONFIG &&
-               unset P4PORT P4CLIENT &&
+               sane_unset P4PORT P4CLIENT &&
                "$GITP4" clone --verbose --dest="git" //depot
        )
 '
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 e6c292385f9492ab8a58a693e854025a11b9b045..a6ffdf39d58bcb2b4469080e828dc8a5b0fbc675 100644 (file)
@@ -59,6 +59,6 @@ int main(int ac, char **av)
        struct cache_tree *another = cache_tree();
        if (read_cache() < 0)
                die("unable to read index file");
-       cache_tree_update(another, active_cache, active_nr, 0, 1, 0);
+       cache_tree_update(another, active_cache, active_nr, WRITE_TREE_DRY_RUN);
        return dump_cache_tree(active_cache_tree, another, "");
 }
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)
 
index 76109da4bcb7abc26ed20508692051a40e8addd7..1e7184f7f00bc75872369761116141c087fa1c1c 100644 (file)
@@ -210,14 +210,7 @@ static int parse_funcname(struct userdiff_funcname *f, const char *k,
        if (git_config_string(&f->pattern, k, v) < 0)
                return -1;
        f->cflags = cflags;
-       return 1;
-}
-
-static int parse_string(const char **d, const char *k, const char *v)
-{
-       if (git_config_string(d, k, v) < 0)
-               return -1;
-       return 1;
+       return 0;
 }
 
 static int parse_tristate(int *b, const char *k, const char *v)
@@ -226,13 +219,13 @@ static int parse_tristate(int *b, const char *k, const char *v)
                *b = -1;
        else
                *b = git_config_bool(k, v);
-       return 1;
+       return 0;
 }
 
 static int parse_bool(int *b, const char *k, const char *v)
 {
        *b = git_config_bool(k, v);
-       return 1;
+       return 0;
 }
 
 int userdiff_config(const char *k, const char *v)
@@ -246,13 +239,13 @@ int userdiff_config(const char *k, const char *v)
        if ((drv = parse_driver(k, v, "binary")))
                return parse_tristate(&drv->binary, k, v);
        if ((drv = parse_driver(k, v, "command")))
-               return parse_string(&drv->external, k, v);
+               return git_config_string(&drv->external, k, v);
        if ((drv = parse_driver(k, v, "textconv")))
-               return parse_string(&drv->textconv, k, v);
+               return git_config_string(&drv->textconv, k, v);
        if ((drv = parse_driver(k, v, "cachetextconv")))
                return parse_bool(&drv->textconv_want_cache, k, v);
        if ((drv = parse_driver(k, v, "wordregex")))
-               return parse_string(&drv->word_regex, k, v);
+               return git_config_string(&drv->word_regex, k, v);
 
        return 0;
 }