Merge branch 'fk/sendmail-from-path'
authorJunio C Hamano <gitster@pobox.com>
Wed, 13 Dec 2017 21:28:56 +0000 (13:28 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 13 Dec 2017 21:28:56 +0000 (13:28 -0800)
"git send-email" tries to see if the sendmail program is available
in /usr/lib and /usr/sbin; extend the list of locations to be
checked to also include directories on $PATH.

* fk/sendmail-from-path:
git-send-email: honor $PATH for sendmail binary

93 files changed:
Documentation/RelNotes/2.15.1.txt
Documentation/RelNotes/2.15.2.txt [new file with mode: 0644]
Documentation/RelNotes/2.16.0.txt
Documentation/config.txt
Documentation/git-branch.txt
Documentation/git-checkout.txt
Documentation/git-config.txt
Documentation/git-log.txt
Documentation/git-notes.txt
Documentation/git-prune.txt
Documentation/git-reflog.txt
Documentation/git-stash.txt
Documentation/git.txt
Documentation/githooks.txt
Documentation/gitrepository-layout.txt
Documentation/technical/http-protocol.txt
Documentation/technical/pack-protocol.txt
Makefile
branch.c
branch.h
builtin/am.c
builtin/branch.c
builtin/checkout.c
builtin/config.c
builtin/diff.c
builtin/log.c
builtin/notes.c
builtin/prune.c
builtin/pull.c
builtin/receive-pack.c
builtin/reflog.c
builtin/submodule--helper.c
cache.h
config.c
config.h
connect.c
contrib/completion/git-completion.bash
daemon.c
diff-lib.c
fetch-pack.c
generate-cmdlist.sh
git-gui/Makefile
git-stash.sh
git.c
grep.c
hash.h
http.c
log-tree.c
log-tree.h
merge-recursive.c
notes-merge.c
perl/Git/Packet.pm
pkt-line.c
pkt-line.h
pretty.c
protocol.c [new file with mode: 0644]
protocol.h [new file with mode: 0644]
refs.c
refs.h
repository.c
repository.h
revision.c
sequencer.c
setup.c
sha1_file.c
sha1_name.c
submodule.c
t/README
t/helper/test-date.c
t/interop/i5700-protocol-transition.sh [new file with mode: 0755]
t/lib-httpd/apache.conf
t/lib-submodule-update.sh
t/perf/aggregate.perl
t/perf/lib-pack.sh [new file with mode: 0644]
t/perf/p5550-fetch-tags.sh
t/perf/p5551-fetch-rescan.sh [new file with mode: 0755]
t/perf/perf-lib.sh
t/perf/run
t/t0021/rot13-filter.pl
t/t1300-repo-config.sh
t/t1430-bad-ref-name.sh
t/t3903-stash.sh
t/t4051-diff-function-context.sh
t/t4051/hello.c
t/t4202-log.sh
t/t5601-clone.sh
t/t5603-clone-dirname.sh
t/t5700-protocol-v1.sh [new file with mode: 0755]
t/t7006-pager.sh
t/t7810-grep.sh
t/test-lib.sh
upload-pack.c
xdiff/xemit.c
index a6ae4c1aeb7a2ed4932986e97402545e3608a359..ec06704e637d538d510e8f3a110cfacb3e79d1e4 100644 (file)
@@ -82,4 +82,7 @@ Fixes since v2.15
  * An ancient bug in "git apply --ignore-space-change" codepath has
    been fixed.
 
+ * There was a recent semantic mismerge in the codepath to write out a
+   section of a configuration section, which has been corrected.
+
 Also contains various documentation updates and code clean-ups.
diff --git a/Documentation/RelNotes/2.15.2.txt b/Documentation/RelNotes/2.15.2.txt
new file mode 100644 (file)
index 0000000..9f7e28f
--- /dev/null
@@ -0,0 +1,47 @@
+Git v2.15.2 Release Notes
+=========================
+
+Fixes since v2.15.1
+-------------------
+
+ * Recent update to the refs infrastructure implementation started
+   rewriting packed-refs file more often than before; this has been
+   optimized again for most trivial cases.
+
+ * The SubmittingPatches document has been converted to produce an
+   HTML version via AsciiDoc/Asciidoctor.
+
+ * Contrary to the documentation, "git pull -4/-6 other-args" did not
+   ask the underlying "git fetch" to go over IPv4/IPv6, which has been
+   corrected.
+
+ * When "git rebase" prepared an mailbox of changes and fed it to "git
+   am" to replay them, it was confused when a stray "From " happened
+   to be in the log message of one of the replayed changes.  This has
+   been corrected.
+
+ * Command line completion (in contrib/) has been taught about the
+   "--copy" option of "git branch".
+
+ * "git apply --inaccurate-eof" when used with "--ignore-space-change"
+   triggered an internal sanity check, which has been fixed.
+
+ * The sequencer machinery (used by "git cherry-pick A..B", and "git
+   rebase -i", among other things) would have lost a commit if stopped
+   due to an unlockable index file, which has been fixed.
+
+ * The three-way merge performed by "git cherry-pick" was confused
+   when a new submodule was added in the meantime, which has been
+   fixed (or "papered over").
+
+ * "git notes" sent its error message to its standard output stream,
+   which was corrected.
+
+ * A few scripts (both in production and tests) incorrectly redirected
+   their error output.  These have been corrected.
+
+ * Clarify and enhance documentation for "merge-base --fork-point", as
+   it was clear what it computed but not why/what for.
+
+
+Also contains various documentation updates and code clean-ups.
index 3f2909498f9a393a2d312ad766187b8f0ec20c2a..431bd5e34ace656c3462e5cb4cf6bd3b77adfece 100644 (file)
@@ -61,7 +61,6 @@ UI, Workflows & Features
 
  * The SubmittingPatches document has been converted to produce an
    HTML version via AsciiDoc/Asciidoctor.
-   (merge 049e64aa50 bc/submitting-patches-in-asciidoc later to maint).
 
  * We learned to talk to watchman to speed up "git status" and other
    operations that need to see which paths have been modified.
@@ -77,6 +76,26 @@ UI, Workflows & Features
    that you are correcting the end-of-line convention and other
    "convert_to_git()" glitches in the in-repository data.
 
+ * "git branch" and "git checkout -b" are now forbidden from creating
+   a branch whose name is "HEAD".
+
+ * "git branch --list" learned to show its output through the pager by
+   default when the output is going to a terminal, which is controlled
+   by the pager.branch configuration variable.  This is similar to a
+   recent change to "git tag --list".
+
+ * "git grep -W", "git diff -W" and their friends learned a heuristic
+   to extend a pre-context beyond the line that matches the "function
+   pattern" (aka "diff.*.xfuncname") to include a comment block, if
+   exists, that immediately precedes it.
+
+ * "git config --expiry-date gc.reflogexpire" can read "2.weeks" from
+   the configuration and report it as a timestamp, just like "--int"
+   would read "1k" and report 1024, to help consumption by scripts.
+
+ * The shell completion (in contrib/) learned that "git pull" can take
+   the "--autostash" option.
+
 
 Performance, Internal Implementation, Development Support etc.
 
@@ -117,6 +136,13 @@ Performance, Internal Implementation, Development Support etc.
  * Drop (perhaps overly cautious) sanity check before using the index
    read from the filesystem at runtime.
 
+ * The build procedure has been taught to avoid some unnecessary
+   instability in the build products.
+
+ * A new mechanism to upgrade the wire protocol in place is proposed
+   and demonstrated that it works with the older versions of Git
+   without harming them.
+
 Also contains various documentation updates and code clean-ups.
 
 
@@ -193,7 +219,6 @@ Fixes since v2.15
  * Recent update to the refs infrastructure implementation started
    rewriting packed-refs file more often than before; this has been
    optimized again for most trivial cases.
-   (merge 7c6bd25c7d mh/avoid-rewriting-packed-refs later to maint).
 
  * Some error messages did not quote filenames shown in it, which have
    been fixed.
@@ -213,53 +238,72 @@ Fixes since v2.15
 
  * Clarify and enhance documentation for "merge-base --fork-point", as
    it was clear what it computed but not why/what for.
-   (merge 6d1700b8af jc/merge-base-fork-point-doc later to maint).
 
  * A few scripts (both in production and tests) incorrectly redirected
    their error output.  These have been corrected.
-   (merge eadf1c8f45 tz/redirect-fix later to maint).
 
  * "git notes" sent its error message to its standard output stream,
    which was corrected.
-   (merge 89b9e31dd5 tz/notes-error-to-stderr later to maint).
 
  * The three-way merge performed by "git cherry-pick" was confused
    when a new submodule was added in the meantime, which has been
    fixed (or "papered over").
-   (merge c641ca6707 sb/test-cherry-pick-submodule-getting-in-a-way later to maint).
 
  * The sequencer machinery (used by "git cherry-pick A..B", and "git
    rebase -i", among other things) would have lost a commit if stopped
    due to an unlockable index file, which has been fixed.
-   (merge bd58886775 pw/sequencer-recover-from-unlockable-index later to maint).
 
  * "git apply --inaccurate-eof" when used with "--ignore-space-change"
    triggered an internal sanity check, which has been fixed.
-   (merge 4855de1233 rs/apply-inaccurate-eof-with-incomplete-line later to maint).
 
  * Command line completion (in contrib/) has been taught about the
    "--copy" option of "git branch".
-   (merge 41ca0f773e tz/complete-branch-copy later to maint).
 
  * When "git rebase" prepared an mailbox of changes and fed it to "git
    am" to replay them, it was confused when a stray "From " happened
    to be in the log message of one of the replayed changes.  This has
    been corrected.
-   (merge ae3b2b04bb ew/rebase-mboxrd later to maint).
 
  * There was a recent semantic mismerge in the codepath to write out a
    section of a configuration section, which has been corrected.
-   (merge 782c030ea2 rs/config-write-section-fix later to maint).
 
  * Mentions of "git-rebase" and "git-am" (dashed form) still remained
    in end-user visible strings emitted by the "git rebase" command;
    they have been corrected.
-   (merge 82cb775c06 ks/rebase-no-git-foo later to maint).
 
  * Contrary to the documentation, "git pull -4/-6 other-args" did not
    ask the underlying "git fetch" to go over IPv4/IPv6, which has been
    corrected.
-   (merge ffb4568afe sw/pull-ipv46-passthru later to maint).
+
+ * "git checkout --recursive" may overwrite and rewind the history of
+   the branch that happens to be checked out in submodule
+   repositories, which might not be desirable.  Detach the HEAD but
+   still allow the recursive checkout to succeed in such a case.
+   (merge 57f22bf997 sb/submodule-recursive-checkout-detach-head later to maint).
+
+ * "git branch --set-upstream" has been deprecated and (sort of)
+   removed, as "--set-upstream-to" is the preferred one these days.
+   The documentation still had "--set-upstream" listed on its
+   synopsys section, which has been corrected.
+   (merge a060f3d3d8 tz/branch-doc-remove-set-upstream later to maint).
+
+ * Internaly we use 0{40} as a placeholder object name to signal the
+   codepath that there is no such object (e.g. the fast-forward check
+   while "git fetch" stores a new remote-tracking ref says "we know
+   there is no 'old' thing pointed at by the ref, as we are creating
+   it anew" by passing 0{40} for the 'old' side), and expect that a
+   codepath to locate an in-core object to return NULL as a sign that
+   the object does not exist.  A look-up for an object that does not
+   exist however is quite costly with a repository with large number
+   of packfiles.  This access pattern has been optimized.
+   (merge 87b5e236a1 jk/fewer-pack-rescan later to maint).
+
+ * In addition to "git stash -m message", the command learned to
+   accept "git stash -mmessage" form.
+   (merge 5675473fcb ph/stash-save-m-option-fix later to maint).
 
  * Other minor doc, test and build updates and code cleanups.
-   (merge c5e3bc6ec4 sd/branch-copy later to maint).
+   (merge 1a1fc2d5b5 rd/man-prune-progress later to maint).
+   (merge 0ba014035a rd/man-reflog-add-n later to maint).
+   (merge e54b63359f rd/doc-notes-prune-fix later to maint).
+   (merge ff4c9b413a sp/doc-info-attributes later to maint).
index 531649cb40eab1749928bfacce8fa2ec0f24d065..c1598ee7039fca0439a18b31f186052bbb40bbce 100644 (file)
@@ -2108,15 +2108,40 @@ matched against are those given directly to Git commands.  This means any URLs
 visited as a result of a redirection do not participate in matching.
 
 ssh.variant::
-       Depending on the value of the environment variables `GIT_SSH` or
-       `GIT_SSH_COMMAND`, or the config setting `core.sshCommand`, Git
-       auto-detects whether to adjust its command-line parameters for use
-       with plink or tortoiseplink, as opposed to the default (OpenSSH).
+       By default, Git determines the command line arguments to use
+       based on the basename of the configured SSH command (configured
+       using the environment variable `GIT_SSH` or `GIT_SSH_COMMAND` or
+       the config setting `core.sshCommand`). If the basename is
+       unrecognized, Git will attempt to detect support of OpenSSH
+       options by first invoking the configured SSH command with the
+       `-G` (print configuration) option and will subsequently use
+       OpenSSH options (if that is successful) or no options besides
+       the host and remote command (if it fails).
++
+The config variable `ssh.variant` can be set to override this detection.
+Valid values are `ssh` (to use OpenSSH options), `plink`, `putty`,
+`tortoiseplink`, `simple` (no options except the host and remote command).
+The default auto-detection can be explicitly requested using the value
+`auto`.  Any other value is treated as `ssh`.  This setting can also be
+overridden via the environment variable `GIT_SSH_VARIANT`.
++
+The current command-line parameters used for each variant are as
+follows:
 +
-The config variable `ssh.variant` can be set to override this auto-detection;
-valid values are `ssh`, `plink`, `putty` or `tortoiseplink`. Any other value
-will be treated as normal ssh. This setting can be overridden via the
-environment variable `GIT_SSH_VARIANT`.
+--
+
+* `ssh` - [-p port] [-4] [-6] [-o option] [username@]host command
+
+* `simple` - [username@]host command
+
+* `plink` or `putty` - [-P port] [-4] [-6] [username@]host command
+
+* `tortoiseplink` - [-P port] [-4] [-6] -batch [username@]host command
+
+--
++
+Except for the `simple` variant, command-line parameters are likely to
+change as git gains new features.
 
 i18n.commitEncoding::
        Character encoding the commit messages are stored in; Git itself
@@ -2544,6 +2569,23 @@ The protocol names currently used by git are:
     `hg` to allow the `git-remote-hg` helper)
 --
 
+protocol.version::
+       Experimental. If set, clients will attempt to communicate with a
+       server using the specified protocol version.  If unset, no
+       attempt will be made by the client to communicate using a
+       particular protocol version, this results in protocol version 0
+       being used.
+       Supported versions:
++
+--
+
+* `0` - the original wire protocol.
+
+* `1` - the original wire protocol with the addition of a version string
+  in the initial response from the server.
+
+--
+
 pull.ff::
        By default, Git does not create an extra merge commit when merging
        a commit that is a descendant of the current commit. Instead, the
index d6587c5e96f4dbcdb4176d9dd5f6a992c0e83f3b..b3084c99c1cabdccc690e4cec5071b22774bf6c8 100644 (file)
@@ -14,7 +14,7 @@ SYNOPSIS
        [(--merged | --no-merged) [<commit>]]
        [--contains [<commit]] [--no-contains [<commit>]]
        [--points-at <object>] [--format=<format>] [<pattern>...]
-'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
+'git branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>]
 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
 'git branch' --unset-upstream [<branchname>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
@@ -86,7 +86,7 @@ OPTIONS
 --delete::
        Delete a branch. The branch must be fully merged in its
        upstream branch, or in `HEAD` if no upstream was set with
-       `--track` or `--set-upstream`.
+       `--track` or `--set-upstream-to`.
 
 -D::
        Shortcut for `--delete --force`.
@@ -281,6 +281,12 @@ start-point is either a local or remote-tracking branch.
        and the object it points at.  The format is the same as
        that of linkgit:git-for-each-ref[1].
 
+CONFIGURATION
+-------------
+`pager.branch` is only respected when listing branches, i.e., when
+`--list` is used or implied. The default is to use a pager.
+See linkgit:git-config[1].
+
 Examples
 --------
 
index e108b0f74bb6dc4d9f3fc27e7c3fe571b19a9d2f..bfa64ca5c98c54b9b9465d45669f45ab887605cf 100644 (file)
@@ -264,6 +264,8 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
        local modifications in a submodule would be overwritten the checkout
        will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
        is used, the work trees of submodules will not be updated.
+       Just like linkgit:git-submodule[1], this will detach the
+       submodules HEAD.
 
 <branch>::
        Branch to checkout; if it refers to a branch (i.e., a name that,
index 4edd09fc6b0742042f3236598d8ecb793773d8ab..14da5fc157ee0ea7467e7b9bd9d33d465c1f7a3f 100644 (file)
@@ -180,6 +180,11 @@ See also <<FILES>>.
        value (but you can use `git config section.variable ~/`
        from the command line to let your shell do the expansion).
 
+--expiry-date::
+       `git config` will ensure that the output is converted from
+       a fixed or relative date-string to a timestamp. This option
+       has no effect when setting the value.
+
 -z::
 --null::
        For all options that output values and/or keys, always
index 32246fdb007e9b25f414cff092b9bf0426c55d15..5437f8b0f0e6699eca662879290d47df85387f0f 100644 (file)
@@ -38,6 +38,13 @@ OPTIONS
        are shown as if 'short' were given, otherwise no ref names are
        shown. The default option is 'short'.
 
+--decorate-refs=<pattern>::
+--decorate-refs-exclude=<pattern>::
+       If no `--decorate-refs` is given, pretend as if all refs were
+       included.  For each candidate, do not use it for decoration if it
+       matches any patterns given to `--decorate-refs-exclude` or if it
+       doesn't match any of the patterns given to `--decorate-refs`.
+
 --source::
        Print out the ref name given on the command line by which each
        commit was reached.
index 43677297f3b85814c18c972a159b81c8076bc445..e8dec1b3c89a66dc2f9e78737fa7d243bee85d37 100644 (file)
@@ -18,7 +18,7 @@ SYNOPSIS
 'git notes' merge --commit [-v | -q]
 'git notes' merge --abort [-v | -q]
 'git notes' remove [--ignore-missing] [--stdin] [<object>...]
-'git notes' prune [-n | -v]
+'git notes' prune [-n] [-v]
 'git notes' get-ref
 
 
index 7a493c80f776092265abd4fb2575639e683bcf5c..a37c0af9313e8e47806c89a584c70c09517244d1 100644 (file)
@@ -9,7 +9,7 @@ git-prune - Prune all unreachable objects from the object database
 SYNOPSIS
 --------
 [verse]
-'git prune' [-n] [-v] [--expire <expire>] [--] [<head>...]
+'git prune' [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]
 
 DESCRIPTION
 -----------
@@ -42,12 +42,15 @@ OPTIONS
 --verbose::
        Report all removed objects.
 
-\--::
-       Do not interpret any more arguments as options.
+--progress::
+       Show progress.
 
 --expire <time>::
        Only expire loose objects older than <time>.
 
+\--::
+       Do not interpret any more arguments as options.
+
 <head>...::
        In addition to objects
        reachable from any of our references, keep objects
index 44c736f1a8e581bc072d5de3b719284f7376ec3b..472a6808cd60899f4554e998b9fb8e4f3103aac2 100644 (file)
@@ -20,9 +20,9 @@ depending on the subcommand:
 'git reflog' ['show'] [log-options] [<ref>]
 'git reflog expire' [--expire=<time>] [--expire-unreachable=<time>]
        [--rewrite] [--updateref] [--stale-fix]
-       [--dry-run] [--verbose] [--all | <refs>...]
+       [--dry-run | -n] [--verbose] [--all | <refs>...]
 'git reflog delete' [--rewrite] [--updateref]
-       [--dry-run] [--verbose] ref@\{specifier\}...
+       [--dry-run | -n] [--verbose] ref@\{specifier\}...
 'git reflog exists' <ref>
 
 Reference logs, or "reflogs", record when the tips of branches and
index 8be661007d03eaa55ee020883161b4d38c31e8cf..056dfb86612d0c5be0d01f16ac6b126fac80b92c 100644 (file)
@@ -175,14 +175,14 @@ create::
        return its object name, without storing it anywhere in the ref
        namespace.
        This is intended to be useful for scripts.  It is probably not
-       the command you want to use; see "save" above.
+       the command you want to use; see "push" above.
 
 store::
 
        Store a given stash created via 'git stash create' (which is a
        dangling merge commit) in the stash ref, updating the stash
        reflog.  This is intended to be useful for scripts.  It is
-       probably not the command you want to use; see "save" above.
+       probably not the command you want to use; see "push" above.
 
 DISCUSSION
 ----------
index 483a1f35475ea435e5c93121e67ad8085855bfc3..e75db104e3a33dde93a98cc09de97f4d4f0bc5eb 100644 (file)
@@ -522,11 +522,10 @@ other
        If either of these environment variables is set then 'git fetch'
        and 'git push' will use the specified command instead of 'ssh'
        when they need to connect to a remote system.
-       The command will be given exactly two or four arguments: the
-       'username@host' (or just 'host') from the URL and the shell
-       command to execute on that remote system, optionally preceded by
-       `-p` (literally) and the 'port' from the URL when it specifies
-       something other than the default SSH port.
+       The command-line parameters passed to the configured command are
+       determined by the ssh variant.  See `ssh.variant` option in
+       linkgit:git-config[1] for details.
+
 +
 `$GIT_SSH_COMMAND` takes precedence over `$GIT_SSH`, and is interpreted
 by the shell, which allows additional arguments to be included.
@@ -705,6 +704,12 @@ of clones and fetches.
        which feed potentially-untrusted URLS to git commands.  See
        linkgit:git-config[1] for more details.
 
+`GIT_PROTOCOL`::
+       For internal use only.  Used in handshaking the wire protocol.
+       Contains a colon ':' separated list of keys with optional values
+       'key[=value]'.  Presence of unknown keys and values must be
+       ignored.
+
 `GIT_OPTIONAL_LOCKS`::
        If set to `0`, Git will complete any requested operation without
        performing any optional sub-operations that require taking a lock.
index 0bb0042d8c2ee761743a93346dc5bea9312d3d7e..b63f2ea860be34fb49de77a2de5b68336e4a49bd 100644 (file)
@@ -223,8 +223,8 @@ to the user by writing to standard error.
 pre-receive
 ~~~~~~~~~~~
 
-This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git push' is done on a local repository.
+This hook is invoked by 'git-receive-pack' when it reacts to
+'git push' and updates reference(s) in its repository.
 Just before starting to update refs on the remote repository, the
 pre-receive hook is invoked.  Its exit status determines the success
 or failure of the update.
@@ -264,8 +264,8 @@ linkgit:git-receive-pack[1] for some caveats.
 update
 ~~~~~~
 
-This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git push' is done on a local repository.
+This hook is invoked by 'git-receive-pack' when it reacts to
+'git push' and updates reference(s) in its repository.
 Just before updating the ref on the remote repository, the update hook
 is invoked.  Its exit status determines the success or failure of
 the ref update.
@@ -309,8 +309,8 @@ unannotated tags to be pushed.
 post-receive
 ~~~~~~~~~~~~
 
-This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git push' is done on a local repository.
+This hook is invoked by 'git-receive-pack' when it reacts to
+'git push' and updates reference(s) in its repository.
 It executes on the remote repository once after all the refs have
 been updated.
 
@@ -348,8 +348,8 @@ will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.
 post-update
 ~~~~~~~~~~~
 
-This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git push' is done on a local repository.
+This hook is invoked by 'git-receive-pack' when it reacts to
+'git push' and updates reference(s) in its repository.
 It executes on the remote repository once after all the refs have
 been updated.
 
@@ -379,8 +379,8 @@ for the user.
 push-to-checkout
 ~~~~~~~~~~~~~~~~
 
-This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git push' is done on a local repository, when
+This hook is invoked by 'git-receive-pack' when it reacts to
+'git push' and updates reference(s) in its repository, and when
 the push tries to update the branch that is currently checked out
 and the `receive.denyCurrentBranch` configuration variable is set to
 `updateInstead`.  Such a push by default is refused if the working
index adf9554ad2f84cb69a6b0a940e4d3e0e336df9ff..c60bcad44aa581b2449a7a638b6487c1c73e8c23 100644 (file)
@@ -208,6 +208,10 @@ info/exclude::
        'git clean' look at it but the core Git commands do not look
        at it.  See also: linkgit:gitignore[5].
 
+info/attributes::
+       Defines which attributes to assign to a path, similar to per-directory
+       `.gitattributes` files.   See also: linkgit:gitattributes[5].
+
 info/sparse-checkout::
        This file stores sparse checkout patterns.
        See also: linkgit:git-read-tree[1].
index 1c561bdd9204fe221512064f765bea9eb7ec5945..a0e45f2889e6e52db71f88d7368bbb2efc02616a 100644 (file)
@@ -219,6 +219,10 @@ smart server reply:
    S: 003c2cb58b79488a98d2721cea644875a8dd0026b115 refs/tags/v1.0\n
    S: 003fa3c2e2402b99163d1d59756e5f207ae21cccba4c refs/tags/v1.0^{}\n
 
+The client may send Extra Parameters (see
+Documentation/technical/pack-protocol.txt) as a colon-separated string
+in the Git-Protocol HTTP header.
+
 Dumb Server Response
 ^^^^^^^^^^^^^^^^^^^^
 Dumb servers MUST respond with the dumb server reply format.
@@ -269,7 +273,11 @@ the C locale ordering.  The stream SHOULD include the default ref
 named `HEAD` as the first ref.  The stream MUST include capability
 declarations behind a NUL on the first ref.
 
+The returned response contains "version 1" if "version=1" was sent as an
+Extra Parameter.
+
   smart_reply     =  PKT-LINE("# service=$servicename" LF)
+                    *1("version 1")
                     ref_list
                     "0000"
   ref_list        =  empty_list / non_empty_list
index ed1eae8b83a651d34f9ce2488f58d02a763fbfd4..cd31edc91ea1989adc7f2321327c0a97685fe62f 100644 (file)
@@ -39,6 +39,19 @@ communicates with that invoked process over the SSH connection.
 The file:// transport runs the 'upload-pack' or 'receive-pack'
 process locally and communicates with it over a pipe.
 
+Extra Parameters
+----------------
+
+The protocol provides a mechanism in which clients can send additional
+information in its first message to the server. These are called "Extra
+Parameters", and are supported by the Git, SSH, and HTTP protocols.
+
+Each Extra Parameter takes the form of `<key>=<value>` or `<key>`.
+
+Servers that receive any such Extra Parameters MUST ignore all
+unrecognized keys. Currently, the only Extra Parameter recognized is
+"version=1".
+
 Git Transport
 -------------
 
@@ -46,18 +59,25 @@ The Git transport starts off by sending the command and repository
 on the wire using the pkt-line format, followed by a NUL byte and a
 hostname parameter, terminated by a NUL byte.
 
-   0032git-upload-pack /project.git\0host=myserver.com\0
+   0033git-upload-pack /project.git\0host=myserver.com\0
+
+The transport may send Extra Parameters by adding an additional NUL
+byte, and then adding one or more NUL-terminated strings:
+
+   003egit-upload-pack /project.git\0host=myserver.com\0\0version=1\0
 
 --
-   git-proto-request = request-command SP pathname NUL [ host-parameter NUL ]
+   git-proto-request = request-command SP pathname NUL
+                      [ host-parameter NUL ] [ NUL extra-parameters ]
    request-command   = "git-upload-pack" / "git-receive-pack" /
                       "git-upload-archive"   ; case sensitive
    pathname          = *( %x01-ff ) ; exclude NUL
    host-parameter    = "host=" hostname [ ":" port ]
+   extra-parameters  = 1*extra-parameter
+   extra-parameter   = 1*( %x01-ff ) NUL
 --
 
-Only host-parameter is allowed in the git-proto-request. Clients
-MUST NOT attempt to send additional parameters. It is used for the
+host-parameter is used for the
 git-daemon name based virtual hosting.  See --interpolated-path
 option to git daemon, with the %H/%CH format characters.
 
@@ -117,6 +137,12 @@ we execute it without the leading '/'.
                     v
    ssh user@example.com "git-upload-pack '~alice/project.git'"
 
+Depending on the value of the `protocol.version` configuration variable,
+Git may attempt to send Extra Parameters as a colon-separated string in
+the GIT_PROTOCOL environment variable. This is done only if
+the `ssh.variant` configuration variable indicates that the ssh command
+supports passing environment variables as an argument.
+
 A few things to remember here:
 
 - The "command name" is spelled with dash (e.g. git-upload-pack), but
@@ -137,11 +163,13 @@ Reference Discovery
 -------------------
 
 When the client initially connects the server will immediately respond
-with a listing of each reference it has (all branches and tags) along
+with a version number (if "version=1" is sent as an Extra Parameter),
+and a listing of each reference it has (all branches and tags) along
 with the object name that each reference currently points to.
 
-   $ echo -e -n "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" |
+   $ echo -e -n "0044git-upload-pack /schacon/gitbook.git\0host=example.com\0\0version=1\0" |
       nc -v example.com 9418
+   000aversion 1
    00887217a7c7e582c46cec22a130adf4b9d7d950fba0 HEAD\0multi_ack thin-pack
                side-band side-band-64k ofs-delta shallow no-progress include-tag
    00441d3fcd5ced445d1abc402225c0b8a1299641f497 refs/heads/integration
@@ -165,7 +193,8 @@ immediately after the ref itself, if presented. A conforming server
 MUST peel the ref if it's an annotated tag.
 
 ----
-  advertised-refs  =  (no-refs / list-of-refs)
+  advertised-refs  =  *1("version 1")
+                     (no-refs / list-of-refs)
                      *shallow
                      flush-pkt
 
index e53750ca01afbd7d777e62aa221f6861f66b0703..fef9c8d2725d6806d9ef4516de64c6d68bd7a7fe 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -849,6 +849,7 @@ LIB_OBJS += pretty.o
 LIB_OBJS += prio-queue.o
 LIB_OBJS += progress.o
 LIB_OBJS += prompt.o
+LIB_OBJS += protocol.o
 LIB_OBJS += quote.o
 LIB_OBJS += reachable.o
 LIB_OBJS += read-cache.o
index 62f7b0d8c2d6a89fde5abffb6c836d0ce7706ac4..fe1e1c3676b26a4f35feaf8137700b25916ad5ee 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -178,24 +178,40 @@ int read_branch_desc(struct strbuf *buf, const char *branch_name)
        return 0;
 }
 
-int validate_new_branchname(const char *name, struct strbuf *ref,
-                           int force, int attr_only)
+/*
+ * Check if 'name' can be a valid name for a branch; die otherwise.
+ * Return 1 if the named branch already exists; return 0 otherwise.
+ * Fill ref with the full refname for the branch.
+ */
+int validate_branchname(const char *name, struct strbuf *ref)
 {
        if (strbuf_check_branch_ref(ref, name))
                die(_("'%s' is not a valid branch name."), name);
 
-       if (!ref_exists(ref->buf))
+       return ref_exists(ref->buf);
+}
+
+/*
+ * Check if a branch 'name' can be created as a new branch; die otherwise.
+ * 'force' can be used when it is OK for the named branch already exists.
+ * Return 1 if the named branch already exists; return 0 otherwise.
+ * Fill ref with the full refname for the branch.
+ */
+int validate_new_branchname(const char *name, struct strbuf *ref, int force)
+{
+       const char *head;
+
+       if (!validate_branchname(name, ref))
                return 0;
-       else if (!force && !attr_only)
-               die(_("A branch named '%s' already exists."), ref->buf + strlen("refs/heads/"));
 
-       if (!attr_only) {
-               const char *head;
+       if (!force)
+               die(_("A branch named '%s' already exists."),
+                   ref->buf + strlen("refs/heads/"));
+
+       head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
+       if (!is_bare_repository() && head && !strcmp(head, ref->buf))
+               die(_("Cannot force update the current branch."));
 
-               head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
-               if (!is_bare_repository() && head && !strcmp(head, ref->buf))
-                       die(_("Cannot force update the current branch."));
-       }
        return 1;
 }
 
@@ -242,9 +258,9 @@ void create_branch(const char *name, const char *start_name,
        if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)
                explicit_tracking = 1;
 
-       if (validate_new_branchname(name, &ref, force,
-                                   track == BRANCH_TRACK_OVERRIDE ||
-                                   clobber_head)) {
+       if ((track == BRANCH_TRACK_OVERRIDE || clobber_head)
+           ? validate_branchname(name, &ref)
+           : validate_new_branchname(name, &ref, force)) {
                if (!force)
                        dont_change_ref = 1;
                else
index b07788558c0e76a701b571f73c0462dcf2abcc63..be5e5d13083a98e57e834d81afe5450f11202130 100644 (file)
--- a/branch.h
+++ b/branch.h
@@ -23,22 +23,19 @@ void create_branch(const char *name, const char *start_name,
                   int clobber_head, int quiet, enum branch_track track);
 
 /*
- * Validates that the requested branch may be created, returning the
- * interpreted ref in ref, force indicates whether (non-head) branches
- * may be overwritten. A non-zero return value indicates that the force
- * parameter was non-zero and the branch already exists.
- *
- * Contrary to all of the above, when attr_only is 1, the caller is
- * not interested in verifying if it is Ok to update the named
- * branch to point at a potentially different commit. It is merely
- * asking if it is OK to change some attribute for the named branch
- * (e.g. tracking upstream).
- *
- * NEEDSWORK: This needs to be split into two separate functions in the
- * longer run for sanity.
- *
+ * Check if 'name' can be a valid name for a branch; die otherwise.
+ * Return 1 if the named branch already exists; return 0 otherwise.
+ * Fill ref with the full refname for the branch.
+ */
+extern int validate_branchname(const char *name, struct strbuf *ref);
+
+/*
+ * Check if a branch 'name' can be created as a new branch; die otherwise.
+ * 'force' can be used when it is OK for the named branch already exists.
+ * Return 1 if the named branch already exists; return 0 otherwise.
+ * Fill ref with the full refname for the branch.
  */
-int validate_new_branchname(const char *name, struct strbuf *ref, int force, int attr_only);
+extern int validate_new_branchname(const char *name, struct strbuf *ref, int force);
 
 /*
  * Remove information about the state of working on the current
index 02853b3e05bfef638963dbfe91a7eccf3d40b9e6..3d98e52085711abc6fa5ff9fd16d2828156df375 100644 (file)
@@ -1433,7 +1433,7 @@ static void write_index_patch(const struct am_state *state)
        if (!get_oid_tree("HEAD", &head))
                tree = lookup_tree(&head);
        else
-               tree = lookup_tree(&empty_tree_oid);
+               tree = lookup_tree(the_hash_algo->empty_tree);
 
        fp = xfopen(am_path(state, "patch"), "w");
        init_revisions(&rev_info, NULL);
index 33fd5fcfd10be978f4d8ccdb6869eb05e86eb956..af95ad2192004ad3a712c15f1e50356daadb7c86 100644 (file)
@@ -463,7 +463,6 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
        struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
        struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
        int recovery = 0;
-       int clobber_head_ok;
 
        if (!oldname) {
                if (copy)
@@ -487,9 +486,10 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
         * A command like "git branch -M currentbranch currentbranch" cannot
         * cause the worktree to become inconsistent with HEAD, so allow it.
         */
-       clobber_head_ok = !strcmp(oldname, newname);
-
-       validate_new_branchname(newname, &newref, force, clobber_head_ok);
+       if (!strcmp(oldname, newname))
+               validate_branchname(newname, &newref);
+       else
+               validate_new_branchname(newname, &newref, force);
 
        reject_rebase_or_bisect_branch(oldref.buf);
 
@@ -675,6 +675,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                copy *= 2;
        }
 
+       if (list)
+               setup_auto_pager("branch", 1);
+
        if (delete) {
                if (!argc)
                        die(_("branch name required"));
@@ -793,9 +796,6 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        } else if (argc > 0 && argc <= 2) {
                struct branch *branch = branch_get(argv[0]);
 
-               if (!strcmp(argv[0], "HEAD"))
-                       die(_("it does not make sense to create 'HEAD' manually"));
-
                if (!branch)
                        die(_("no such branch '%s'"), argv[0]);
 
index 7d8bcc3833512bc262bd0a8d86db9169d991b320..e1e157d205a06ffad39a9685cab55b16c69d339c 100644 (file)
@@ -514,7 +514,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
                }
                tree = parse_tree_indirect(old->commit ?
                                           &old->commit->object.oid :
-                                          &empty_tree_oid);
+                                          the_hash_algo->empty_tree);
                init_tree_desc(&trees[0], tree->buffer, tree->size);
                tree = parse_tree_indirect(&new->commit->object.oid);
                init_tree_desc(&trees[1], tree->buffer, tree->size);
@@ -1287,11 +1287,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        if (opts.new_branch) {
                struct strbuf buf = STRBUF_INIT;
 
-               opts.branch_exists =
-                       validate_new_branchname(opts.new_branch, &buf,
-                                               !!opts.new_branch_force,
-                                               !!opts.new_branch_force);
-
+               if (opts.new_branch_force)
+                       opts.branch_exists = validate_branchname(opts.new_branch, &buf);
+               else
+                       opts.branch_exists =
+                               validate_new_branchname(opts.new_branch, &buf, 0);
                strbuf_release(&buf);
        }
 
index d13daeeb55927758ceec816f39412123d2ce5846..ab5f95476e6c726798fd84b4a6300577bf0fafba 100644 (file)
@@ -52,6 +52,7 @@ static int show_origin;
 #define TYPE_INT (1<<1)
 #define TYPE_BOOL_OR_INT (1<<2)
 #define TYPE_PATH (1<<3)
+#define TYPE_EXPIRY_DATE (1<<4)
 
 static struct option builtin_config_options[] = {
        OPT_GROUP(N_("Config file location")),
@@ -80,6 +81,7 @@ static struct option builtin_config_options[] = {
        OPT_BIT(0, "int", &types, N_("value is decimal number"), TYPE_INT),
        OPT_BIT(0, "bool-or-int", &types, N_("value is --bool or --int"), TYPE_BOOL_OR_INT),
        OPT_BIT(0, "path", &types, N_("value is a path (file or directory name)"), TYPE_PATH),
+       OPT_BIT(0, "expiry-date", &types, N_("value is an expiry date"), TYPE_EXPIRY_DATE),
        OPT_GROUP(N_("Other")),
        OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")),
        OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")),
@@ -159,6 +161,11 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value
                                return -1;
                        strbuf_addstr(buf, v);
                        free((char *)v);
+               } else if (types == TYPE_EXPIRY_DATE) {
+                       timestamp_t t;
+                       if (git_config_expiry_date(&t, key_, value_) < 0)
+                               return -1;
+                       strbuf_addf(buf, "%"PRItime, t);
                } else if (value_) {
                        strbuf_addstr(buf, value_);
                } else {
@@ -273,12 +280,13 @@ static char *normalize_value(const char *key, const char *value)
        if (!value)
                return NULL;
 
-       if (types == 0 || types == TYPE_PATH)
+       if (types == 0 || types == TYPE_PATH || types == TYPE_EXPIRY_DATE)
                /*
                 * We don't do normalization for TYPE_PATH here: If
                 * the path is like ~/foobar/, we prefer to store
                 * "~/foobar/" in the config file, and to expand the ~
                 * when retrieving the value.
+                * Also don't do normalization for expiry dates.
                 */
                return xstrdup(value);
        if (types == TYPE_INT)
index 9808d062a80a2ff07699fb0ea4cbd29f560630d3..16bfb22f7381ee8e6967ab836686c5def7cff892 100644 (file)
@@ -379,7 +379,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
                                add_head_to_pending(&rev);
                                if (!rev.pending.nr) {
                                        struct tree *tree;
-                                       tree = lookup_tree(&empty_tree_oid);
+                                       tree = lookup_tree(the_hash_algo->empty_tree);
                                        add_pending_object(&rev, &tree->object, "HEAD");
                                }
                                break;
index 6c1fa896ad01a8de0dcd99be80827d436d2cc857..14fdf39165d20602ad8b682b55660ec2466c1d20 100644 (file)
@@ -142,11 +142,19 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
        struct userformat_want w;
        int quiet = 0, source = 0, mailmap = 0;
        static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP};
+       static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;
+       static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;
+       struct decoration_filter decoration_filter = {&decorate_refs_include,
+                                                     &decorate_refs_exclude};
 
        const struct option builtin_log_options[] = {
                OPT__QUIET(&quiet, N_("suppress diff output")),
                OPT_BOOL(0, "source", &source, N_("show source")),
                OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")),
+               OPT_STRING_LIST(0, "decorate-refs", &decorate_refs_include,
+                               N_("pattern"), N_("only decorate refs that match <pattern>")),
+               OPT_STRING_LIST(0, "decorate-refs-exclude", &decorate_refs_exclude,
+                               N_("pattern"), N_("do not decorate refs that match <pattern>")),
                { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
                  PARSE_OPT_OPTARG, decorate_callback},
                OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
@@ -205,7 +213,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
 
        if (decoration_style) {
                rev->show_decorations = 1;
-               load_ref_decorations(decoration_style);
+               load_ref_decorations(&decoration_filter, decoration_style);
        }
 
        if (rev->line_level_traverse)
index a2a542f82b2a3cc9ff8d4615192a406c0012d22a..1a2c7d92ad7e73246be8dc64946feb00c16f7261 100644 (file)
@@ -33,7 +33,7 @@ static const char * const git_notes_usage[] = {
        N_("git notes merge --commit [-v | -q]"),
        N_("git notes merge --abort [-v | -q]"),
        N_("git notes [--ref <notes-ref>] remove [<object>...]"),
-       N_("git notes [--ref <notes-ref>] prune [-n | -v]"),
+       N_("git notes [--ref <notes-ref>] prune [-n] [-v]"),
        N_("git notes [--ref <notes-ref>] get-ref"),
        NULL
 };
index cddabf26a95cc22dfcad9843554dc8af26c858bc..d2fdae680a1ebe75d55d4fb66a3bdd0b54829666 100644 (file)
@@ -8,7 +8,7 @@
 #include "progress.h"
 
 static const char * const prune_usage[] = {
-       N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"),
+       N_("git prune [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]"),
        NULL
 };
 static int show_only;
index 166b777ed69073f44f82e8052a0bdb6f287d22a9..511dbbe0f6e25d8f0e8c6ce511cc0ff734adc9bc 100644 (file)
@@ -557,7 +557,7 @@ static int pull_into_void(const struct object_id *merge_head,
         * index/worktree changes that the user already made on the unborn
         * branch.
         */
-       if (checkout_fast_forward(&empty_tree_oid, merge_head, 0))
+       if (checkout_fast_forward(the_hash_algo->empty_tree, merge_head, 0))
                return 1;
 
        if (update_ref("initial pull", "HEAD", merge_head, curr_head, 0, UPDATE_REFS_DIE_ON_ERR))
index 4d37a160d759889e28602adc6064cde044248e4e..b7ce7c7f5275febbf6db6b71e3b610b09a4fe191 100644 (file)
@@ -24,6 +24,7 @@
 #include "tmp-objdir.h"
 #include "oidset.h"
 #include "packfile.h"
+#include "protocol.h"
 
 static const char * const receive_pack_usage[] = {
        N_("git receive-pack <git-dir>"),
@@ -1961,6 +1962,22 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
        else if (0 <= receive_unpack_limit)
                unpack_limit = receive_unpack_limit;
 
+       switch (determine_protocol_version_server()) {
+       case protocol_v1:
+               /*
+                * v1 is just the original protocol with a version string,
+                * so just fall through after writing the version string.
+                */
+               if (advertise_refs || !stateless_rpc)
+                       packet_write_fmt(1, "version 1\n");
+
+               /* fallthrough */
+       case protocol_v0:
+               break;
+       case protocol_unknown_version:
+               BUG("unknown protocol version");
+       }
+
        if (advertise_refs || !stateless_rpc) {
                write_head_info();
        }
index ab31a3b6aa1d0b2a11752104a89d8a80c7ebc762..2233725315ba32832ec5c866b7dbdf72103ac74d 100644 (file)
@@ -416,16 +416,6 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
        return ent;
 }
 
-static int parse_expire_cfg_value(const char *var, const char *value, timestamp_t *expire)
-{
-       if (!value)
-               return config_error_nonbool(var);
-       if (parse_expiry_date(value, expire))
-               return error(_("'%s' for '%s' is not a valid timestamp"),
-                            value, var);
-       return 0;
-}
-
 /* expiry timer slot */
 #define EXPIRE_TOTAL   01
 #define EXPIRE_UNREACH 02
@@ -443,11 +433,11 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)
 
        if (!strcmp(key, "reflogexpire")) {
                slot = EXPIRE_TOTAL;
-               if (parse_expire_cfg_value(var, value, &expire))
+               if (git_config_expiry_date(&expire, var, value))
                        return -1;
        } else if (!strcmp(key, "reflogexpireunreachable")) {
                slot = EXPIRE_UNREACH;
-               if (parse_expire_cfg_value(var, value, &expire))
+               if (git_config_expiry_date(&expire, var, value))
                        return -1;
        } else
                return git_default_config(var, value, cb);
index 2086f0eb089b34bd38b2a4f47ae4b710b7cb0ba1..a5c4a8a6941d46c78e4dc58d0df47532f0bb2b45 100644 (file)
@@ -623,7 +623,7 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
 
                if (refs_head_ref(get_submodule_ref_store(path),
                                  handle_submodule_head_ref, &oid))
-                       die(_("could not resolve HEAD ref inside the"
+                       die(_("could not resolve HEAD ref inside the "
                              "submodule '%s'"), path);
 
                print_status(flags, '+', path, &oid, displaypath);
diff --git a/cache.h b/cache.h
index 2e143450514c88ec30b447c8a94e280be9105aa0..d3e240228c9c488f78ee9be06c9d151f1d2325b2 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -14,6 +14,7 @@
 #include "hash.h"
 #include "path.h"
 #include "sha1-array.h"
+#include "repository.h"
 
 #ifndef platform_SHA_CTX
 /*
@@ -77,6 +78,8 @@ struct object_id {
        unsigned char hash[GIT_MAX_RAWSZ];
 };
 
+#define the_hash_algo the_repository->hash_algo
+
 #if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
 #define DTYPE(de)      ((de)->d_type)
 #else
@@ -450,6 +453,16 @@ static inline enum object_type object_type(unsigned int mode)
 #define GIT_QUARANTINE_ENVIRONMENT "GIT_QUARANTINE_PATH"
 #define GIT_OPTIONAL_LOCKS_ENVIRONMENT "GIT_OPTIONAL_LOCKS"
 
+/*
+ * Environment variable used in handshaking the wire protocol.
+ * Contains a colon ':' separated list of keys with optional values
+ * 'key[=value]'.  Presence of unknown keys and values must be
+ * ignored.
+ */
+#define GIT_PROTOCOL_ENVIRONMENT "GIT_PROTOCOL"
+/* HTTP header used to handshake the wire protocol */
+#define GIT_PROTOCOL_HEADER "Git-Protocol"
+
 /*
  * This environment variable is expected to contain a boolean indicating
  * whether we should or should not treat:
@@ -897,6 +910,7 @@ struct repository_format {
        int version;
        int precious_objects;
        int is_bare;
+       int hash_algo;
        char *work_tree;
        struct string_list unknown_extensions;
 };
@@ -1029,22 +1043,22 @@ extern const struct object_id empty_blob_oid;
 
 static inline int is_empty_blob_sha1(const unsigned char *sha1)
 {
-       return !hashcmp(sha1, EMPTY_BLOB_SHA1_BIN);
+       return !hashcmp(sha1, the_hash_algo->empty_blob->hash);
 }
 
 static inline int is_empty_blob_oid(const struct object_id *oid)
 {
-       return !hashcmp(oid->hash, EMPTY_BLOB_SHA1_BIN);
+       return !oidcmp(oid, the_hash_algo->empty_blob);
 }
 
 static inline int is_empty_tree_sha1(const unsigned char *sha1)
 {
-       return !hashcmp(sha1, EMPTY_TREE_SHA1_BIN);
+       return !hashcmp(sha1, the_hash_algo->empty_tree->hash);
 }
 
 static inline int is_empty_tree_oid(const struct object_id *oid)
 {
-       return !hashcmp(oid->hash, EMPTY_TREE_SHA1_BIN);
+       return !oidcmp(oid, the_hash_algo->empty_tree);
 }
 
 /* set default permissions by passing mode arguments to open(2) */
index 676786a028f2c726a55694cb5af1b1781ddd6e4e..e617c2018d22b6d389548a3573d2deb2ec608b99 100644 (file)
--- a/config.c
+++ b/config.c
@@ -990,6 +990,16 @@ int git_config_pathname(const char **dest, const char *var, const char *value)
        return 0;
 }
 
+int git_config_expiry_date(timestamp_t *timestamp, const char *var, const char *value)
+{
+       if (!value)
+               return config_error_nonbool(var);
+       if (parse_expiry_date(value, timestamp))
+               return error(_("'%s' for '%s' is not a valid timestamp"),
+                            value, var);
+       return 0;
+}
+
 static int git_default_core_config(const char *var, const char *value)
 {
        /* This needs a better name */
index 524d411823e1949dd180ada236a6f4edee8ef7eb..ef70a9cac1e6dc67df24d157a4e5de38edd8c984 100644 (file)
--- a/config.h
+++ b/config.h
@@ -58,6 +58,7 @@ extern int git_config_bool_or_int(const char *, const char *, int *);
 extern int git_config_bool(const char *, const char *);
 extern int git_config_string(const char **, const char *, const char *);
 extern int git_config_pathname(const char **, const char *, const char *);
+extern int git_config_expiry_date(timestamp_t *, const char *, const char *);
 extern int git_config_set_in_file_gently(const char *, const char *, const char *);
 extern void git_config_set_in_file(const char *, const char *, const char *);
 extern int git_config_set_gently(const char *, const char *);
index df56c0cbff4dfb1263917f6147bd7e1912418dd6..c3a014c5babf72ee4c0d135fec264afb37b040de 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -11,6 +11,8 @@
 #include "string-list.h"
 #include "sha1-array.h"
 #include "transport.h"
+#include "strbuf.h"
+#include "protocol.h"
 
 static char *server_capabilities;
 static const char *parse_feature_value(const char *, const char *, int *);
@@ -107,6 +109,118 @@ static void annotate_refs_with_symref_info(struct ref *ref)
        string_list_clear(&symref, 0);
 }
 
+/*
+ * Read one line of a server's ref advertisement into packet_buffer.
+ */
+static int read_remote_ref(int in, char **src_buf, size_t *src_len,
+                          int *responded)
+{
+       int len = packet_read(in, src_buf, src_len,
+                             packet_buffer, sizeof(packet_buffer),
+                             PACKET_READ_GENTLE_ON_EOF |
+                             PACKET_READ_CHOMP_NEWLINE);
+       const char *arg;
+       if (len < 0)
+               die_initial_contact(*responded);
+       if (len > 4 && skip_prefix(packet_buffer, "ERR ", &arg))
+               die("remote error: %s", arg);
+
+       *responded = 1;
+
+       return len;
+}
+
+#define EXPECTING_PROTOCOL_VERSION 0
+#define EXPECTING_FIRST_REF 1
+#define EXPECTING_REF 2
+#define EXPECTING_SHALLOW 3
+
+/* Returns 1 if packet_buffer is a protocol version pkt-line, 0 otherwise. */
+static int process_protocol_version(void)
+{
+       switch (determine_protocol_version_client(packet_buffer)) {
+       case protocol_v1:
+               return 1;
+       case protocol_v0:
+               return 0;
+       default:
+               die("server is speaking an unknown protocol");
+       }
+}
+
+static void process_capabilities(int *len)
+{
+       int nul_location = strlen(packet_buffer);
+       if (nul_location == *len)
+               return;
+       server_capabilities = xstrdup(packet_buffer + nul_location + 1);
+       *len = nul_location;
+}
+
+static int process_dummy_ref(void)
+{
+       struct object_id oid;
+       const char *name;
+
+       if (parse_oid_hex(packet_buffer, &oid, &name))
+               return 0;
+       if (*name != ' ')
+               return 0;
+       name++;
+
+       return !oidcmp(&null_oid, &oid) && !strcmp(name, "capabilities^{}");
+}
+
+static void check_no_capabilities(int len)
+{
+       if (strlen(packet_buffer) != len)
+               warning("Ignoring capabilities after first line '%s'",
+                       packet_buffer + strlen(packet_buffer));
+}
+
+static int process_ref(int len, struct ref ***list, unsigned int flags,
+                      struct oid_array *extra_have)
+{
+       struct object_id old_oid;
+       const char *name;
+
+       if (parse_oid_hex(packet_buffer, &old_oid, &name))
+               return 0;
+       if (*name != ' ')
+               return 0;
+       name++;
+
+       if (extra_have && !strcmp(name, ".have")) {
+               oid_array_append(extra_have, &old_oid);
+       } else if (!strcmp(name, "capabilities^{}")) {
+               die("protocol error: unexpected capabilities^{}");
+       } else if (check_ref(name, flags)) {
+               struct ref *ref = alloc_ref(name);
+               oidcpy(&ref->old_oid, &old_oid);
+               **list = ref;
+               *list = &ref->next;
+       }
+       check_no_capabilities(len);
+       return 1;
+}
+
+static int process_shallow(int len, struct oid_array *shallow_points)
+{
+       const char *arg;
+       struct object_id old_oid;
+
+       if (!skip_prefix(packet_buffer, "shallow ", &arg))
+               return 0;
+
+       if (get_oid_hex(arg, &old_oid))
+               die("protocol error: expected shallow sha-1, got '%s'", arg);
+       if (!shallow_points)
+               die("repository on the other end cannot be shallow");
+       oid_array_append(shallow_points, &old_oid);
+       check_no_capabilities(len);
+       return 1;
+}
+
 /*
  * Read all the refs from the other end
  */
@@ -123,76 +237,41 @@ struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
         * willing to talk to us.  A hang-up before seeing any
         * response does not necessarily mean an ACL problem, though.
         */
-       int saw_response;
-       int got_dummy_ref_with_capabilities_declaration = 0;
+       int responded = 0;
+       int len;
+       int state = EXPECTING_PROTOCOL_VERSION;
 
        *list = NULL;
-       for (saw_response = 0; ; saw_response = 1) {
-               struct ref *ref;
-               struct object_id old_oid;
-               char *name;
-               int len, name_len;
-               char *buffer = packet_buffer;
-               const char *arg;
-
-               len = packet_read(in, &src_buf, &src_len,
-                                 packet_buffer, sizeof(packet_buffer),
-                                 PACKET_READ_GENTLE_ON_EOF |
-                                 PACKET_READ_CHOMP_NEWLINE);
-               if (len < 0)
-                       die_initial_contact(saw_response);
-
-               if (!len)
-                       break;
-
-               if (len > 4 && skip_prefix(buffer, "ERR ", &arg))
-                       die("remote error: %s", arg);
-
-               if (len == GIT_SHA1_HEXSZ + strlen("shallow ") &&
-                       skip_prefix(buffer, "shallow ", &arg)) {
-                       if (get_oid_hex(arg, &old_oid))
-                               die("protocol error: expected shallow sha-1, got '%s'", arg);
-                       if (!shallow_points)
-                               die("repository on the other end cannot be shallow");
-                       oid_array_append(shallow_points, &old_oid);
-                       continue;
-               }
-
-               if (len < GIT_SHA1_HEXSZ + 2 || get_oid_hex(buffer, &old_oid) ||
-                       buffer[GIT_SHA1_HEXSZ] != ' ')
-                       die("protocol error: expected sha/ref, got '%s'", buffer);
-               name = buffer + GIT_SHA1_HEXSZ + 1;
-
-               name_len = strlen(name);
-               if (len != name_len + GIT_SHA1_HEXSZ + 1) {
-                       free(server_capabilities);
-                       server_capabilities = xstrdup(name + name_len + 1);
-               }
 
-               if (extra_have && !strcmp(name, ".have")) {
-                       oid_array_append(extra_have, &old_oid);
-                       continue;
-               }
-
-               if (!strcmp(name, "capabilities^{}")) {
-                       if (saw_response)
-                               die("protocol error: unexpected capabilities^{}");
-                       if (got_dummy_ref_with_capabilities_declaration)
-                               die("protocol error: multiple capabilities^{}");
-                       got_dummy_ref_with_capabilities_declaration = 1;
-                       continue;
+       while ((len = read_remote_ref(in, &src_buf, &src_len, &responded))) {
+               switch (state) {
+               case EXPECTING_PROTOCOL_VERSION:
+                       if (process_protocol_version()) {
+                               state = EXPECTING_FIRST_REF;
+                               break;
+                       }
+                       state = EXPECTING_FIRST_REF;
+                       /* fallthrough */
+               case EXPECTING_FIRST_REF:
+                       process_capabilities(&len);
+                       if (process_dummy_ref()) {
+                               state = EXPECTING_SHALLOW;
+                               break;
+                       }
+                       state = EXPECTING_REF;
+                       /* fallthrough */
+               case EXPECTING_REF:
+                       if (process_ref(len, &list, flags, extra_have))
+                               break;
+                       state = EXPECTING_SHALLOW;
+                       /* fallthrough */
+               case EXPECTING_SHALLOW:
+                       if (process_shallow(len, shallow_points))
+                               break;
+                       die("protocol error: unexpected '%s'", packet_buffer);
+               default:
+                       die("unexpected state %d", state);
                }
-
-               if (!check_ref(name, flags))
-                       continue;
-
-               if (got_dummy_ref_with_capabilities_declaration)
-                       die("protocol error: unexpected ref after capabilities^{}");
-
-               ref = alloc_ref(buffer + GIT_SHA1_HEXSZ + 1);
-               oidcpy(&ref->old_oid, &old_oid);
-               *list = ref;
-               list = &ref->next;
        }
 
        annotate_refs_with_symref_info(*orig_list);
@@ -503,12 +582,25 @@ static int git_tcp_connect_sock(char *host, int flags)
 #endif /* NO_IPV6 */
 
 
-static void git_tcp_connect(int fd[2], char *host, int flags)
+/*
+ * Dummy child_process returned by git_connect() if the transport protocol
+ * does not need fork(2).
+ */
+static struct child_process no_fork = CHILD_PROCESS_INIT;
+
+int git_connection_is_socket(struct child_process *conn)
+{
+       return conn == &no_fork;
+}
+
+static struct child_process *git_tcp_connect(int fd[2], char *host, int flags)
 {
        int sockfd = git_tcp_connect_sock(host, flags);
 
        fd[0] = sockfd;
        fd[1] = dup(sockfd);
+
+       return &no_fork;
 }
 
 
@@ -682,8 +774,6 @@ static enum protocol parse_connect_url(const char *url_orig, char **ret_host,
        return protocol;
 }
 
-static struct child_process no_fork = CHILD_PROCESS_INIT;
-
 static const char *get_ssh_command(void)
 {
        const char *ssh;
@@ -697,37 +787,47 @@ static const char *get_ssh_command(void)
        return NULL;
 }
 
-static int override_ssh_variant(int *port_option, int *needs_batch)
+enum ssh_variant {
+       VARIANT_AUTO,
+       VARIANT_SIMPLE,
+       VARIANT_SSH,
+       VARIANT_PLINK,
+       VARIANT_PUTTY,
+       VARIANT_TORTOISEPLINK,
+};
+
+static void override_ssh_variant(enum ssh_variant *ssh_variant)
 {
-       char *variant;
+       const char *variant = getenv("GIT_SSH_VARIANT");
 
-       variant = xstrdup_or_null(getenv("GIT_SSH_VARIANT"));
-       if (!variant &&
-           git_config_get_string("ssh.variant", &variant))
-               return 0;
+       if (!variant && git_config_get_string_const("ssh.variant", &variant))
+               return;
 
-       if (!strcmp(variant, "plink") || !strcmp(variant, "putty")) {
-               *port_option = 'P';
-               *needs_batch = 0;
-       } else if (!strcmp(variant, "tortoiseplink")) {
-               *port_option = 'P';
-               *needs_batch = 1;
-       } else {
-               *port_option = 'p';
-               *needs_batch = 0;
-       }
-       free(variant);
-       return 1;
+       if (!strcmp(variant, "auto"))
+               *ssh_variant = VARIANT_AUTO;
+       else if (!strcmp(variant, "plink"))
+               *ssh_variant = VARIANT_PLINK;
+       else if (!strcmp(variant, "putty"))
+               *ssh_variant = VARIANT_PUTTY;
+       else if (!strcmp(variant, "tortoiseplink"))
+               *ssh_variant = VARIANT_TORTOISEPLINK;
+       else if (!strcmp(variant, "simple"))
+               *ssh_variant = VARIANT_SIMPLE;
+       else
+               *ssh_variant = VARIANT_SSH;
 }
 
-static void handle_ssh_variant(const char *ssh_command, int is_cmdline,
-                              int *port_option, int *needs_batch)
+static enum ssh_variant determine_ssh_variant(const char *ssh_command,
+                                             int is_cmdline)
 {
+       enum ssh_variant ssh_variant = VARIANT_AUTO;
        const char *variant;
        char *p = NULL;
 
-       if (override_ssh_variant(port_option, needs_batch))
-               return;
+       override_ssh_variant(&ssh_variant);
+
+       if (ssh_variant != VARIANT_AUTO)
+               return ssh_variant;
 
        if (!is_cmdline) {
                p = xstrdup(ssh_command);
@@ -746,27 +846,200 @@ static void handle_ssh_variant(const char *ssh_command, int is_cmdline,
                        free(ssh_argv);
                } else {
                        free(p);
-                       return;
+                       return ssh_variant;
                }
        }
 
-       if (!strcasecmp(variant, "plink") ||
-           !strcasecmp(variant, "plink.exe"))
-               *port_option = 'P';
+       if (!strcasecmp(variant, "ssh") ||
+           !strcasecmp(variant, "ssh.exe"))
+               ssh_variant = VARIANT_SSH;
+       else if (!strcasecmp(variant, "plink") ||
+                !strcasecmp(variant, "plink.exe"))
+               ssh_variant = VARIANT_PLINK;
        else if (!strcasecmp(variant, "tortoiseplink") ||
-                !strcasecmp(variant, "tortoiseplink.exe")) {
-               *port_option = 'P';
-               *needs_batch = 1;
-       }
+                !strcasecmp(variant, "tortoiseplink.exe"))
+               ssh_variant = VARIANT_TORTOISEPLINK;
+
        free(p);
+       return ssh_variant;
+}
+
+/*
+ * Open a connection using Git's native protocol.
+ *
+ * The caller is responsible for freeing hostandport, but this function may
+ * modify it (for example, to truncate it to remove the port part).
+ */
+static struct child_process *git_connect_git(int fd[2], char *hostandport,
+                                            const char *path, const char *prog,
+                                            int flags)
+{
+       struct child_process *conn;
+       struct strbuf request = STRBUF_INIT;
+       /*
+        * Set up virtual host information based on where we will
+        * connect, unless the user has overridden us in
+        * the environment.
+        */
+       char *target_host = getenv("GIT_OVERRIDE_VIRTUAL_HOST");
+       if (target_host)
+               target_host = xstrdup(target_host);
+       else
+               target_host = xstrdup(hostandport);
+
+       transport_check_allowed("git");
+
+       /*
+        * These underlying connection commands die() if they
+        * cannot connect.
+        */
+       if (git_use_proxy(hostandport))
+               conn = git_proxy_connect(fd, hostandport);
+       else
+               conn = git_tcp_connect(fd, hostandport, flags);
+       /*
+        * Separate original protocol components prog and path
+        * from extended host header with a NUL byte.
+        *
+        * Note: Do not add any other headers here!  Doing so
+        * will cause older git-daemon servers to crash.
+        */
+       strbuf_addf(&request,
+                   "%s %s%chost=%s%c",
+                   prog, path, 0,
+                   target_host, 0);
+
+       /* If using a new version put that stuff here after a second null byte */
+       if (get_protocol_version_config() > 0) {
+               strbuf_addch(&request, '\0');
+               strbuf_addf(&request, "version=%d%c",
+                           get_protocol_version_config(), '\0');
+       }
+
+       packet_write(fd[1], request.buf, request.len);
+
+       free(target_host);
+       strbuf_release(&request);
+       return conn;
+}
+
+/*
+ * Append the appropriate environment variables to `env` and options to
+ * `args` for running ssh in Git's SSH-tunneled transport.
+ */
+static void push_ssh_options(struct argv_array *args, struct argv_array *env,
+                            enum ssh_variant variant, const char *port,
+                            int flags)
+{
+       if (variant == VARIANT_SSH &&
+           get_protocol_version_config() > 0) {
+               argv_array_push(args, "-o");
+               argv_array_push(args, "SendEnv=" GIT_PROTOCOL_ENVIRONMENT);
+               argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=version=%d",
+                                get_protocol_version_config());
+       }
+
+       if (flags & CONNECT_IPV4) {
+               switch (variant) {
+               case VARIANT_AUTO:
+                       BUG("VARIANT_AUTO passed to push_ssh_options");
+               case VARIANT_SIMPLE:
+                       die("ssh variant 'simple' does not support -4");
+               case VARIANT_SSH:
+               case VARIANT_PLINK:
+               case VARIANT_PUTTY:
+               case VARIANT_TORTOISEPLINK:
+                       argv_array_push(args, "-4");
+               }
+       } else if (flags & CONNECT_IPV6) {
+               switch (variant) {
+               case VARIANT_AUTO:
+                       BUG("VARIANT_AUTO passed to push_ssh_options");
+               case VARIANT_SIMPLE:
+                       die("ssh variant 'simple' does not support -6");
+               case VARIANT_SSH:
+               case VARIANT_PLINK:
+               case VARIANT_PUTTY:
+               case VARIANT_TORTOISEPLINK:
+                       argv_array_push(args, "-6");
+               }
+       }
+
+       if (variant == VARIANT_TORTOISEPLINK)
+               argv_array_push(args, "-batch");
+
+       if (port) {
+               switch (variant) {
+               case VARIANT_AUTO:
+                       BUG("VARIANT_AUTO passed to push_ssh_options");
+               case VARIANT_SIMPLE:
+                       die("ssh variant 'simple' does not support setting port");
+               case VARIANT_SSH:
+                       argv_array_push(args, "-p");
+                       break;
+               case VARIANT_PLINK:
+               case VARIANT_PUTTY:
+               case VARIANT_TORTOISEPLINK:
+                       argv_array_push(args, "-P");
+               }
+
+               argv_array_push(args, port);
+       }
+}
+
+/* Prepare a child_process for use by Git's SSH-tunneled transport. */
+static void fill_ssh_args(struct child_process *conn, const char *ssh_host,
+                         const char *port, int flags)
+{
+       const char *ssh;
+       enum ssh_variant variant;
+
+       if (looks_like_command_line_option(ssh_host))
+               die("strange hostname '%s' blocked", ssh_host);
+
+       ssh = get_ssh_command();
+       if (ssh) {
+               variant = determine_ssh_variant(ssh, 1);
+       } else {
+               /*
+                * GIT_SSH is the no-shell version of
+                * GIT_SSH_COMMAND (and must remain so for
+                * historical compatibility).
+                */
+               conn->use_shell = 0;
+
+               ssh = getenv("GIT_SSH");
+               if (!ssh)
+                       ssh = "ssh";
+               variant = determine_ssh_variant(ssh, 0);
+       }
+
+       if (variant == VARIANT_AUTO) {
+               struct child_process detect = CHILD_PROCESS_INIT;
+
+               detect.use_shell = conn->use_shell;
+               detect.no_stdin = detect.no_stdout = detect.no_stderr = 1;
+
+               argv_array_push(&detect.args, ssh);
+               argv_array_push(&detect.args, "-G");
+               push_ssh_options(&detect.args, &detect.env_array,
+                                VARIANT_SSH, port, flags);
+               argv_array_push(&detect.args, ssh_host);
+
+               variant = run_command(&detect) ? VARIANT_SIMPLE : VARIANT_SSH;
+       }
+
+       argv_array_push(&conn->args, ssh);
+       push_ssh_options(&conn->args, &conn->env_array, variant, port, flags);
+       argv_array_push(&conn->args, ssh_host);
 }
 
 /*
- * This returns a dummy child_process if the transport protocol does not
- * need fork(2), or a struct child_process object if it does.  Once done,
- * finish the connection with finish_connect() with the value returned from
- * this function (it is safe to call finish_connect() with NULL to support
- * the former case).
+ * This returns the dummy child_process `no_fork` if the transport protocol
+ * does not need fork(2), or a struct child_process object if it does.  Once
+ * done, finish the connection with finish_connect() with the value returned
+ * from this function (it is safe to call finish_connect() with NULL to
+ * support the former case).
  *
  * If it returns, the connect is successful; it just dies on errors (this
  * will hopefully be changed in a libification effort, to return NULL when
@@ -776,7 +1049,7 @@ struct child_process *git_connect(int fd[2], const char *url,
                                  const char *prog, int flags)
 {
        char *hostandport, *path;
-       struct child_process *conn = &no_fork;
+       struct child_process *conn;
        enum protocol protocol;
 
        /* Without this we cannot rely on waitpid() to tell
@@ -792,40 +1065,10 @@ struct child_process *git_connect(int fd[2], const char *url,
                printf("Diag: path=%s\n", path ? path : "NULL");
                conn = NULL;
        } else if (protocol == PROTO_GIT) {
-               /*
-                * Set up virtual host information based on where we will
-                * connect, unless the user has overridden us in
-                * the environment.
-                */
-               char *target_host = getenv("GIT_OVERRIDE_VIRTUAL_HOST");
-               if (target_host)
-                       target_host = xstrdup(target_host);
-               else
-                       target_host = xstrdup(hostandport);
-
-               transport_check_allowed("git");
-
-               /* These underlying connection commands die() if they
-                * cannot connect.
-                */
-               if (git_use_proxy(hostandport))
-                       conn = git_proxy_connect(fd, hostandport);
-               else
-                       git_tcp_connect(fd, hostandport, flags);
-               /*
-                * Separate original protocol components prog and path
-                * from extended host header with a NUL byte.
-                *
-                * Note: Do not add any other headers here!  Doing so
-                * will cause older git-daemon servers to crash.
-                */
-               packet_write_fmt(fd[1],
-                            "%s %s%chost=%s%c",
-                            prog, path, 0,
-                            target_host, 0);
-               free(target_host);
+               conn = git_connect_git(fd, hostandport, path, prog, flags);
        } else {
                struct strbuf cmd = STRBUF_INIT;
+               const char *const *var;
 
                conn = xmalloc(sizeof(*conn));
                child_process_init(conn);
@@ -838,13 +1081,12 @@ struct child_process *git_connect(int fd[2], const char *url,
                sq_quote_buf(&cmd, path);
 
                /* remove repo-local variables from the environment */
-               conn->env = local_repo_env;
+               for (var = local_repo_env; *var; var++)
+                       argv_array_push(&conn->env_array, *var);
+
                conn->use_shell = 1;
                conn->in = conn->out = -1;
                if (protocol == PROTO_SSH) {
-                       const char *ssh;
-                       int needs_batch = 0;
-                       int port_option = 'p';
                        char *ssh_host = hostandport;
                        const char *port = NULL;
                        transport_check_allowed("ssh");
@@ -866,46 +1108,13 @@ struct child_process *git_connect(int fd[2], const char *url,
                                strbuf_release(&cmd);
                                return NULL;
                        }
-
-                       if (looks_like_command_line_option(ssh_host))
-                               die("strange hostname '%s' blocked", ssh_host);
-
-                       ssh = get_ssh_command();
-                       if (ssh)
-                               handle_ssh_variant(ssh, 1, &port_option,
-                                                  &needs_batch);
-                       else {
-                               /*
-                                * GIT_SSH is the no-shell version of
-                                * GIT_SSH_COMMAND (and must remain so for
-                                * historical compatibility).
-                                */
-                               conn->use_shell = 0;
-
-                               ssh = getenv("GIT_SSH");
-                               if (!ssh)
-                                       ssh = "ssh";
-                               else
-                                       handle_ssh_variant(ssh, 0,
-                                                          &port_option,
-                                                          &needs_batch);
-                       }
-
-                       argv_array_push(&conn->args, ssh);
-                       if (flags & CONNECT_IPV4)
-                               argv_array_push(&conn->args, "-4");
-                       else if (flags & CONNECT_IPV6)
-                               argv_array_push(&conn->args, "-6");
-                       if (needs_batch)
-                               argv_array_push(&conn->args, "-batch");
-                       if (port) {
-                               argv_array_pushf(&conn->args,
-                                                "-%c", port_option);
-                               argv_array_push(&conn->args, port);
-                       }
-                       argv_array_push(&conn->args, ssh_host);
+                       fill_ssh_args(conn, ssh_host, port, flags);
                } else {
                        transport_check_allowed("file");
+                       if (get_protocol_version_config() > 0) {
+                               argv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT "=version=%d",
+                                                get_protocol_version_config());
+                       }
                }
                argv_array_push(&conn->args, cmd.buf);
 
@@ -921,11 +1130,6 @@ struct child_process *git_connect(int fd[2], const char *url,
        return conn;
 }
 
-int git_connection_is_socket(struct child_process *conn)
-{
-       return conn == &no_fork;
-}
-
 int finish_connect(struct child_process *conn)
 {
        int code;
index 35df6ce9ea3f37c36e4df78929758dd1a818712e..3683c772c5586ccc1f6dfc8c047919d8d96aa891 100644 (file)
@@ -1922,6 +1922,7 @@ _git_pull ()
        --*)
                __gitcomp "
                        --rebase --no-rebase
+                       --autostash --no-autostash
                        $__git_merge_options
                        $__git_fetch_options
                "
index 30747075f0c45942220f63202f8c984e3e0956a7..e37e343d0aec37fd74e7bdf694b99f59d022690a 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -282,7 +282,7 @@ static const char *path_ok(const char *directory, struct hostinfo *hi)
        return NULL;            /* Fallthrough. Deny by default */
 }
 
-typedef int (*daemon_service_fn)(void);
+typedef int (*daemon_service_fn)(const struct argv_array *env);
 struct daemon_service {
        const char *name;
        const char *config_name;
@@ -363,7 +363,7 @@ static int run_access_hook(struct daemon_service *service, const char *dir,
 }
 
 static int run_service(const char *dir, struct daemon_service *service,
-                      struct hostinfo *hi)
+                      struct hostinfo *hi, const struct argv_array *env)
 {
        const char *path;
        int enabled = service->enabled;
@@ -422,7 +422,7 @@ static int run_service(const char *dir, struct daemon_service *service,
         */
        signal(SIGTERM, SIG_IGN);
 
-       return service->fn();
+       return service->fn(env);
 }
 
 static void copy_to_log(int fd)
@@ -462,25 +462,34 @@ static int run_service_command(struct child_process *cld)
        return finish_command(cld);
 }
 
-static int upload_pack(void)
+static int upload_pack(const struct argv_array *env)
 {
        struct child_process cld = CHILD_PROCESS_INIT;
        argv_array_pushl(&cld.args, "upload-pack", "--strict", NULL);
        argv_array_pushf(&cld.args, "--timeout=%u", timeout);
+
+       argv_array_pushv(&cld.env_array, env->argv);
+
        return run_service_command(&cld);
 }
 
-static int upload_archive(void)
+static int upload_archive(const struct argv_array *env)
 {
        struct child_process cld = CHILD_PROCESS_INIT;
        argv_array_push(&cld.args, "upload-archive");
+
+       argv_array_pushv(&cld.env_array, env->argv);
+
        return run_service_command(&cld);
 }
 
-static int receive_pack(void)
+static int receive_pack(const struct argv_array *env)
 {
        struct child_process cld = CHILD_PROCESS_INIT;
        argv_array_push(&cld.args, "receive-pack");
+
+       argv_array_pushv(&cld.env_array, env->argv);
+
        return run_service_command(&cld);
 }
 
@@ -573,8 +582,11 @@ static void canonicalize_client(struct strbuf *out, const char *in)
 
 /*
  * Read the host as supplied by the client connection.
+ *
+ * Returns a pointer to the character after the NUL byte terminating the host
+ * arguemnt, or 'extra_args' if there is no host arguemnt.
  */
-static void parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
+static char *parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
 {
        char *val;
        int vallen;
@@ -602,6 +614,43 @@ static void parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
                if (extra_args < end && *extra_args)
                        die("Invalid request");
        }
+
+       return extra_args;
+}
+
+static void parse_extra_args(struct hostinfo *hi, struct argv_array *env,
+                            char *extra_args, int buflen)
+{
+       const char *end = extra_args + buflen;
+       struct strbuf git_protocol = STRBUF_INIT;
+
+       /* First look for the host argument */
+       extra_args = parse_host_arg(hi, extra_args, buflen);
+
+       /* Look for additional arguments places after a second NUL byte */
+       for (; extra_args < end; extra_args += strlen(extra_args) + 1) {
+               const char *arg = extra_args;
+
+               /*
+                * Parse the extra arguments, adding most to 'git_protocol'
+                * which will be used to set the 'GIT_PROTOCOL' envvar in the
+                * service that will be run.
+                *
+                * If there ends up being a particular arg in the future that
+                * git-daemon needs to parse specificly (like the 'host' arg)
+                * then it can be parsed here and not added to 'git_protocol'.
+                */
+               if (*arg) {
+                       if (git_protocol.len > 0)
+                               strbuf_addch(&git_protocol, ':');
+                       strbuf_addstr(&git_protocol, arg);
+               }
+       }
+
+       if (git_protocol.len > 0)
+               argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=%s",
+                                git_protocol.buf);
+       strbuf_release(&git_protocol);
 }
 
 /*
@@ -695,6 +744,7 @@ static int execute(void)
        int pktlen, len, i;
        char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT");
        struct hostinfo hi;
+       struct argv_array env = ARGV_ARRAY_INIT;
 
        hostinfo_init(&hi);
 
@@ -716,8 +766,9 @@ static int execute(void)
                pktlen--;
        }
 
+       /* parse additional args hidden behind a NUL byte */
        if (len != pktlen)
-               parse_host_arg(&hi, line + len + 1, pktlen - len - 1);
+               parse_extra_args(&hi, &env, line + len + 1, pktlen - len - 1);
 
        for (i = 0; i < ARRAY_SIZE(daemon_service); i++) {
                struct daemon_service *s = &(daemon_service[i]);
@@ -730,13 +781,15 @@ static int execute(void)
                         * Note: The directory here is probably context sensitive,
                         * and might depend on the actual service being performed.
                         */
-                       int rc = run_service(arg, s, &hi);
+                       int rc = run_service(arg, s, &hi, &env);
                        hostinfo_clear(&hi);
+                       argv_array_clear(&env);
                        return rc;
                }
        }
 
        hostinfo_clear(&hi);
+       argv_array_clear(&env);
        logerror("Protocol error: '%s'", line);
        return -1;
 }
index 5173023cd3c3e4390e9ddc08e898d813f6a32093..8104603a3b36f2fd73761db34efde44d5938f68f 100644 (file)
@@ -218,7 +218,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                        } else if (revs->diffopt.ita_invisible_in_index &&
                                   ce_intent_to_add(ce)) {
                                diff_addremove(&revs->diffopt, '+', ce->ce_mode,
-                                              &empty_tree_oid, 0,
+                                              the_hash_algo->empty_tree, 0,
                                               ce->name, 0);
                                continue;
                        }
index 008b25d3db08726b0fc1569f88fba76694bbcc12..9f6b07ad91f8c2c17e85004a0e71a8b69752d120 100644 (file)
@@ -716,7 +716,8 @@ static int everything_local(struct fetch_pack_args *args,
        for (ref = *refs; ref; ref = ref->next) {
                struct object *o;
 
-               if (!has_object_file(&ref->old_oid))
+               if (!has_object_file_with_flags(&ref->old_oid,
+                                               OBJECT_INFO_QUICK))
                        continue;
 
                o = parse_object(&ref->old_oid);
index ab0d1b0c064a72480d7eac454b297ce95a48c8f3..eeea4b67ea7f1ccee1770c18a36d018b5f3b32b0 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-echo "/* Automatically generated by $0 */
+echo "/* Automatically generated by generate-cmdlist.sh */
 struct cmdname_help {
        char name[16];
        char help[80];
index 918a8de369113bb022f5080ac9c516771f544577..f10caedaa7a3970628148258ed8dc9f6a5e8c827 100644 (file)
@@ -254,7 +254,7 @@ $(ALL_MSGFILES): %.msg : %.po
 lib/tclIndex: $(ALL_LIBFILES) GIT-GUI-VARS
        $(QUIET_INDEX)if echo \
          $(foreach p,$(PRELOAD_FILES),source $p\;) \
-         auto_mkindex lib '*.tcl' \
+         auto_mkindex lib $(patsubst lib/%,%,$(sort $(ALL_LIBFILES))) \
        | $(TCL_PATH) $(QUIET_2DEVNULL); then : ok; \
        else \
         echo >&2 "    * $(TCL_PATH) failed; using unoptimized loading"; \
index 4b7495144013b6a224571376a411cdcbcfd055aa..1114005ce2829011cde2e6de608a2b90df355fc9 100755 (executable)
@@ -76,6 +76,12 @@ create_stash () {
                        shift
                        stash_msg=${1?"BUG: create_stash () -m requires an argument"}
                        ;;
+               -m*)
+                       stash_msg=${1#-m}
+                       ;;
+               --message=*)
+                       stash_msg=${1#--message=}
+                       ;;
                -u|--include-untracked)
                        shift
                        untracked=${1?"BUG: create_stash () -u requires an argument"}
@@ -193,6 +199,12 @@ store_stash () {
                        shift
                        stash_msg="$1"
                        ;;
+               -m*)
+                       stash_msg=${1#-m}
+                       ;;
+               --message=*)
+                       stash_msg=${1#--message=}
+                       ;;
                -q|--quiet)
                        quiet=t
                        ;;
@@ -251,6 +263,12 @@ push_stash () {
                        test -z ${1+x} && usage
                        stash_msg=$1
                        ;;
+               -m*)
+                       stash_msg=${1#-m}
+                       ;;
+               --message=*)
+                       stash_msg=${1#--message=}
+                       ;;
                --help)
                        show_help
                        ;;
diff --git a/git.c b/git.c
index 9e96dd409017c643e8a9bebbc41281728978b0b8..c870b9719c21b2db23ea7ab5d56fdeda483cb02e 100644 (file)
--- a/git.c
+++ b/git.c
@@ -372,7 +372,7 @@ static struct cmd_struct commands[] = {
        { "archive", cmd_archive, RUN_SETUP_GENTLY },
        { "bisect--helper", cmd_bisect__helper, RUN_SETUP },
        { "blame", cmd_blame, RUN_SETUP },
-       { "branch", cmd_branch, RUN_SETUP },
+       { "branch", cmd_branch, RUN_SETUP | DELAY_PAGER_CONFIG },
        { "bundle", cmd_bundle, RUN_SETUP_GENTLY },
        { "cat-file", cmd_cat_file, RUN_SETUP },
        { "check-attr", cmd_check_attr, RUN_SETUP },
diff --git a/grep.c b/grep.c
index d0b9b6cdfa74537ed67dcfef4ae486466d7185f8..3d7cd0e96f1ee160a66dd500c58d4026bf24e34c 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -477,6 +477,8 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt
        int options = PCRE2_MULTILINE;
        const uint8_t *character_tables = NULL;
        int jitret;
+       int patinforet;
+       size_t jitsizearg;
 
        assert(opt->pcre2);
 
@@ -511,6 +513,30 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt
                jitret = pcre2_jit_compile(p->pcre2_pattern, PCRE2_JIT_COMPLETE);
                if (jitret)
                        die("Couldn't JIT the PCRE2 pattern '%s', got '%d'\n", p->pattern, jitret);
+
+               /*
+                * The pcre2_config(PCRE2_CONFIG_JIT, ...) call just
+                * tells us whether the library itself supports JIT,
+                * but to see whether we're going to be actually using
+                * JIT we need to extract PCRE2_INFO_JITSIZE from the
+                * pattern *after* we do pcre2_jit_compile() above.
+                *
+                * This is because if the pattern contains the
+                * (*NO_JIT) verb (see pcre2syntax(3))
+                * pcre2_jit_compile() will exit early with 0. If we
+                * then proceed to call pcre2_jit_match() further down
+                * the line instead of pcre2_match() we'll either
+                * segfault (pre PCRE 10.31) or run into a fatal error
+                * (post PCRE2 10.31)
+                */
+               patinforet = pcre2_pattern_info(p->pcre2_pattern, PCRE2_INFO_JITSIZE, &jitsizearg);
+               if (patinforet)
+                       BUG("pcre2_pattern_info() failed: %d", patinforet);
+               if (jitsizearg == 0) {
+                       p->pcre2_jit_on = 0;
+                       return;
+               }
+
                p->pcre2_jit_stack = pcre2_jit_stack_create(1, 1024 * 1024, NULL);
                if (!p->pcre2_jit_stack)
                        die("Couldn't allocate PCRE2 JIT stack");
@@ -1476,31 +1502,52 @@ static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs,
        }
 }
 
+static int is_empty_line(const char *bol, const char *eol);
+
 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, gs, bol, end))
-               funcname_needed = 2;
+       unsigned cur = lno, from = 1, funcname_lno = 0, orig_from;
+       int funcname_needed = !!opt->funcname, comment_needed = 0;
 
        if (opt->pre_context < lno)
                from = lno - opt->pre_context;
        if (from <= opt->last_shown)
                from = opt->last_shown + 1;
+       orig_from = from;
+       if (opt->funcbody) {
+               if (match_funcname(opt, gs, bol, end))
+                       comment_needed = 1;
+               else
+                       funcname_needed = 1;
+               from = opt->last_shown + 1;
+       }
 
        /* Rewind. */
-       while (bol > gs->buf &&
-              cur > (funcname_needed == 2 ? opt->last_shown + 1 : from)) {
+       while (bol > gs->buf && cur > from) {
+               char *next_bol = bol;
                char *eol = --bol;
 
                while (bol > gs->buf && bol[-1] != '\n')
                        bol--;
                cur--;
+               if (comment_needed && (is_empty_line(bol, eol) ||
+                                      match_funcname(opt, gs, bol, eol))) {
+                       comment_needed = 0;
+                       from = orig_from;
+                       if (cur < from) {
+                               cur++;
+                               bol = next_bol;
+                               break;
+                       }
+               }
                if (funcname_needed && match_funcname(opt, gs, bol, eol)) {
                        funcname_lno = cur;
                        funcname_needed = 0;
+                       if (opt->funcbody)
+                               comment_needed = 1;
+                       else
+                               from = orig_from;
                }
        }
 
diff --git a/hash.h b/hash.h
index 024d0d3d50b174aac1d948e9ecfa7ae92020355a..7d7a864f5dd43bb53ee6b31d391df3e15cbdbb08 100644 (file)
--- a/hash.h
+++ b/hash.h
@@ -1,6 +1,8 @@
 #ifndef HASH_H
 #define HASH_H
 
+#include "git-compat-util.h"
+
 #if defined(SHA1_PPC)
 #include "ppc/sha1.h"
 #elif defined(SHA1_APPLE)
 #include "block-sha1/sha1.h"
 #endif
 
+/*
+ * Note that these constants are suitable for indexing the hash_algos array and
+ * comparing against each other, but are otherwise arbitrary, so they should not
+ * be exposed to the user or serialized to disk.  To know whether a
+ * git_hash_algo struct points to some usable hash function, test the format_id
+ * field for being non-zero.  Use the name field for user-visible situations and
+ * the format_id field for fixed-length fields on disk.
+ */
+/* An unknown hash function. */
+#define GIT_HASH_UNKNOWN 0
+/* SHA-1 */
+#define GIT_HASH_SHA1 1
+/* Number of algorithms supported (including unknown). */
+#define GIT_HASH_NALGOS (GIT_HASH_SHA1 + 1)
+
+typedef void (*git_hash_init_fn)(void *ctx);
+typedef void (*git_hash_update_fn)(void *ctx, const void *in, size_t len);
+typedef void (*git_hash_final_fn)(unsigned char *hash, void *ctx);
+
+struct git_hash_algo {
+       /*
+        * The name of the algorithm, as appears in the config file and in
+        * messages.
+        */
+       const char *name;
+
+       /* A four-byte version identifier, used in pack indices. */
+       uint32_t format_id;
+
+       /* The size of a hash context (e.g. git_SHA_CTX). */
+       size_t ctxsz;
+
+       /* The length of the hash in binary. */
+       size_t rawsz;
+
+       /* The length of the hash in hex characters. */
+       size_t hexsz;
+
+       /* The hash initialization function. */
+       git_hash_init_fn init_fn;
+
+       /* The hash update function. */
+       git_hash_update_fn update_fn;
+
+       /* The hash finalization function. */
+       git_hash_final_fn final_fn;
+
+       /* The OID of the empty tree. */
+       const struct object_id *empty_tree;
+
+       /* The OID of the empty blob. */
+       const struct object_id *empty_blob;
+};
+extern const struct git_hash_algo hash_algos[GIT_HASH_NALGOS];
+
 #endif
diff --git a/http.c b/http.c
index 713525f38ed3f254aed2b0dd65fece7972444072..215bebef1bfb935355a765027fc8605fccbc3d1b 100644 (file)
--- a/http.c
+++ b/http.c
@@ -12,6 +12,7 @@
 #include "gettext.h"
 #include "transport.h"
 #include "packfile.h"
+#include "protocol.h"
 
 static struct trace_key trace_curl = TRACE_KEY_INIT(CURL);
 #if LIBCURL_VERSION_NUM >= 0x070a08
@@ -898,6 +899,21 @@ static void set_from_env(const char **var, const char *envname)
                *var = val;
 }
 
+static void protocol_http_header(void)
+{
+       if (get_protocol_version_config() > 0) {
+               struct strbuf protocol_header = STRBUF_INIT;
+
+               strbuf_addf(&protocol_header, GIT_PROTOCOL_HEADER ": version=%d",
+                           get_protocol_version_config());
+
+
+               extra_http_headers = curl_slist_append(extra_http_headers,
+                                                      protocol_header.buf);
+               strbuf_release(&protocol_header);
+       }
+}
+
 void http_init(struct remote *remote, const char *url, int proactive_auth)
 {
        char *low_speed_limit;
@@ -928,6 +944,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
        if (remote)
                var_override(&http_proxy_authmethod, remote->http_proxy_authmethod);
 
+       protocol_http_header();
+
        pragma_header = curl_slist_append(http_copy_default_headers(),
                "Pragma: no-cache");
        no_pragma_header = curl_slist_append(http_copy_default_headers(),
index 3b904f0375e233db974ab49b3b31dd2fc73e842d..fca29d4799da27164394a8c59da067a0522241fe 100644 (file)
@@ -94,8 +94,12 @@ static int add_ref_decoration(const char *refname, const struct object_id *oid,
 {
        struct object *obj;
        enum decoration_type type = DECORATION_NONE;
+       struct decoration_filter *filter = (struct decoration_filter *)cb_data;
 
-       assert(cb_data == NULL);
+       if (filter && !ref_filter_match(refname,
+                             filter->include_ref_pattern,
+                             filter->exclude_ref_pattern))
+               return 0;
 
        if (starts_with(refname, git_replace_ref_base)) {
                struct object_id original_oid;
@@ -148,15 +152,23 @@ static int add_graft_decoration(const struct commit_graft *graft, void *cb_data)
        return 0;
 }
 
-void load_ref_decorations(int flags)
+void load_ref_decorations(struct decoration_filter *filter, int flags)
 {
        if (!decoration_loaded) {
-
+               if (filter) {
+                       struct string_list_item *item;
+                       for_each_string_list_item(item, filter->exclude_ref_pattern) {
+                               normalize_glob_ref(item, NULL, item->string);
+                       }
+                       for_each_string_list_item(item, filter->include_ref_pattern) {
+                               normalize_glob_ref(item, NULL, item->string);
+                       }
+               }
                decoration_loaded = 1;
                decoration_flags = flags;
-               for_each_ref(add_ref_decoration, NULL);
-               head_ref(add_ref_decoration, NULL);
-               for_each_commit_graft(add_graft_decoration, NULL);
+               for_each_ref(add_ref_decoration, filter);
+               head_ref(add_ref_decoration, filter);
+               for_each_commit_graft(add_graft_decoration, filter);
        }
 }
 
index 48f11fb740ddf670cac819cb733cbc736cc30d0e..deba035187185bc28a83520b47480c3141d8398e 100644 (file)
@@ -7,6 +7,10 @@ struct log_info {
        struct commit *commit, *parent;
 };
 
+struct decoration_filter {
+       struct string_list *include_ref_pattern, *exclude_ref_pattern;
+};
+
 int parse_decorate_color_config(const char *var, const char *slot_name, const char *value);
 void init_log_tree_opt(struct rev_info *);
 int log_tree_diff_flush(struct rev_info *);
@@ -24,7 +28,7 @@ void show_decorations(struct rev_info *opt, struct commit *commit);
 void log_write_email_headers(struct rev_info *opt, struct commit *commit,
                             const char **extra_headers_p,
                             int *need_8bit_cte_p);
-void load_ref_decorations(int flags);
+void load_ref_decorations(struct decoration_filter *filter, int flags);
 
 #define FORMAT_PATCH_NAME_MAX 64
 void fmt_output_commit(struct strbuf *, struct commit *, struct rev_info *);
index d00b274381e7fff7efd2b38f4328764105d9d7af..a4c280dfc78e943ec541f83ccb0b39921ccdf2b2 100644 (file)
@@ -2082,7 +2082,7 @@ int merge_recursive(struct merge_options *o,
                /* if there is no common ancestor, use an empty tree */
                struct tree *tree;
 
-               tree = lookup_tree(&empty_tree_oid);
+               tree = lookup_tree(the_hash_algo->empty_tree);
                merged_common_ancestors = make_virtual_commit(tree, "ancestor");
        }
 
index 4a83b0ebd5ce8b24da0b32820f5666571b099ecf..0f6573cb17cbbbc2219453bdca424fd7ebc156dc 100644 (file)
@@ -595,7 +595,7 @@ int notes_merge(struct notes_merge_options *o,
        bases = get_merge_bases(local, remote);
        if (!bases) {
                base_oid = &null_oid;
-               base_tree_oid = &empty_tree_oid;
+               base_tree_oid = the_hash_algo->empty_tree;
                if (o->verbosity >= 4)
                        printf("No merge base found; doing history-less merge\n");
        } else if (!bases->next) {
index 255b28c0988376ab7065078f411d5a53752bbf42..b75738bed4b52ac0085eb6a4919ba954d8fcfe98 100644 (file)
@@ -17,7 +17,7 @@ BEGIN
                        packet_compare_lists
                        packet_bin_read
                        packet_txt_read
-                       packet_required_key_val_read
+                       packet_key_val_read
                        packet_bin_write
                        packet_txt_write
                        packet_flush
@@ -68,28 +68,33 @@ sub packet_bin_read {
 
 sub remove_final_lf_or_die {
        my $buf = shift;
-       unless ( $buf =~ s/\n$// ) {
-               die "A non-binary line MUST be terminated by an LF.\n"
-                   . "Received: '$buf'";
+       if ( $buf =~ s/\n$// ) {
+               return $buf;
        }
-       return $buf;
+       die "A non-binary line MUST be terminated by an LF.\n"
+           . "Received: '$buf'";
 }
 
 sub packet_txt_read {
        my ( $res, $buf ) = packet_bin_read();
-       unless ( $res == -1 or $buf eq '' ) {
+       if ( $res != -1 and $buf ne '' ) {
                $buf = remove_final_lf_or_die($buf);
        }
        return ( $res, $buf );
 }
 
-sub packet_required_key_val_read {
+# Read a text packet, expecting that it is in the form "key=value" for
+# the given $key.  An EOF does not trigger any error and is reported
+# back to the caller (like packet_txt_read() does).  Die if the "key"
+# part of "key=value" does not match the given $key, or the value part
+# is empty.
+sub packet_key_val_read {
        my ( $key ) = @_;
        my ( $res, $buf ) = packet_txt_read();
-       unless ( $res == -1 or ( $buf =~ s/^$key=// and $buf ne '' ) ) {
-               die "bad $key: '$buf'";
+       if ( $res == -1 or ( $buf =~ s/^$key=// and $buf ne '' ) ) {
+               return ( $res, $buf );
        }
-       return ( $res, $buf );
+       die "bad $key: '$buf'";
 }
 
 sub packet_bin_write {
index 93ea311443a37b81b85a72dc569c715417d7ef29..2827ca772a3703f71bc588d0f6cacd5caa318fe7 100644 (file)
@@ -188,6 +188,12 @@ static int packet_write_gently(const int fd_out, const char *buf, size_t size)
        return 0;
 }
 
+void packet_write(int fd_out, const char *buf, size_t size)
+{
+       if (packet_write_gently(fd_out, buf, size))
+               die_errno("packet write failed");
+}
+
 void packet_buf_write(struct strbuf *buf, const char *fmt, ...)
 {
        va_list args;
index 66ef610fc4f7de2f6b7572f1e5fb36438c5dec19..3dad583e2d02264c4a831c939ae0e13a54de2ff6 100644 (file)
@@ -22,6 +22,7 @@
 void packet_flush(int fd);
 void packet_write_fmt(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
 void packet_buf_flush(struct strbuf *buf);
+void packet_write(int fd_out, const char *buf, size_t size);
 void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
 int packet_flush_gently(int fd);
 int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
index 2f6b0ae6c1486660bb37442b991cb1140a9f2bb6..f7ce4902301490d73bdd79bd396cf7bbe5f893ea 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -1186,11 +1186,11 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
                strbuf_addstr(sb, get_revision_mark(NULL, commit));
                return 1;
        case 'd':
-               load_ref_decorations(DECORATE_SHORT_REFS);
+               load_ref_decorations(NULL, DECORATE_SHORT_REFS);
                format_decorations(sb, commit, c->auto_color);
                return 1;
        case 'D':
-               load_ref_decorations(DECORATE_SHORT_REFS);
+               load_ref_decorations(NULL, DECORATE_SHORT_REFS);
                format_decorations_extended(sb, commit, c->auto_color, "", ", ", "");
                return 1;
        case 'g':               /* reflog info */
diff --git a/protocol.c b/protocol.c
new file mode 100644 (file)
index 0000000..43012b7
--- /dev/null
@@ -0,0 +1,79 @@
+#include "cache.h"
+#include "config.h"
+#include "protocol.h"
+
+static enum protocol_version parse_protocol_version(const char *value)
+{
+       if (!strcmp(value, "0"))
+               return protocol_v0;
+       else if (!strcmp(value, "1"))
+               return protocol_v1;
+       else
+               return protocol_unknown_version;
+}
+
+enum protocol_version get_protocol_version_config(void)
+{
+       const char *value;
+       if (!git_config_get_string_const("protocol.version", &value)) {
+               enum protocol_version version = parse_protocol_version(value);
+
+               if (version == protocol_unknown_version)
+                       die("unknown value for config 'protocol.version': %s",
+                           value);
+
+               return version;
+       }
+
+       return protocol_v0;
+}
+
+enum protocol_version determine_protocol_version_server(void)
+{
+       const char *git_protocol = getenv(GIT_PROTOCOL_ENVIRONMENT);
+       enum protocol_version version = protocol_v0;
+
+       /*
+        * Determine which protocol version the client has requested.  Since
+        * multiple 'version' keys can be sent by the client, indicating that
+        * the client is okay to speak any of them, select the greatest version
+        * that the client has requested.  This is due to the assumption that
+        * the most recent protocol version will be the most state-of-the-art.
+        */
+       if (git_protocol) {
+               struct string_list list = STRING_LIST_INIT_DUP;
+               const struct string_list_item *item;
+               string_list_split(&list, git_protocol, ':', -1);
+
+               for_each_string_list_item(item, &list) {
+                       const char *value;
+                       enum protocol_version v;
+
+                       if (skip_prefix(item->string, "version=", &value)) {
+                               v = parse_protocol_version(value);
+                               if (v > version)
+                                       version = v;
+                       }
+               }
+
+               string_list_clear(&list, 0);
+       }
+
+       return version;
+}
+
+enum protocol_version determine_protocol_version_client(const char *server_response)
+{
+       enum protocol_version version = protocol_v0;
+
+       if (skip_prefix(server_response, "version ", &server_response)) {
+               version = parse_protocol_version(server_response);
+
+               if (version == protocol_unknown_version)
+                       die("server is speaking an unknown protocol");
+               if (version == protocol_v0)
+                       die("protocol error: server explicitly said version 0");
+       }
+
+       return version;
+}
diff --git a/protocol.h b/protocol.h
new file mode 100644 (file)
index 0000000..1b2bc94
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef PROTOCOL_H
+#define PROTOCOL_H
+
+enum protocol_version {
+       protocol_unknown_version = -1,
+       protocol_v0 = 0,
+       protocol_v1 = 1,
+};
+
+/*
+ * Used by a client to determine which protocol version to request be used when
+ * communicating with a server, reflecting the configured value of the
+ * 'protocol.version' config.  If unconfigured, a value of 'protocol_v0' is
+ * returned.
+ */
+extern enum protocol_version get_protocol_version_config(void);
+
+/*
+ * Used by a server to determine which protocol version should be used based on
+ * a client's request, communicated via the 'GIT_PROTOCOL' environment variable
+ * by setting appropriate values for the key 'version'.  If a client doesn't
+ * request a particular protocol version, a default of 'protocol_v0' will be
+ * used.
+ */
+extern enum protocol_version determine_protocol_version_server(void);
+
+/*
+ * Used by a client to determine which protocol version the server is speaking
+ * based on the server's initial response.
+ */
+extern enum protocol_version determine_protocol_version_client(const char *server_response);
+
+#endif /* PROTOCOL_H */
diff --git a/refs.c b/refs.c
index 339d4318ee4500ffeb26426bdf9976979ecf739a..20ba82b4343ff2ef72cea32deec8a8d7fbd6def7 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -242,6 +242,50 @@ int ref_exists(const char *refname)
        return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, NULL, NULL);
 }
 
+static int match_ref_pattern(const char *refname,
+                            const struct string_list_item *item)
+{
+       int matched = 0;
+       if (item->util == NULL) {
+               if (!wildmatch(item->string, refname, 0))
+                       matched = 1;
+       } else {
+               const char *rest;
+               if (skip_prefix(refname, item->string, &rest) &&
+                   (!*rest || *rest == '/'))
+                       matched = 1;
+       }
+       return matched;
+}
+
+int ref_filter_match(const char *refname,
+                    const struct string_list *include_patterns,
+                    const struct string_list *exclude_patterns)
+{
+       struct string_list_item *item;
+
+       if (exclude_patterns && exclude_patterns->nr) {
+               for_each_string_list_item(item, exclude_patterns) {
+                       if (match_ref_pattern(refname, item))
+                               return 0;
+               }
+       }
+
+       if (include_patterns && include_patterns->nr) {
+               int found = 0;
+               for_each_string_list_item(item, include_patterns) {
+                       if (match_ref_pattern(refname, item)) {
+                               found = 1;
+                               break;
+                       }
+               }
+
+               if (!found)
+                       return 0;
+       }
+       return 1;
+}
+
 static int filter_refs(const char *refname, const struct object_id *oid,
                           int flags, void *data)
 {
@@ -369,6 +413,27 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data)
        return ret;
 }
 
+void normalize_glob_ref(struct string_list_item *item, const char *prefix,
+                       const char *pattern)
+{
+       struct strbuf normalized_pattern = STRBUF_INIT;
+
+       if (*pattern == '/')
+               BUG("pattern must not start with '/'");
+
+       if (prefix) {
+               strbuf_addstr(&normalized_pattern, prefix);
+       }
+       else if (!starts_with(pattern, "refs/"))
+               strbuf_addstr(&normalized_pattern, "refs/");
+       strbuf_addstr(&normalized_pattern, pattern);
+       strbuf_strip_suffix(&normalized_pattern, "/");
+
+       item->string = strbuf_detach(&normalized_pattern, NULL);
+       item->util = has_glob_specials(pattern) ? NULL : item->string;
+       strbuf_release(&normalized_pattern);
+}
+
 int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
        const char *prefix, void *cb_data)
 {
diff --git a/refs.h b/refs.h
index 18582a408c60b44ed0b74e6d6c762c7d4d93f0e9..01be5ae32fb01298ff6c0738ac4adfe42643b682 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -312,6 +312,30 @@ int for_each_namespaced_ref(each_ref_fn fn, void *cb_data);
 int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data);
 int for_each_rawref(each_ref_fn fn, void *cb_data);
 
+/*
+ * Normalizes partial refs to their fully qualified form.
+ * Will prepend <prefix> to the <pattern> if it doesn't start with 'refs/'.
+ * <prefix> will default to 'refs/' if NULL.
+ *
+ * item.string will be set to the result.
+ * item.util will be set to NULL if <pattern> contains glob characters, or
+ * non-NULL if it doesn't.
+ */
+void normalize_glob_ref(struct string_list_item *item, const char *prefix,
+                       const char *pattern);
+
+/*
+ * Returns 0 if refname matches any of the exclude_patterns, or if it doesn't
+ * match any of the include_patterns. Returns 1 otherwise.
+ *
+ * If pattern list is NULL or empty, matching against that list is skipped.
+ * This has the effect of matching everything by default, unless the user
+ * specifies rules otherwise.
+ */
+int ref_filter_match(const char *refname,
+                    const struct string_list *include_patterns,
+                    const struct string_list *exclude_patterns);
+
 static inline const char *has_glob_specials(const char *pattern)
 {
        return strpbrk(pattern, "?*[");
index bb2fae5446b7ea8d2bfac87faa30c165f714d121..998413b8bb8deaddef76d93f67be4a1435c9e3a7 100644 (file)
@@ -5,7 +5,7 @@
 
 /* The main repository */
 static struct repository the_repo = {
-       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &the_index, 0, 0
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &the_index, NULL, 0, 0
 };
 struct repository *the_repository = &the_repo;
 
@@ -64,6 +64,11 @@ void repo_set_gitdir(struct repository *repo, const char *path)
        free(old_gitdir);
 }
 
+void repo_set_hash_algo(struct repository *repo, int hash_algo)
+{
+       repo->hash_algo = &hash_algos[hash_algo];
+}
+
 /*
  * Attempt to resolve and set the provided 'gitdir' for repository 'repo'.
  * Return 0 upon success and a non-zero value upon failure.
@@ -136,6 +141,8 @@ int repo_init(struct repository *repo, const char *gitdir, const char *worktree)
        if (read_and_verify_repository_format(&format, repo->commondir))
                goto error;
 
+       repo_set_hash_algo(repo, format.hash_algo);
+
        if (worktree)
                repo_set_worktree(repo, worktree);
 
index 7f5e24a0a24011393e9b1b9986895ebe8b3f10ab..0329e40c7f5e72dad3ba46328a8e3d6c29ed8e58 100644 (file)
@@ -4,6 +4,7 @@
 struct config_set;
 struct index_state;
 struct submodule_cache;
+struct git_hash_algo;
 
 struct repository {
        /* Environment */
@@ -67,6 +68,9 @@ struct repository {
         */
        struct index_state *index;
 
+       /* Repository's current hash algorithm, as serialized on disk. */
+       const struct git_hash_algo *hash_algo;
+
        /* Configurations */
        /*
         * Bit used during initialization to indicate if repository state (like
@@ -86,6 +90,7 @@ extern struct repository *the_repository;
 
 extern void repo_set_gitdir(struct repository *repo, const char *path);
 extern void repo_set_worktree(struct repository *repo, const char *path);
+extern void repo_set_hash_algo(struct repository *repo, int algo);
 extern int repo_init(struct repository *repo, const char *gitdir, const char *worktree);
 extern int repo_submodule_init(struct repository *submodule,
                               struct repository *superproject,
index e2e691dd5a48087da0b476cf4d6cded645f23d6e..f6a3da5cd969765ada5ef536f08fb0cf4846cd39 100644 (file)
@@ -1832,7 +1832,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->simplify_by_decoration = 1;
                revs->limited = 1;
                revs->prune = 1;
-               load_ref_decorations(DECORATE_SHORT_REFS);
+               load_ref_decorations(NULL, DECORATE_SHORT_REFS);
        } else if (!strcmp(arg, "--date-order")) {
                revs->sort_order = REV_SORT_BY_COMMIT_DATE;
                revs->topo_order = 1;
index fa94ed652d2ce87fddf824aa516456b5f42735e7..e90bc316bb7a232a40c332c6d1add255d3893869 100644 (file)
@@ -347,7 +347,7 @@ static int read_oneliner(struct strbuf *buf,
 
 static struct tree *empty_tree(void)
 {
-       return lookup_tree(&empty_tree_oid);
+       return lookup_tree(the_hash_algo->empty_tree);
 }
 
 static int error_dirty_index(struct replay_opts *opts)
@@ -706,7 +706,7 @@ static int is_original_commit_empty(struct commit *commit)
                                oid_to_hex(&parent->object.oid));
                ptree_oid = &parent->tree->object.oid;
        } else {
-               ptree_oid = &empty_tree_oid; /* commit is root */
+               ptree_oid = the_hash_algo->empty_tree; /* commit is root */
        }
 
        return !oidcmp(ptree_oid, &commit->tree->object.oid);
@@ -959,7 +959,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
        } else {
                unborn = get_oid("HEAD", &head);
                if (unborn)
-                       oidcpy(&head, &empty_tree_oid);
+                       oidcpy(&head, the_hash_algo->empty_tree);
                if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD",
                                       NULL, 0))
                        return error_dirty_index(opts);
diff --git a/setup.c b/setup.c
index 94768512b7913c4e46b90fc8226c183cbd7239d7..50c6b2ab11fa3579a17079c16a4fb7241df9b875 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -434,16 +434,15 @@ static int check_repo_format(const char *var, const char *value, void *vdata)
        return 0;
 }
 
-static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
+static int check_repository_format_gently(const char *gitdir, struct repository_format *candidate, int *nongit_ok)
 {
        struct strbuf sb = STRBUF_INIT;
        struct strbuf err = STRBUF_INIT;
-       struct repository_format candidate;
        int has_common;
 
        has_common = get_common_dir(&sb, gitdir);
        strbuf_addstr(&sb, "/config");
-       read_repository_format(&candidate, sb.buf);
+       read_repository_format(candidate, sb.buf);
        strbuf_release(&sb);
 
        /*
@@ -451,10 +450,10 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
         * we treat a missing config as a silent "ok", even when nongit_ok
         * is unset.
         */
-       if (candidate.version < 0)
+       if (candidate->version < 0)
                return 0;
 
-       if (verify_repository_format(&candidate, &err) < 0) {
+       if (verify_repository_format(candidate, &err) < 0) {
                if (nongit_ok) {
                        warning("%s", err.buf);
                        strbuf_release(&err);
@@ -464,21 +463,21 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
                die("%s", err.buf);
        }
 
-       repository_format_precious_objects = candidate.precious_objects;
-       string_list_clear(&candidate.unknown_extensions, 0);
+       repository_format_precious_objects = candidate->precious_objects;
+       string_list_clear(&candidate->unknown_extensions, 0);
        if (!has_common) {
-               if (candidate.is_bare != -1) {
-                       is_bare_repository_cfg = candidate.is_bare;
+               if (candidate->is_bare != -1) {
+                       is_bare_repository_cfg = candidate->is_bare;
                        if (is_bare_repository_cfg == 1)
                                inside_work_tree = -1;
                }
-               if (candidate.work_tree) {
+               if (candidate->work_tree) {
                        free(git_work_tree_cfg);
-                       git_work_tree_cfg = candidate.work_tree;
+                       git_work_tree_cfg = candidate->work_tree;
                        inside_work_tree = -1;
                }
        } else {
-               free(candidate.work_tree);
+               free(candidate->work_tree);
        }
 
        return 0;
@@ -489,6 +488,7 @@ int read_repository_format(struct repository_format *format, const char *path)
        memset(format, 0, sizeof(*format));
        format->version = -1;
        format->is_bare = -1;
+       format->hash_algo = GIT_HASH_SHA1;
        string_list_init(&format->unknown_extensions, 1);
        git_config_from_file(check_repo_format, path, format);
        return format->version;
@@ -625,6 +625,7 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)
 
 static const char *setup_explicit_git_dir(const char *gitdirenv,
                                          struct strbuf *cwd,
+                                         struct repository_format *repo_fmt,
                                          int *nongit_ok)
 {
        const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
@@ -650,7 +651,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
                die("Not a git repository: '%s'", gitdirenv);
        }
 
-       if (check_repository_format_gently(gitdirenv, nongit_ok)) {
+       if (check_repository_format_gently(gitdirenv, repo_fmt, nongit_ok)) {
                free(gitfile);
                return NULL;
        }
@@ -723,9 +724,10 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
 
 static const char *setup_discovered_git_dir(const char *gitdir,
                                            struct strbuf *cwd, int offset,
+                                           struct repository_format *repo_fmt,
                                            int *nongit_ok)
 {
-       if (check_repository_format_gently(gitdir, nongit_ok))
+       if (check_repository_format_gently(gitdir, repo_fmt, nongit_ok))
                return NULL;
 
        /* --work-tree is set without --git-dir; use discovered one */
@@ -737,7 +739,7 @@ static const char *setup_discovered_git_dir(const char *gitdir,
                        gitdir = to_free = real_pathdup(gitdir, 1);
                if (chdir(cwd->buf))
                        die_errno("Could not come back to cwd");
-               ret = setup_explicit_git_dir(gitdir, cwd, nongit_ok);
+               ret = setup_explicit_git_dir(gitdir, cwd, repo_fmt, nongit_ok);
                free(to_free);
                return ret;
        }
@@ -769,11 +771,12 @@ static const char *setup_discovered_git_dir(const char *gitdir,
 
 /* #16.1, #17.1, #20.1, #21.1, #22.1 (see t1510) */
 static const char *setup_bare_git_dir(struct strbuf *cwd, int offset,
+                                     struct repository_format *repo_fmt,
                                      int *nongit_ok)
 {
        int root_len;
 
-       if (check_repository_format_gently(".", nongit_ok))
+       if (check_repository_format_gently(".", repo_fmt, nongit_ok))
                return NULL;
 
        setenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, "0", 1);
@@ -785,7 +788,7 @@ static const char *setup_bare_git_dir(struct strbuf *cwd, int offset,
                gitdir = offset == cwd->len ? "." : xmemdupz(cwd->buf, offset);
                if (chdir(cwd->buf))
                        die_errno("Could not come back to cwd");
-               return setup_explicit_git_dir(gitdir, cwd, nongit_ok);
+               return setup_explicit_git_dir(gitdir, cwd, repo_fmt, nongit_ok);
        }
 
        inside_git_dir = 1;
@@ -1026,6 +1029,7 @@ const char *setup_git_directory_gently(int *nongit_ok)
        static struct strbuf cwd = STRBUF_INIT;
        struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT;
        const char *prefix;
+       struct repository_format repo_fmt;
 
        /*
         * We may have read an incomplete configuration before
@@ -1053,18 +1057,18 @@ const char *setup_git_directory_gently(int *nongit_ok)
                prefix = NULL;
                break;
        case GIT_DIR_EXPLICIT:
-               prefix = setup_explicit_git_dir(gitdir.buf, &cwd, nongit_ok);
+               prefix = setup_explicit_git_dir(gitdir.buf, &cwd, &repo_fmt, nongit_ok);
                break;
        case GIT_DIR_DISCOVERED:
                if (dir.len < cwd.len && chdir(dir.buf))
                        die(_("Cannot change to '%s'"), dir.buf);
                prefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len,
-                                                 nongit_ok);
+                                                 &repo_fmt, nongit_ok);
                break;
        case GIT_DIR_BARE:
                if (dir.len < cwd.len && chdir(dir.buf))
                        die(_("Cannot change to '%s'"), dir.buf);
-               prefix = setup_bare_git_dir(&cwd, dir.len, nongit_ok);
+               prefix = setup_bare_git_dir(&cwd, dir.len, &repo_fmt, nongit_ok);
                break;
        case GIT_DIR_HIT_CEILING:
                prefix = setup_nongit(cwd.buf, nongit_ok);
@@ -1110,6 +1114,8 @@ const char *setup_git_directory_gently(int *nongit_ok)
                        repo_set_gitdir(the_repository, gitdir);
                        setup_git_env();
                }
+               if (startup_info->have_repository)
+                       repo_set_hash_algo(the_repository, repo_fmt.hash_algo);
        }
 
        strbuf_release(&dir);
@@ -1171,7 +1177,8 @@ int git_config_perm(const char *var, const char *value)
 
 void check_repository_format(void)
 {
-       check_repository_format_gently(get_git_dir(), NULL);
+       struct repository_format repo_fmt;
+       check_repository_format_gently(get_git_dir(), &repo_fmt, NULL);
        startup_info->have_repository = 1;
 }
 
index 8ae6cb6285a86fe3117200cb789bbf57a2063923..b44f5247caa9389f8d1c7643b7d9d99418d21f72 100644 (file)
@@ -39,6 +39,64 @@ const struct object_id empty_blob_oid = {
        EMPTY_BLOB_SHA1_BIN_LITERAL
 };
 
+static void git_hash_sha1_init(void *ctx)
+{
+       git_SHA1_Init((git_SHA_CTX *)ctx);
+}
+
+static void git_hash_sha1_update(void *ctx, const void *data, size_t len)
+{
+       git_SHA1_Update((git_SHA_CTX *)ctx, data, len);
+}
+
+static void git_hash_sha1_final(unsigned char *hash, void *ctx)
+{
+       git_SHA1_Final(hash, (git_SHA_CTX *)ctx);
+}
+
+static void git_hash_unknown_init(void *ctx)
+{
+       die("trying to init unknown hash");
+}
+
+static void git_hash_unknown_update(void *ctx, const void *data, size_t len)
+{
+       die("trying to update unknown hash");
+}
+
+static void git_hash_unknown_final(unsigned char *hash, void *ctx)
+{
+       die("trying to finalize unknown hash");
+}
+
+const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
+       {
+               NULL,
+               0x00000000,
+               0,
+               0,
+               0,
+               git_hash_unknown_init,
+               git_hash_unknown_update,
+               git_hash_unknown_final,
+               NULL,
+               NULL,
+       },
+       {
+               "sha-1",
+               /* "sha1", big-endian */
+               0x73686131,
+               sizeof(git_SHA_CTX),
+               GIT_SHA1_RAWSZ,
+               GIT_SHA1_HEXSZ,
+               git_hash_sha1_init,
+               git_hash_sha1_update,
+               git_hash_sha1_final,
+               &empty_tree_oid,
+               &empty_blob_oid,
+       },
+};
+
 /*
  * This is meant to hold a *small* number of objects that you would
  * want read_sha1_file() to be able to return, but yet you do not want
@@ -1164,6 +1222,9 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi,
                                    lookup_replace_object(sha1) :
                                    sha1;
 
+       if (is_null_sha1(real))
+               return -1;
+
        if (!oi)
                oi = &blank_oi;
 
index 9a2d5caf3b785ec629fcbc2325cd63e0ef72cf57..611c7d24ddee678470ba74cb3c2ca669a778b44a 100644 (file)
@@ -1438,9 +1438,19 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
                strbuf_branchname(sb, name, INTERPRET_BRANCH_LOCAL);
        else
                strbuf_addstr(sb, name);
-       if (name[0] == '-')
-               return -1;
+
+       /*
+        * This splice must be done even if we end up rejecting the
+        * name; builtin/branch.c::copy_or_rename_branch() still wants
+        * to see what the name expanded to so that "branch -m" can be
+        * used as a tool to correct earlier mistakes.
+        */
        strbuf_splice(sb, 0, 0, "refs/heads/", 11);
+
+       if (*name == '-' ||
+           !strcmp(sb->buf, "refs/heads/HEAD"))
+               return -1;
+
        return check_refname_format(sb->buf, 0);
 }
 
index bb531e0e59ce7f8fa5cde622101b7ef1b324a603..fa25888783aa9ba7ce5b290ba64ec3c1b7a87cec 100644 (file)
@@ -587,7 +587,7 @@ void show_submodule_inline_diff(struct diff_options *o, const char *path,
                struct object_id *one, struct object_id *two,
                unsigned dirty_submodule)
 {
-       const struct object_id *old = &empty_tree_oid, *new = &empty_tree_oid;
+       const struct object_id *old = the_hash_algo->empty_tree, *new = the_hash_algo->empty_tree;
        struct commit *left = NULL, *right = NULL;
        struct commit_list *merge_bases = NULL;
        struct child_process cp = CHILD_PROCESS_INIT;
@@ -1670,7 +1670,8 @@ int submodule_move_head(const char *path,
                        cp.dir = path;
 
                        prepare_submodule_repo_env(&cp.env_array);
-                       argv_array_pushl(&cp.args, "update-ref", "HEAD", new, NULL);
+                       argv_array_pushl(&cp.args, "update-ref", "HEAD",
+                                        "--no-deref", new, NULL);
 
                        if (run_command(&cp)) {
                                ret = -1;
index 4b079e4494d9324f0b03dfe8f714b3717292c0b6..b3f7b449c366c2d7d7afd12139a54b7a588f9747 100644 (file)
--- a/t/README
+++ b/t/README
@@ -332,13 +332,10 @@ Writing Tests
 -------------
 
 The test script is written as a shell script.  It should start
-with the standard "#!/bin/sh" with copyright notices, and an
+with the standard "#!/bin/sh", and an
 assignment to variable 'test_description', like this:
 
        #!/bin/sh
-       #
-       # Copyright (c) 2005 Junio C Hamano
-       #
 
        test_description='xxx test (option --frotz)
 
@@ -677,6 +674,11 @@ library for your script to use.
    <expected> file.  This behaves like "cmp" but produces more
    helpful output when the test is run with "-v" option.
 
+ - test_cmp_rev <expected> <actual>
+
+   Check whether the <expected> rev points to the same commit as the
+   <actual> rev.
+
  - test_line_count (= | -lt | -ge | ...) <length> <file>
 
    Check whether a file has the length it is expected to.
@@ -808,6 +810,18 @@ use these, and "test_set_prereq" for how to define your own.
    Git was compiled with support for PCRE. Wrap any tests
    that use git-grep --perl-regexp or git-grep -P in these.
 
+ - LIBPCRE1
+
+   Git was compiled with PCRE v1 support via
+   USE_LIBPCRE1=YesPlease. Wrap any PCRE using tests that for some
+   reason need v1 of the PCRE library instead of v2 in these.
+
+ - LIBPCRE2
+
+   Git was compiled with PCRE v2 support via
+   USE_LIBPCRE2=YesPlease. Wrap any PCRE using tests that for some
+   reason need v2 of the PCRE library instead of v1 in these.
+
  - CASE_INSENSITIVE_FS
 
    Test is run on a case insensitive file system.
index f414a3ac670fb2d77645f5d7301b6aa8f603379e..ac8368797073adfdf203f60d93df55a988443dbd 100644 (file)
@@ -5,6 +5,7 @@ static const char *usage_msg = "\n"
 "  test-date show:<format> [time_t]...\n"
 "  test-date parse [date]...\n"
 "  test-date approxidate [date]...\n"
+"  test-date timestamp [date]...\n"
 "  test-date is64bit\n"
 "  test-date time_t-is64bit\n";
 
@@ -71,6 +72,15 @@ static void parse_approxidate(const char **argv, struct timeval *now)
        }
 }
 
+static void parse_approx_timestamp(const char **argv, struct timeval *now)
+{
+       for (; *argv; argv++) {
+               timestamp_t t;
+               t = approxidate_relative(*argv, now);
+               printf("%s -> %"PRItime"\n", *argv, t);
+       }
+}
+
 int cmd_main(int argc, const char **argv)
 {
        struct timeval now;
@@ -95,6 +105,8 @@ int cmd_main(int argc, const char **argv)
                parse_dates(argv+1, &now);
        else if (!strcmp(*argv, "approxidate"))
                parse_approxidate(argv+1, &now);
+       else if (!strcmp(*argv, "timestamp"))
+               parse_approx_timestamp(argv+1, &now);
        else if (!strcmp(*argv, "is64bit"))
                return sizeof(timestamp_t) == 8 ? 0 : 1;
        else if (!strcmp(*argv, "time_t-is64bit"))
diff --git a/t/interop/i5700-protocol-transition.sh b/t/interop/i5700-protocol-transition.sh
new file mode 100755 (executable)
index 0000000..97e8e58
--- /dev/null
@@ -0,0 +1,68 @@
+#!/bin/sh
+
+VERSION_A=.
+VERSION_B=v2.0.0
+
+: ${LIB_GIT_DAEMON_PORT:=5700}
+LIB_GIT_DAEMON_COMMAND='git.b daemon'
+
+test_description='clone and fetch by client who is trying to use a new protocol'
+. ./interop-lib.sh
+. "$TEST_DIRECTORY"/lib-git-daemon.sh
+
+start_git_daemon --export-all
+
+repo=$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo
+
+test_expect_success "create repo served by $VERSION_B" '
+       git.b init "$repo" &&
+       git.b -C "$repo" commit --allow-empty -m one
+'
+
+test_expect_success "git:// clone with $VERSION_A and protocol v1" '
+       GIT_TRACE_PACKET=1 git.a -c protocol.version=1 clone "$GIT_DAEMON_URL/repo" child 2>log &&
+       git.a -C child log -1 --format=%s >actual &&
+       git.b -C "$repo" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+       grep "version=1" log
+'
+
+test_expect_success "git:// fetch with $VERSION_A and protocol v1" '
+       git.b -C "$repo" commit --allow-empty -m two &&
+       git.b -C "$repo" log -1 --format=%s >expect &&
+
+       GIT_TRACE_PACKET=1 git.a -C child -c protocol.version=1 fetch 2>log &&
+       git.a -C child log -1 --format=%s FETCH_HEAD >actual &&
+
+       test_cmp expect actual &&
+       grep "version=1" log &&
+       ! grep "version 1" log
+'
+
+stop_git_daemon
+
+test_expect_success "create repo served by $VERSION_B" '
+       git.b init parent &&
+       git.b -C parent commit --allow-empty -m one
+'
+
+test_expect_success "file:// clone with $VERSION_A and protocol v1" '
+       GIT_TRACE_PACKET=1 git.a -c protocol.version=1 clone --upload-pack="git.b upload-pack" parent child2 2>log &&
+       git.a -C child2 log -1 --format=%s >actual &&
+       git.b -C parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+       ! grep "version 1" log
+'
+
+test_expect_success "file:// fetch with $VERSION_A and protocol v1" '
+       git.b -C parent commit --allow-empty -m two &&
+       git.b -C parent log -1 --format=%s >expect &&
+
+       GIT_TRACE_PACKET=1 git.a -C child2 -c protocol.version=1 fetch --upload-pack="git.b upload-pack" 2>log &&
+       git.a -C child2 log -1 --format=%s FETCH_HEAD >actual &&
+
+       test_cmp expect actual &&
+       ! grep "version 1" log
+'
+
+test_done
index 0642ae7e6ef0fbbd9e7eacdebd061d258c8b4222..df19436314b7c6d16833044c5c53ba90eae62225 100644 (file)
@@ -67,6 +67,9 @@ LockFile accept.lock
 <IfModule !mod_unixd.c>
        LoadModule unixd_module modules/mod_unixd.so
 </IfModule>
+<IfModule !mod_setenvif.c>
+       LoadModule setenvif_module modules/mod_setenvif.so
+</IfModule>
 </IfVersion>
 
 PassEnv GIT_VALGRIND
@@ -76,6 +79,10 @@ PassEnv ASAN_OPTIONS
 PassEnv GIT_TRACE
 PassEnv GIT_CONFIG_NOSYSTEM
 
+<IfVersion >= 2.4>
+       SetEnvIf Git-Protocol ".*" GIT_PROTOCOL=$0
+</IfVersion>
+
 Alias /dumb/ www/
 Alias /auth/dumb/ www/auth/dumb/
 
index bb94c2320958eb1d1a319899697ecefc881e3043..38dadd2c2902f3621d54168d32a54d7863497968 100755 (executable)
@@ -554,6 +554,10 @@ test_submodule_switch_common() {
 #  - if succeeds, once "git submodule update" is invoked, the contents of
 #    submodule directories are updated
 #
+# If the command under test is known to not work with submodules in certain
+# conditions, set the appropriate KNOWN_FAILURE_* variable used in the tests
+# below to 1.
+#
 # Use as follows:
 #
 # my_func () {
@@ -622,19 +626,11 @@ test_submodule_forced_switch () {
 # - Removing a submodule with a git directory absorbs the submodules
 #   git directory first into the superproject.
 
-test_submodule_switch_recursing_with_args () {
-       cmd_args="$1"
-       command="git $cmd_args --recurse-submodules"
-       RESULTDS=success
-       if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1
-       then
-               RESULTDS=failure
-       fi
-       RESULTOI=success
-       if test "$KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED" = 1
-       then
-               RESULTOI=failure
-       fi
+# Internal function; use test_submodule_switch_recursing_with_args() or
+# test_submodule_forced_switch_recursing_with_args() instead.
+test_submodule_recursing_with_args_common() {
+       command="$1"
+
        ######################### Appearing submodule #########################
        # Switching to a commit letting a submodule appear checks it out ...
        test_expect_success "$command: added submodule is checked out" '
@@ -648,7 +644,7 @@ test_submodule_switch_recursing_with_args () {
                        test_submodule_content sub1 origin/add_sub1
                )
        '
-       # ... ignoring an empty existing directory ...
+       # ... ignoring an empty existing directory.
        test_expect_success "$command: added submodule is checked out in empty dir" '
                prolog &&
                reset_work_tree_to_interested no_submodule &&
@@ -661,34 +657,23 @@ test_submodule_switch_recursing_with_args () {
                        test_submodule_content sub1 origin/add_sub1
                )
        '
-       # ... unless there is an untracked file in its place.
-       test_expect_success "$command: added submodule doesn't remove untracked file with same name" '
-               prolog &&
-               reset_work_tree_to_interested no_submodule &&
-               (
-                       cd submodule_update &&
-                       git branch -t add_sub1 origin/add_sub1 &&
-                       : >sub1 &&
-                       test_must_fail $command add_sub1 &&
-                       test_superproject_content origin/no_submodule &&
-                       test_must_be_empty sub1
-               )
-       '
-       # ... but an ignored file is fine.
-       test_expect_$RESULTOI "$command: added submodule removes an untracked ignored file" '
-               test_when_finished "rm submodule_update/.git/info/exclude" &&
+       test_expect_success "$command: submodule branch is not changed, detach HEAD instead" '
                prolog &&
-               reset_work_tree_to_interested no_submodule &&
+               reset_work_tree_to_interested add_sub1 &&
                (
                        cd submodule_update &&
-                       git branch -t add_sub1 origin/add_sub1 &&
-                       : >sub1 &&
-                       echo sub1 >.git/info/exclude
-                       $command add_sub1 &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
+                       git -C sub1 checkout -b keep_branch &&
+                       git -C sub1 rev-parse HEAD >expect &&
+                       git branch -t check-keep origin/modify_sub1 &&
+                       $command check-keep &&
+                       test_superproject_content origin/modify_sub1 &&
+                       test_submodule_content sub1 origin/modify_sub1 &&
+                       git -C sub1 rev-parse keep_branch >actual &&
+                       test_cmp expect actual &&
+                       test_must_fail git -C sub1 symbolic-ref HEAD
                )
        '
+
        # Replacing a tracked file with a submodule produces a checked out submodule
        test_expect_success "$command: replace tracked file with submodule checks out submodule" '
                prolog &&
@@ -742,33 +727,6 @@ test_submodule_switch_recursing_with_args () {
                        test_git_directory_exists sub1
                )
        '
-       # Replacing a submodule with files in a directory must succeeds
-       # when the submodule is clean
-       test_expect_$RESULTDS "$command: replace submodule with a directory" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
-                       $command replace_sub1_with_directory &&
-                       test_superproject_content origin/replace_sub1_with_directory &&
-                       test_submodule_content sub1 origin/replace_sub1_with_directory
-               )
-       '
-       # ... absorbing a .git directory.
-       test_expect_$RESULTDS "$command: replace submodule containing a .git directory with a directory must absorb the git dir" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
-                       replace_gitfile_with_git_dir sub1 &&
-                       rm -rf .git/modules &&
-                       $command replace_sub1_with_directory &&
-                       test_superproject_content origin/replace_sub1_with_directory &&
-                       test_git_directory_exists sub1
-               )
-       '
 
        # Replacing it with a file ...
        test_expect_success "$command: replace submodule with a file" '
@@ -782,7 +740,11 @@ test_submodule_switch_recursing_with_args () {
                        test -f sub1
                )
        '
-
+       RESULTDS=success
+       if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1
+       then
+               RESULTDS=failure
+       fi
        # ... must check its local work tree for untracked files
        test_expect_$RESULTDS "$command: replace submodule with a file must fail with untracked files" '
                prolog &&
@@ -794,21 +756,7 @@ test_submodule_switch_recursing_with_args () {
                        test_must_fail $command replace_sub1_with_file &&
                        test_superproject_content origin/add_sub1 &&
                        test_submodule_content sub1 origin/add_sub1
-               )
-       '
-
-       # ... and ignored files are ignored
-       test_expect_success "$command: replace submodule with a file works ignores ignored files in submodule" '
-               test_when_finished "rm submodule_update/.git/modules/sub1/info/exclude" &&
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
-                       : >sub1/ignored &&
-                       $command replace_sub1_with_file &&
-                       test_superproject_content origin/replace_sub1_with_file &&
-                       test -f sub1
+                       test -f sub1/untracked_file
                )
        '
 
@@ -825,19 +773,6 @@ test_submodule_switch_recursing_with_args () {
                        test_submodule_content sub1 origin/modify_sub1
                )
        '
-
-       test_expect_success "git -c submodule.recurse=true $cmd_args: modified submodule updates submodule work tree" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t modify_sub1 origin/modify_sub1 &&
-                       git -c submodule.recurse=true $cmd_args modify_sub1 &&
-                       test_superproject_content origin/modify_sub1 &&
-                       test_submodule_content sub1 origin/modify_sub1
-               )
-       '
-
        # Updating a submodule to an invalid sha1 doesn't update the
        # superproject nor the submodule's work tree.
        test_expect_success "$command: updating to a missing submodule commit fails" '
@@ -851,126 +786,166 @@ test_submodule_switch_recursing_with_args () {
                        test_submodule_content sub1 origin/add_sub1
                )
        '
-
-       # recursing deeper than one level doesn't work yet.
-       test_expect_success "$command: modified submodule updates submodule recursively" '
-               prolog &&
-               reset_work_tree_to_interested add_nested_sub &&
-               (
-                       cd submodule_update &&
-                       git branch -t modify_sub1_recursively origin/modify_sub1_recursively &&
-                       $command modify_sub1_recursively &&
-                       test_superproject_content origin/modify_sub1_recursively &&
-                       test_submodule_content sub1 origin/modify_sub1_recursively &&
-                       test_submodule_content -C sub1 sub2 origin/modify_sub1_recursively
-               )
-       '
 }
 
-# Test that submodule contents are updated when switching between commits
-# that change a submodule, but throwing away local changes in
-# the superproject as well as the submodule is allowed.
-test_submodule_forced_switch_recursing_with_args () {
+# Declares and invokes several tests that, in various situations, checks that
+# the provided Git command, when invoked with --recurse-submodules:
+#  - succeeds in updating the worktree and index of a superproject to a target
+#    commit, or fails atomically (depending on the test situation)
+#  - if succeeds, the contents of submodule directories are updated
+#
+# Specify the Git command so that "git $GIT_COMMAND --recurse-submodules"
+# works.
+#
+# If the command under test is known to not work with submodules in certain
+# conditions, set the appropriate KNOWN_FAILURE_* variable used in the tests
+# below to 1.
+#
+# Use as follows:
+#
+# test_submodule_switch_recursing_with_args "$GIT_COMMAND"
+test_submodule_switch_recursing_with_args () {
        cmd_args="$1"
        command="git $cmd_args --recurse-submodules"
-       RESULT=success
+       test_submodule_recursing_with_args_common "$command"
+
+       RESULTDS=success
        if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1
        then
-               RESULT=failure
+               RESULTDS=failure
        fi
-       ######################### Appearing submodule #########################
-       # Switching to a commit letting a submodule appear creates empty dir ...
-       test_expect_success "$command: added submodule is checked out" '
+       RESULTOI=success
+       if test "$KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED" = 1
+       then
+               RESULTOI=failure
+       fi
+       # Switching to a commit letting a submodule appear cannot override an
+       # untracked file.
+       test_expect_success "$command: added submodule doesn't remove untracked file with same name" '
                prolog &&
                reset_work_tree_to_interested no_submodule &&
                (
                        cd submodule_update &&
                        git branch -t add_sub1 origin/add_sub1 &&
-                       $command add_sub1 &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
+                       : >sub1 &&
+                       test_must_fail $command add_sub1 &&
+                       test_superproject_content origin/no_submodule &&
+                       test_must_be_empty sub1
                )
        '
-       # ... and doesn't care if it already exists ...
-       test_expect_success "$command: added submodule ignores empty directory" '
+       # ... but an ignored file is fine.
+       test_expect_$RESULTOI "$command: added submodule removes an untracked ignored file" '
+               test_when_finished "rm submodule_update/.git/info/exclude" &&
                prolog &&
                reset_work_tree_to_interested no_submodule &&
                (
                        cd submodule_update &&
                        git branch -t add_sub1 origin/add_sub1 &&
-                       mkdir sub1 &&
+                       : >sub1 &&
+                       echo sub1 >.git/info/exclude
                        $command add_sub1 &&
                        test_superproject_content origin/add_sub1 &&
                        test_submodule_content sub1 origin/add_sub1
                )
        '
-       # ... not caring about an untracked file either
-       test_expect_success "$command: added submodule does remove untracked unignored file with same name when forced" '
+
+       # Replacing a submodule with files in a directory must succeeds
+       # when the submodule is clean
+       test_expect_$RESULTDS "$command: replace submodule with a directory" '
                prolog &&
-               reset_work_tree_to_interested no_submodule &&
+               reset_work_tree_to_interested add_sub1 &&
                (
                        cd submodule_update &&
-                       git branch -t add_sub1 origin/add_sub1 &&
-                       >sub1 &&
-                       $command add_sub1 &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
+                       git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
+                       $command replace_sub1_with_directory &&
+                       test_superproject_content origin/replace_sub1_with_directory &&
+                       test_submodule_content sub1 origin/replace_sub1_with_directory
                )
        '
-       # Replacing a tracked file with a submodule checks out the submodule
-       test_expect_success "$command: replace tracked file with submodule populates the submodule" '
+       # ... absorbing a .git directory.
+       test_expect_$RESULTDS "$command: replace submodule containing a .git directory with a directory must absorb the git dir" '
                prolog &&
-               reset_work_tree_to_interested replace_sub1_with_file &&
+               reset_work_tree_to_interested add_sub1 &&
                (
                        cd submodule_update &&
-                       git branch -t replace_file_with_sub1 origin/replace_file_with_sub1 &&
-                       $command replace_file_with_sub1 &&
-                       test_superproject_content origin/replace_file_with_sub1 &&
-                       test_submodule_content sub1 origin/replace_file_with_sub1
+                       git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
+                       replace_gitfile_with_git_dir sub1 &&
+                       rm -rf .git/modules &&
+                       $command replace_sub1_with_directory &&
+                       test_superproject_content origin/replace_sub1_with_directory &&
+                       test_git_directory_exists sub1
                )
        '
-       # ... as does removing a directory with tracked files with a
-       # submodule.
-       test_expect_success "$command: replace directory with submodule" '
+
+       # ... and ignored files are ignored
+       test_expect_success "$command: replace submodule with a file works ignores ignored files in submodule" '
+               test_when_finished "rm submodule_update/.git/modules/sub1/info/exclude" &&
                prolog &&
-               reset_work_tree_to_interested replace_sub1_with_directory &&
+               reset_work_tree_to_interested add_sub1 &&
                (
                        cd submodule_update &&
-                       git branch -t replace_directory_with_sub1 origin/replace_directory_with_sub1 &&
-                       $command replace_directory_with_sub1 &&
-                       test_superproject_content origin/replace_directory_with_sub1 &&
-                       test_submodule_content sub1 origin/replace_directory_with_sub1
+                       git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
+                       : >sub1/ignored &&
+                       $command replace_sub1_with_file &&
+                       test_superproject_content origin/replace_sub1_with_file &&
+                       test -f sub1
                )
        '
 
-       ######################## Disappearing submodule #######################
-       # Removing a submodule doesn't remove its work tree ...
-       test_expect_success "$command: removed submodule leaves submodule directory and its contents in place" '
+       test_expect_success "git -c submodule.recurse=true $cmd_args: modified submodule updates submodule work tree" '
                prolog &&
                reset_work_tree_to_interested add_sub1 &&
                (
                        cd submodule_update &&
-                       git branch -t remove_sub1 origin/remove_sub1 &&
-                       $command remove_sub1 &&
-                       test_superproject_content origin/remove_sub1 &&
-                       ! test -e sub1
+                       git branch -t modify_sub1 origin/modify_sub1 &&
+                       git -c submodule.recurse=true $cmd_args modify_sub1 &&
+                       test_superproject_content origin/modify_sub1 &&
+                       test_submodule_content sub1 origin/modify_sub1
                )
        '
-       # ... especially when it contains a .git directory.
-       test_expect_success "$command: removed submodule leaves submodule containing a .git directory alone" '
+
+       # recursing deeper than one level doesn't work yet.
+       test_expect_success "$command: modified submodule updates submodule recursively" '
                prolog &&
-               reset_work_tree_to_interested add_sub1 &&
+               reset_work_tree_to_interested add_nested_sub &&
                (
                        cd submodule_update &&
-                       git branch -t remove_sub1 origin/remove_sub1 &&
-                       replace_gitfile_with_git_dir sub1 &&
-                       rm -rf .git/modules/sub1 &&
-                       $command remove_sub1 &&
-                       test_superproject_content origin/remove_sub1 &&
-                       test_git_directory_exists sub1 &&
-                       ! test -e sub1
+                       git branch -t modify_sub1_recursively origin/modify_sub1_recursively &&
+                       $command modify_sub1_recursively &&
+                       test_superproject_content origin/modify_sub1_recursively &&
+                       test_submodule_content sub1 origin/modify_sub1_recursively &&
+                       test_submodule_content -C sub1 sub2 origin/modify_sub1_recursively
+               )
+       '
+}
+
+# Same as test_submodule_switch_recursing_with_args(), except that throwing
+# away local changes in the superproject is allowed.
+test_submodule_forced_switch_recursing_with_args () {
+       cmd_args="$1"
+       command="git $cmd_args --recurse-submodules"
+       test_submodule_recursing_with_args_common "$command"
+
+       RESULT=success
+       if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1
+       then
+               RESULT=failure
+       fi
+       # Switching to a commit letting a submodule appear does not care about
+       # an untracked file.
+       test_expect_success "$command: added submodule does remove untracked unignored file with same name when forced" '
+               prolog &&
+               reset_work_tree_to_interested no_submodule &&
+               (
+                       cd submodule_update &&
+                       git branch -t add_sub1 origin/add_sub1 &&
+                       >sub1 &&
+                       $command add_sub1 &&
+                       test_superproject_content origin/add_sub1 &&
+                       test_submodule_content sub1 origin/add_sub1
                )
        '
+
        # Replacing a submodule with files in a directory ...
        test_expect_success "$command: replace submodule with a directory" '
                prolog &&
@@ -997,17 +972,6 @@ test_submodule_forced_switch_recursing_with_args () {
                        test_git_directory_exists sub1
                )
        '
-       # Replacing it with a file
-       test_expect_success "$command: replace submodule with a file" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
-                       $command replace_sub1_with_file &&
-                       test_superproject_content origin/replace_sub1_with_file
-               )
-       '
 
        # ... even if the submodule contains ignored files
        test_expect_success "$command: replace submodule with a file ignoring ignored files" '
@@ -1022,46 +986,6 @@ test_submodule_forced_switch_recursing_with_args () {
                )
        '
 
-       # ... but stops for untracked files that would be lost
-       test_expect_$RESULT "$command: replace submodule with a file stops for untracked files" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
-                       : >sub1/untracked_file &&
-                       test_must_fail $command replace_sub1_with_file &&
-                       test_superproject_content origin/add_sub1 &&
-                       test -f sub1/untracked_file
-               )
-       '
-
-       ########################## Modified submodule #########################
-       # Updating a submodule sha1 updates the submodule's work tree
-       test_expect_success "$command: modified submodule updates submodule work tree" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t modify_sub1 origin/modify_sub1 &&
-                       $command modify_sub1 &&
-                       test_superproject_content origin/modify_sub1 &&
-                       test_submodule_content sub1 origin/modify_sub1
-               )
-       '
-       # Updating a submodule to an invalid sha1 doesn't update the
-       # submodule's work tree, subsequent update will fail
-       test_expect_success "$command: modified submodule does not update submodule work tree to invalid commit" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t invalid_sub1 origin/invalid_sub1 &&
-                       test_must_fail $command invalid_sub1 &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
-               )
-       '
        # Updating a submodule from an invalid sha1 updates
        test_expect_success "$command: modified submodule does update submodule work tree from invalid commit" '
                prolog &&
index 1dbc85b21407dcfc83d9dc6a56e8e0f21c748c0c..e40120848837c1ef78cf75d1724c69226823db15 100755 (executable)
@@ -69,12 +69,17 @@ sub format_times {
        @tests = glob "p????-*.sh";
 }
 
+my $resultsdir = "test-results";
+if ($ENV{GIT_PERF_SUBSECTION} ne "") {
+       $resultsdir .= "/" . $ENV{GIT_PERF_SUBSECTION};
+}
+
 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";
+       my $fname = "$resultsdir/$t.subtests";
        open my $fp, "<", $fname or die "cannot open $fname: $!";
        for (<$fp>) {
                chomp;
@@ -98,7 +103,7 @@ sub read_descr {
 my %descrs;
 my $descrlen = 4; # "Test"
 for my $t (@subtests) {
-       $descrs{$t} = $shorttests{$t}.": ".read_descr("test-results/$t.descr");
+       $descrs{$t} = $shorttests{$t}.": ".read_descr("$resultsdir/$t.descr");
        $descrlen = length $descrs{$t} if length $descrs{$t}>$descrlen;
 }
 
@@ -138,7 +143,7 @@ sub have_slash {
        my $firstr;
        for my $i (0..$#dirs) {
                my $d = $dirs[$i];
-               $times{$prefixes{$d}.$t} = [get_times("test-results/$prefixes{$d}$t.times")];
+               $times{$prefixes{$d}.$t} = [get_times("$resultsdir/$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];
diff --git a/t/perf/lib-pack.sh b/t/perf/lib-pack.sh
new file mode 100644 (file)
index 0000000..d3865db
--- /dev/null
@@ -0,0 +1,25 @@
+# Helpers for dealing with large numbers of packs.
+
+# create $1 nonsense packs, each with a single blob
+create_packs () {
+       perl -le '
+               my ($n) = @ARGV;
+               for (1..$n) {
+                       print "blob";
+                       print "data <<EOF";
+                       print "$_";
+                       print "EOF";
+                       print "checkpoint"
+               }
+       ' "$@" |
+       git fast-import
+}
+
+# create a large number of packs, disabling any gc which might
+# cause us to repack them
+setup_many_packs () {
+       git config gc.auto 0 &&
+       git config gc.autopacklimit 0 &&
+       git config fastimport.unpacklimit 0 &&
+       create_packs 500
+}
index a5dc39f86af65d6f6b200b95c4f3c66d5f482ec1..d0e0e019ea364cb1cef3863d256aa5596e01eb56 100755 (executable)
@@ -20,6 +20,7 @@ start to show a noticeable performance problem on my machine, but without
 taking too long to set up and run the tests.
 '
 . ./perf-lib.sh
+. "$TEST_DIRECTORY/perf/lib-pack.sh"
 
 # make a long nonsense history on branch $1, consisting of $2 commits, each
 # with a unique file pointing to the blob at $2.
@@ -44,26 +45,6 @@ create_tags () {
        git update-ref --stdin
 }
 
-# create $1 nonsense packs, each with a single blob
-create_packs () {
-       perl -le '
-               my ($n) = @ARGV;
-               for (1..$n) {
-                       print "blob";
-                       print "data <<EOF";
-                       print "$_";
-                       print "EOF";
-               }
-       ' "$@" |
-       git fast-import &&
-
-       git cat-file --batch-all-objects --batch-check='%(objectname)' |
-       while read sha1
-       do
-               echo $sha1 | git pack-objects .git/objects/pack/pack
-       done
-}
-
 test_expect_success 'create parent and child' '
        git init parent &&
        git -C parent commit --allow-empty -m base &&
@@ -84,9 +65,7 @@ test_expect_success 'populate parent tags' '
 test_expect_success 'create child packs' '
        (
                cd child &&
-               git config gc.auto 0 &&
-               git config gc.autopacklimit 0 &&
-               create_packs 500
+               setup_many_packs
        )
 '
 
diff --git a/t/perf/p5551-fetch-rescan.sh b/t/perf/p5551-fetch-rescan.sh
new file mode 100755 (executable)
index 0000000..b99dc23
--- /dev/null
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+test_description='fetch performance with many packs
+
+It is common for fetch to consider objects that we might not have, and it is an
+easy mistake for the code to use a function like `parse_object` that might
+give the correct _answer_ on such an object, but do so slowly (due to
+re-scanning the pack directory for lookup failures).
+
+The resulting performance drop can be hard to notice in a real repository, but
+becomes quite large in a repository with a large number of packs. So this
+test creates a more pathological case, since any mistakes would produce a more
+noticeable slowdown.
+'
+. ./perf-lib.sh
+. "$TEST_DIRECTORY"/perf/lib-pack.sh
+
+test_expect_success 'create parent and child' '
+       git init parent &&
+       git clone parent child
+'
+
+
+test_expect_success 'create refs in the parent' '
+       (
+               cd parent &&
+               git commit --allow-empty -m foo &&
+               head=$(git rev-parse HEAD) &&
+               test_seq 1000 |
+               sed "s,.*,update refs/heads/& $head," |
+               $MODERN_GIT update-ref --stdin
+       )
+'
+
+test_expect_success 'create many packs in the child' '
+       (
+               cd child &&
+               setup_many_packs
+       )
+'
+
+test_perf 'fetch' '
+       # start at the same state for each iteration
+       obj=$($MODERN_GIT -C parent rev-parse HEAD) &&
+       (
+               cd child &&
+               $MODERN_GIT for-each-ref --format="delete %(refname)" refs/remotes |
+               $MODERN_GIT update-ref --stdin &&
+               rm -vf .git/objects/$(echo $obj | sed "s|^..|&/|") &&
+
+               git fetch
+       )
+'
+
+test_done
index b50211b2591d19904fa3c995b01aea2121db476a..e4c343a6b795b6f990fa278788ad65a0a5cdc4e6 100644 (file)
@@ -56,12 +56,10 @@ MODERN_GIT=$GIT_BUILD_DIR/bin-wrappers/git
 export MODERN_GIT
 
 perf_results_dir=$TEST_OUTPUT_DIRECTORY/test-results
+test -n "$GIT_PERF_SUBSECTION" && perf_results_dir="$perf_results_dir/$GIT_PERF_SUBSECTION"
 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
index beb4acc0e428d20280a649c428da8b0b3aa5b4c6..43e4de49ef2bea9ae09b502f28fbc0913e294571 100755 (executable)
@@ -2,9 +2,14 @@
 
 case "$1" in
        --help)
-               echo "usage: $0 [other_git_tree...] [--] [test_scripts]"
+               echo "usage: $0 [--config file] [other_git_tree...] [--] [test_scripts]"
                exit 0
                ;;
+       --config)
+               shift
+               GIT_PERF_CONFIG_FILE=$(cd "$(dirname "$1")"; pwd)/$(basename "$1")
+               export GIT_PERF_CONFIG_FILE
+               shift ;;
 esac
 
 die () {
@@ -29,8 +34,10 @@ unpack_git_rev () {
        (cd "$(git rev-parse --show-cdup)" && git archive --format=tar $rev) |
        (cd build/$rev && tar x)
 }
+
 build_git_rev () {
        rev=$1
+       name="$2"
        for config in config.mak config.mak.autogen config.status
        do
                if test -e "../../$config"
@@ -38,7 +45,7 @@ build_git_rev () {
                        cp "../../$config" "build/$rev/"
                fi
        done
-       echo "=== Building $rev ==="
+       echo "=== Building $rev ($name) ==="
        (
                cd build/$rev &&
                if test -n "$GIT_PERF_MAKE_COMMAND"
@@ -65,7 +72,7 @@ run_dirs_helper () {
                if [ ! -d build/$rev ]; then
                        unpack_git_rev $rev
                fi
-               build_git_rev $rev
+               build_git_rev $rev "$mydir"
                mydir=build/$rev
        fi
        if test "$mydir" = .; then
@@ -87,14 +94,78 @@ run_dirs () {
        done
 }
 
-GIT_PERF_AGGREGATING_LATER=t
-export GIT_PERF_AGGREGATING_LATER
+get_subsections () {
+       section="$1"
+       test -z "$GIT_PERF_CONFIG_FILE" && return
+       git config -f "$GIT_PERF_CONFIG_FILE" --name-only --get-regex "$section\..*\.[^.]+" |
+       sed -e "s/$section\.\(.*\)\..*/\1/" | sort | uniq
+}
+
+get_var_from_env_or_config () {
+       env_var="$1"
+       conf_sec="$2"
+       conf_var="$3"
+       # $4 can be set to a default value
+
+       # Do nothing if the env variable is already set
+       eval "test -z \"\${$env_var+x}\"" || return
+
+       test -z "$GIT_PERF_CONFIG_FILE" && return
+
+       # Check if the variable is in the config file
+       if test -n "$GIT_PERF_SUBSECTION"
+       then
+               var="$conf_sec.$GIT_PERF_SUBSECTION.$conf_var"
+               conf_value=$(git config -f "$GIT_PERF_CONFIG_FILE" "$var") &&
+               eval "$env_var=\"$conf_value\"" && return
+       fi
+       var="$conf_sec.$conf_var"
+       conf_value=$(git config -f "$GIT_PERF_CONFIG_FILE" "$var") &&
+       eval "$env_var=\"$conf_value\"" && return
+
+       test -n "${4+x}" && eval "$env_var=\"$4\""
+}
+
+run_subsection () {
+       get_var_from_env_or_config "GIT_PERF_REPEAT_COUNT" "perf" "repeatCount" 3
+       export GIT_PERF_REPEAT_COUNT
+
+       get_var_from_env_or_config "GIT_PERF_DIRS_OR_REVS" "perf" "dirsOrRevs"
+       set -- $GIT_PERF_DIRS_OR_REVS "$@"
+
+       get_var_from_env_or_config "GIT_PERF_MAKE_COMMAND" "perf" "makeCommand"
+       get_var_from_env_or_config "GIT_PERF_MAKE_OPTS" "perf" "makeOpts"
+
+       GIT_PERF_AGGREGATING_LATER=t
+       export GIT_PERF_AGGREGATING_LATER
+
+       if test $# = 0 -o "$1" = -- -o -f "$1"; then
+               set -- . "$@"
+       fi
+
+       run_dirs "$@"
+       ./aggregate.perl "$@"
+}
 
 cd "$(dirname $0)"
 . ../../GIT-BUILD-OPTIONS
 
-if test $# = 0 -o "$1" = -- -o -f "$1"; then
-       set -- . "$@"
+mkdir -p test-results
+get_subsections "perf" >test-results/run_subsections.names
+
+if test $(wc -l <test-results/run_subsections.names) -eq 0
+then
+       (
+               run_subsection "$@"
+       )
+else
+       while read -r subsec
+       do
+               (
+                       GIT_PERF_SUBSECTION="$subsec"
+                       export GIT_PERF_SUBSECTION
+                       echo "======== Run for subsection '$GIT_PERF_SUBSECTION' ========"
+                       run_subsection "$@"
+               )
+       done <test-results/run_subsections.names
 fi
-run_dirs "$@"
-./aggregate.perl "$@"
index 6fd7fa476be2a091f683174d57100cbc7ba19fb8..f1678851de954dd93ab0e2fc7912813ad5f09960 100644 (file)
@@ -70,7 +70,7 @@ sub rot13 {
 $debug->flush();
 
 while (1) {
-       my ( $res, $command ) = packet_required_key_val_read("command");
+       my ( $res, $command ) = packet_key_val_read("command");
        if ( $res == -1 ) {
                print $debug "STOP\n";
                exit();
@@ -106,7 +106,7 @@ sub rot13 {
                packet_txt_write("status=success");
                packet_flush();
        } else {
-               my ( $res, $pathname ) = packet_required_key_val_read("pathname");
+               my ( $res, $pathname ) = packet_key_val_read("pathname");
                if ( $res == -1 ) {
                        die "unexpected EOF while expecting pathname";
                }
index 364a537000bbbdd43047bd3f9c52c950a96dcbda..cbeb9bebeea67c3654c279b2c12fed4825840d8f 100755 (executable)
@@ -901,6 +901,36 @@ test_expect_success 'get --path barfs on boolean variable' '
        test_must_fail git config --get --path path.bool
 '
 
+test_expect_success 'get --expiry-date' '
+       rel="3.weeks.5.days.00:00" &&
+       rel_out="$rel ->" &&
+       cat >.git/config <<-\EOF &&
+       [date]
+       valid1 = "3.weeks.5.days 00:00"
+       valid2 = "Fri Jun 4 15:46:55 2010"
+       valid3 = "2017/11/11 11:11:11PM"
+       valid4 = "2017/11/10 09:08:07 PM"
+       valid5 = "never"
+       invalid1 = "abc"
+       EOF
+       cat >expect <<-EOF &&
+       $(test-date timestamp $rel)
+       1275666415
+       1510441871
+       1510348087
+       0
+       EOF
+       {
+               echo "$rel_out $(git config --expiry-date date.valid1)"
+               git config --expiry-date date.valid2 &&
+               git config --expiry-date date.valid3 &&
+               git config --expiry-date date.valid4 &&
+               git config --expiry-date date.valid5
+       } >actual &&
+       test_cmp expect actual &&
+       test_must_fail git config --expiry-date date.invalid1
+'
+
 cat > expect << EOF
 [quote]
        leading = " test"
index e88349c8a0483b97ea38013de61d799003fd85b5..c7878a60edfdcf24f3c45367f58e62b0ce6dab71 100755 (executable)
@@ -331,4 +331,47 @@ test_expect_success 'update-ref --stdin -z fails delete with bad ref name' '
        grep "fatal: invalid ref format: ~a" err
 '
 
+test_expect_success 'branch rejects HEAD as a branch name' '
+       test_must_fail git branch HEAD HEAD^ &&
+       test_must_fail git show-ref refs/heads/HEAD
+'
+
+test_expect_success 'checkout -b rejects HEAD as a branch name' '
+       test_must_fail git checkout -B HEAD HEAD^ &&
+       test_must_fail git show-ref refs/heads/HEAD
+'
+
+test_expect_success 'update-ref can operate on refs/heads/HEAD' '
+       git update-ref refs/heads/HEAD HEAD^ &&
+       git show-ref refs/heads/HEAD &&
+       git update-ref -d refs/heads/HEAD &&
+       test_must_fail git show-ref refs/heads/HEAD
+'
+
+test_expect_success 'branch -d can remove refs/heads/HEAD' '
+       git update-ref refs/heads/HEAD HEAD^ &&
+       git branch -d HEAD &&
+       test_must_fail git show-ref refs/heads/HEAD
+'
+
+test_expect_success 'branch -m can rename refs/heads/HEAD' '
+       git update-ref refs/heads/HEAD HEAD^ &&
+       git branch -m HEAD tail &&
+       test_must_fail git show-ref refs/heads/HEAD &&
+       git show-ref refs/heads/tail
+'
+
+test_expect_success 'branch -d can remove refs/heads/-dash' '
+       git update-ref refs/heads/-dash HEAD^ &&
+       git branch -d -- -dash &&
+       test_must_fail git show-ref refs/heads/-dash
+'
+
+test_expect_success 'branch -m can rename refs/heads/-dash' '
+       git update-ref refs/heads/-dash HEAD^ &&
+       git branch -m -- -dash dash &&
+       test_must_fail git show-ref refs/heads/-dash &&
+       git show-ref refs/heads/dash
+'
+
 test_done
index 3b1ac1971af1d972b77b3dfe8ae4a56e00e1fa55..39c7f2ebd740db097d67b12a68fa35f3fa884fc8 100755 (executable)
@@ -804,6 +804,99 @@ test_expect_success 'push -m shows right message' '
        test_cmp expect actual
 '
 
+test_expect_success 'push -m also works without space' '
+       >foo &&
+       git add foo &&
+       git stash push -m"unspaced test message" &&
+       echo "stash@{0}: On master: unspaced test message" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'store -m foo shows right message' '
+       git stash clear &&
+       git reset --hard &&
+       echo quux >bazzy &&
+       git add bazzy &&
+       STASH_ID=$(git stash create) &&
+       git stash store -m "store m" $STASH_ID &&
+       echo "stash@{0}: store m" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'store -mfoo shows right message' '
+       git stash clear &&
+       git reset --hard &&
+       echo quux >bazzy &&
+       git add bazzy &&
+       STASH_ID=$(git stash create) &&
+       git stash store -m"store mfoo" $STASH_ID &&
+       echo "stash@{0}: store mfoo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'store --message=foo shows right message' '
+       git stash clear &&
+       git reset --hard &&
+       echo quux >bazzy &&
+       git add bazzy &&
+       STASH_ID=$(git stash create) &&
+       git stash store --message="store message=foo" $STASH_ID &&
+       echo "stash@{0}: store message=foo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'store --message foo shows right message' '
+       git stash clear &&
+       git reset --hard &&
+       echo quux >bazzy &&
+       git add bazzy &&
+       STASH_ID=$(git stash create) &&
+       git stash store --message "store message foo" $STASH_ID &&
+       echo "stash@{0}: store message foo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'push -mfoo uses right message' '
+       >foo &&
+       git add foo &&
+       git stash push -m"test mfoo" &&
+       echo "stash@{0}: On master: test mfoo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'push --message foo is synonym for -mfoo' '
+       >foo &&
+       git add foo &&
+       git stash push --message "test message foo" &&
+       echo "stash@{0}: On master: test message foo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'push --message=foo is synonym for -mfoo' '
+       >foo &&
+       git add foo &&
+       git stash push --message="test message=foo" &&
+       echo "stash@{0}: On master: test message=foo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'push -m shows right message' '
+       >foo &&
+       git add foo &&
+       git stash push -m "test m foo" &&
+       echo "stash@{0}: On master: test m foo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'create stores correct message' '
        >foo &&
        git add foo &&
index 3e6b485ecba1a59e3d69edc3ac5a6f0f1d327bd3..2d76a971c43f6850a1f443eab3e91bf3762fe952 100755 (executable)
@@ -85,6 +85,10 @@ test_expect_success 'setup' '
 
 check_diff changed_hello 'changed function'
 
+test_expect_success ' context includes comment' '
+       grep "^ .*Hello comment" changed_hello.diff
+'
+
 test_expect_success ' context includes begin' '
        grep "^ .*Begin of hello" changed_hello.diff
 '
index 63b1a1e4efbb4c5dee315ba56a6ad0658da0a9a5..73e767e1783d8ab97d7a45297a173b1bed9f00a2 100644 (file)
@@ -1,4 +1,7 @@
 
+/*
+ * Hello comment.
+ */
 static void hello(void)        // Begin of hello
 {
        /*
index 8f155da7a50a657db9fd092e24639f67cd03cd24..25b1f8cc73bc35bedaaffeacbb75a1329d725c3b 100755 (executable)
@@ -737,6 +737,107 @@ test_expect_success 'log.decorate configuration' '
 
 '
 
+test_expect_success 'decorate-refs with glob' '
+       cat >expect.decorate <<-\EOF &&
+       Merge-tag-reach
+       Merge-tags-octopus-a-and-octopus-b
+       seventh
+       octopus-b (octopus-b)
+       octopus-a (octopus-a)
+       reach
+       EOF
+       git log -n6 --decorate=short --pretty="tformat:%f%d" \
+               --decorate-refs="heads/octopus*" >actual &&
+       test_cmp expect.decorate actual
+'
+
+test_expect_success 'decorate-refs without globs' '
+       cat >expect.decorate <<-\EOF &&
+       Merge-tag-reach
+       Merge-tags-octopus-a-and-octopus-b
+       seventh
+       octopus-b
+       octopus-a
+       reach (tag: reach)
+       EOF
+       git log -n6 --decorate=short --pretty="tformat:%f%d" \
+               --decorate-refs="tags/reach" >actual &&
+       test_cmp expect.decorate actual
+'
+
+test_expect_success 'multiple decorate-refs' '
+       cat >expect.decorate <<-\EOF &&
+       Merge-tag-reach
+       Merge-tags-octopus-a-and-octopus-b
+       seventh
+       octopus-b (octopus-b)
+       octopus-a (octopus-a)
+       reach (tag: reach)
+       EOF
+       git log -n6 --decorate=short --pretty="tformat:%f%d" \
+               --decorate-refs="heads/octopus*" \
+               --decorate-refs="tags/reach" >actual &&
+    test_cmp expect.decorate actual
+'
+
+test_expect_success 'decorate-refs-exclude with glob' '
+       cat >expect.decorate <<-\EOF &&
+       Merge-tag-reach (HEAD -> master)
+       Merge-tags-octopus-a-and-octopus-b
+       seventh (tag: seventh)
+       octopus-b (tag: octopus-b)
+       octopus-a (tag: octopus-a)
+       reach (tag: reach, reach)
+       EOF
+       git log -n6 --decorate=short --pretty="tformat:%f%d" \
+               --decorate-refs-exclude="heads/octopus*" >actual &&
+       test_cmp expect.decorate actual
+'
+
+test_expect_success 'decorate-refs-exclude without globs' '
+       cat >expect.decorate <<-\EOF &&
+       Merge-tag-reach (HEAD -> master)
+       Merge-tags-octopus-a-and-octopus-b
+       seventh (tag: seventh)
+       octopus-b (tag: octopus-b, octopus-b)
+       octopus-a (tag: octopus-a, octopus-a)
+       reach (reach)
+       EOF
+       git log -n6 --decorate=short --pretty="tformat:%f%d" \
+               --decorate-refs-exclude="tags/reach" >actual &&
+       test_cmp expect.decorate actual
+'
+
+test_expect_success 'multiple decorate-refs-exclude' '
+       cat >expect.decorate <<-\EOF &&
+       Merge-tag-reach (HEAD -> master)
+       Merge-tags-octopus-a-and-octopus-b
+       seventh (tag: seventh)
+       octopus-b (tag: octopus-b)
+       octopus-a (tag: octopus-a)
+       reach (reach)
+       EOF
+       git log -n6 --decorate=short --pretty="tformat:%f%d" \
+               --decorate-refs-exclude="heads/octopus*" \
+               --decorate-refs-exclude="tags/reach" >actual &&
+       test_cmp expect.decorate actual
+'
+
+test_expect_success 'decorate-refs and decorate-refs-exclude' '
+       cat >expect.decorate <<-\EOF &&
+       Merge-tag-reach (master)
+       Merge-tags-octopus-a-and-octopus-b
+       seventh
+       octopus-b
+       octopus-a
+       reach (reach)
+       EOF
+       git log -n6 --decorate=short --pretty="tformat:%f%d" \
+               --decorate-refs="heads/*" \
+               --decorate-refs-exclude="heads/oc*" >actual &&
+       test_cmp expect.decorate actual
+'
+
 test_expect_success 'log.decorate config parsing' '
        git log --oneline --decorate=full >expect.full &&
        git log --oneline --decorate=short >expect.short &&
index 50e40abb11e1d317a2a58321f1007dde925c8da2..0f895478f02bcf4cc4408ce065e79e0314b66793 100755 (executable)
@@ -306,23 +306,21 @@ test_expect_success 'clone checking out a tag' '
        test_cmp fetch.expected fetch.actual
 '
 
-setup_ssh_wrapper () {
-       test_expect_success 'setup ssh wrapper' '
-               rm -f "$TRASH_DIRECTORY/ssh-wrapper$X" &&
-               cp "$GIT_BUILD_DIR/t/helper/test-fake-ssh$X" \
-                       "$TRASH_DIRECTORY/ssh-wrapper$X" &&
-               GIT_SSH="$TRASH_DIRECTORY/ssh-wrapper$X" &&
-               export GIT_SSH &&
-               export TRASH_DIRECTORY &&
-               >"$TRASH_DIRECTORY"/ssh-output
-       '
-}
+test_expect_success 'set up ssh wrapper' '
+       cp "$GIT_BUILD_DIR/t/helper/test-fake-ssh$X" \
+               "$TRASH_DIRECTORY/ssh$X" &&
+       GIT_SSH="$TRASH_DIRECTORY/ssh$X" &&
+       export GIT_SSH &&
+       export TRASH_DIRECTORY &&
+       >"$TRASH_DIRECTORY"/ssh-output
+'
 
 copy_ssh_wrapper_as () {
        rm -f "${1%$X}$X" &&
-       cp "$TRASH_DIRECTORY/ssh-wrapper$X" "${1%$X}$X" &&
+       cp "$TRASH_DIRECTORY/ssh$X" "${1%$X}$X" &&
+       test_when_finished "rm $(git rev-parse --sq-quote "${1%$X}$X")" &&
        GIT_SSH="${1%$X}$X" &&
-       export GIT_SSH
+       test_when_finished "GIT_SSH=\"\$TRASH_DIRECTORY/ssh\$X\""
 }
 
 expect_ssh () {
@@ -346,8 +344,6 @@ expect_ssh () {
        (cd "$TRASH_DIRECTORY" && test_cmp ssh-expect ssh-output)
 }
 
-setup_ssh_wrapper
-
 test_expect_success 'clone myhost:src uses ssh' '
        git clone myhost:src ssh-clone &&
        expect_ssh myhost src
@@ -364,9 +360,52 @@ test_expect_success 'bracketed hostnames are still ssh' '
        expect_ssh "-p 123" myhost src
 '
 
-test_expect_success 'uplink is not treated as putty' '
+test_expect_success 'OpenSSH variant passes -4' '
+       git clone -4 "[myhost:123]:src" ssh-ipv4-clone &&
+       expect_ssh "-4 -p 123" myhost src
+'
+
+test_expect_success 'variant can be overridden' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/putty" &&
+       git -c ssh.variant=putty clone -4 "[myhost:123]:src" ssh-putty-clone &&
+       expect_ssh "-4 -P 123" myhost src
+'
+
+test_expect_success 'variant=auto picks based on basename' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" &&
+       git -c ssh.variant=auto clone -4 "[myhost:123]:src" ssh-auto-clone &&
+       expect_ssh "-4 -P 123" myhost src
+'
+
+test_expect_success 'simple does not support -4/-6' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/simple" &&
+       test_must_fail git clone -4 "myhost:src" ssh-4-clone-simple
+'
+
+test_expect_success 'simple does not support port' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/simple" &&
+       test_must_fail git clone "[myhost:123]:src" ssh-bracket-clone-simple
+'
+
+test_expect_success 'uplink is treated as simple' '
        copy_ssh_wrapper_as "$TRASH_DIRECTORY/uplink" &&
-       git clone "[myhost:123]:src" ssh-bracket-clone-uplink &&
+       test_must_fail git clone "[myhost:123]:src" ssh-bracket-clone-uplink &&
+       git clone "myhost:src" ssh-clone-uplink &&
+       expect_ssh myhost src
+'
+
+test_expect_success 'OpenSSH-like uplink is treated as ssh' '
+       write_script "$TRASH_DIRECTORY/uplink" <<-EOF &&
+       if test "\$1" = "-G"
+       then
+               exit 0
+       fi &&
+       exec "\$TRASH_DIRECTORY/ssh$X" "\$@"
+       EOF
+       test_when_finished "rm -f \"\$TRASH_DIRECTORY/uplink\"" &&
+       GIT_SSH="$TRASH_DIRECTORY/uplink" &&
+       test_when_finished "GIT_SSH=\"\$TRASH_DIRECTORY/ssh\$X\"" &&
+       git clone "[myhost:123]:src" ssh-bracket-clone-sshlike-uplink &&
        expect_ssh "-p 123" myhost src
 '
 
@@ -418,12 +457,14 @@ test_expect_success 'ssh.variant overrides plink detection' '
 '
 
 test_expect_success 'GIT_SSH_VARIANT overrides plink detection to plink' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" &&
        GIT_SSH_VARIANT=plink \
        git clone "[myhost:123]:src" ssh-bracket-clone-variant-3 &&
        expect_ssh "-P 123" myhost src
 '
 
 test_expect_success 'GIT_SSH_VARIANT overrides plink to tortoiseplink' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" &&
        GIT_SSH_VARIANT=tortoiseplink \
        git clone "[myhost:123]:src" ssh-bracket-clone-variant-4 &&
        expect_ssh "-batch -P 123" myhost src
@@ -435,9 +476,6 @@ test_expect_success 'clean failure on broken quoting' '
                git clone "[myhost:123]:src" sq-failure
 '
 
-# Reset the GIT_SSH environment variable for clone tests.
-setup_ssh_wrapper
-
 counter=0
 # $1 url
 # $2 none|host
index d5af7581297d6167b82d029a015e367c057ff7af..13b5e5eb9b9fa2aa96e0496eb9fa3e7049fdfb51 100755 (executable)
@@ -11,7 +11,9 @@ test_expect_success 'setup ssh wrapper' '
        git upload-pack "$TRASH_DIRECTORY"
        EOF
        GIT_SSH="$TRASH_DIRECTORY/ssh-wrapper" &&
+       GIT_SSH_VARIANT=ssh &&
        export GIT_SSH &&
+       export GIT_SSH_VARIANT &&
        export TRASH_DIRECTORY
 '
 
diff --git a/t/t5700-protocol-v1.sh b/t/t5700-protocol-v1.sh
new file mode 100755 (executable)
index 0000000..ba86a44
--- /dev/null
@@ -0,0 +1,294 @@
+#!/bin/sh
+
+test_description='test git wire-protocol transition'
+
+TEST_NO_CREATE_REPO=1
+
+. ./test-lib.sh
+
+# Test protocol v1 with 'git://' transport
+#
+. "$TEST_DIRECTORY"/lib-git-daemon.sh
+start_git_daemon --export-all --enable=receive-pack
+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent
+
+test_expect_success 'create repo to be served by git-daemon' '
+       git init "$daemon_parent" &&
+       test_commit -C "$daemon_parent" one
+'
+
+test_expect_success 'clone with git:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -c protocol.version=1 \
+               clone "$GIT_DAEMON_URL/parent" daemon_child 2>log &&
+
+       git -C daemon_child log -1 --format=%s >actual &&
+       git -C "$daemon_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Client requested to use protocol v1
+       grep "clone> .*\\\0\\\0version=1\\\0$" log &&
+       # Server responded using protocol v1
+       grep "clone< version 1" log
+'
+
+test_expect_success 'fetch with git:// using protocol v1' '
+       test_commit -C "$daemon_parent" two &&
+
+       GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \
+               fetch 2>log &&
+
+       git -C daemon_child log -1 --format=%s origin/master >actual &&
+       git -C "$daemon_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Client requested to use protocol v1
+       grep "fetch> .*\\\0\\\0version=1\\\0$" log &&
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'pull with git:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \
+               pull 2>log &&
+
+       git -C daemon_child log -1 --format=%s >actual &&
+       git -C "$daemon_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Client requested to use protocol v1
+       grep "fetch> .*\\\0\\\0version=1\\\0$" log &&
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'push with git:// using protocol v1' '
+       test_commit -C daemon_child three &&
+
+       # Push to another branch, as the target repository has the
+       # master branch checked out and we cannot push into it.
+       GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \
+               push origin HEAD:client_branch 2>log &&
+
+       git -C daemon_child log -1 --format=%s >actual &&
+       git -C "$daemon_parent" log -1 --format=%s client_branch >expect &&
+       test_cmp expect actual &&
+
+       # Client requested to use protocol v1
+       grep "push> .*\\\0\\\0version=1\\\0$" log &&
+       # Server responded using protocol v1
+       grep "push< version 1" log
+'
+
+stop_git_daemon
+
+# Test protocol v1 with 'file://' transport
+#
+test_expect_success 'create repo to be served by file:// transport' '
+       git init file_parent &&
+       test_commit -C file_parent one
+'
+
+test_expect_success 'clone with file:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -c protocol.version=1 \
+               clone "file://$(pwd)/file_parent" file_child 2>log &&
+
+       git -C file_child log -1 --format=%s >actual &&
+       git -C file_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "clone< version 1" log
+'
+
+test_expect_success 'fetch with file:// using protocol v1' '
+       test_commit -C file_parent two &&
+
+       GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \
+               fetch 2>log &&
+
+       git -C file_child log -1 --format=%s origin/master >actual &&
+       git -C file_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'pull with file:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \
+               pull 2>log &&
+
+       git -C file_child log -1 --format=%s >actual &&
+       git -C file_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'push with file:// using protocol v1' '
+       test_commit -C file_child three &&
+
+       # Push to another branch, as the target repository has the
+       # master branch checked out and we cannot push into it.
+       GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \
+               push origin HEAD:client_branch 2>log &&
+
+       git -C file_child log -1 --format=%s >actual &&
+       git -C file_parent log -1 --format=%s client_branch >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "push< version 1" log
+'
+
+# Test protocol v1 with 'ssh://' transport
+#
+test_expect_success 'setup ssh wrapper' '
+       GIT_SSH="$GIT_BUILD_DIR/t/helper/test-fake-ssh" &&
+       export GIT_SSH &&
+       GIT_SSH_VARIANT=ssh &&
+       export GIT_SSH_VARIANT &&
+       export TRASH_DIRECTORY &&
+       >"$TRASH_DIRECTORY"/ssh-output
+'
+
+expect_ssh () {
+       test_when_finished '(cd "$TRASH_DIRECTORY" && rm -f ssh-expect && >ssh-output)' &&
+       echo "ssh: -o SendEnv=GIT_PROTOCOL myhost $1 '$PWD/ssh_parent'" >"$TRASH_DIRECTORY/ssh-expect" &&
+       (cd "$TRASH_DIRECTORY" && test_cmp ssh-expect ssh-output)
+}
+
+test_expect_success 'create repo to be served by ssh:// transport' '
+       git init ssh_parent &&
+       test_commit -C ssh_parent one
+'
+
+test_expect_success 'clone with ssh:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -c protocol.version=1 \
+               clone "ssh://myhost:$(pwd)/ssh_parent" ssh_child 2>log &&
+       expect_ssh git-upload-pack &&
+
+       git -C ssh_child log -1 --format=%s >actual &&
+       git -C ssh_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "clone< version 1" log
+'
+
+test_expect_success 'fetch with ssh:// using protocol v1' '
+       test_commit -C ssh_parent two &&
+
+       GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \
+               fetch 2>log &&
+       expect_ssh git-upload-pack &&
+
+       git -C ssh_child log -1 --format=%s origin/master >actual &&
+       git -C ssh_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'pull with ssh:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \
+               pull 2>log &&
+       expect_ssh git-upload-pack &&
+
+       git -C ssh_child log -1 --format=%s >actual &&
+       git -C ssh_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'push with ssh:// using protocol v1' '
+       test_commit -C ssh_child three &&
+
+       # Push to another branch, as the target repository has the
+       # master branch checked out and we cannot push into it.
+       GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \
+               push origin HEAD:client_branch 2>log &&
+       expect_ssh git-receive-pack &&
+
+       git -C ssh_child log -1 --format=%s >actual &&
+       git -C ssh_parent log -1 --format=%s client_branch >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "push< version 1" log
+'
+
+# Test protocol v1 with 'http://' transport
+#
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+test_expect_success 'create repo to be served by http:// transport' '
+       git init "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+       git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config http.receivepack true &&
+       test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" one
+'
+
+test_expect_success 'clone with http:// using protocol v1' '
+       GIT_TRACE_PACKET=1 GIT_TRACE_CURL=1 git -c protocol.version=1 \
+               clone "$HTTPD_URL/smart/http_parent" http_child 2>log &&
+
+       git -C http_child log -1 --format=%s >actual &&
+       git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Client requested to use protocol v1
+       grep "Git-Protocol: version=1" log &&
+       # Server responded using protocol v1
+       grep "git< version 1" log
+'
+
+test_expect_success 'fetch with http:// using protocol v1' '
+       test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" two &&
+
+       GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \
+               fetch 2>log &&
+
+       git -C http_child log -1 --format=%s origin/master >actual &&
+       git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "git< version 1" log
+'
+
+test_expect_success 'pull with http:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \
+               pull 2>log &&
+
+       git -C http_child log -1 --format=%s >actual &&
+       git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "git< version 1" log
+'
+
+test_expect_success 'push with http:// using protocol v1' '
+       test_commit -C http_child three &&
+
+       # Push to another branch, as the target repository has the
+       # master branch checked out and we cannot push into it.
+       GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \
+               push origin HEAD:client_branch && #2>log &&
+
+       git -C http_child log -1 --format=%s >actual &&
+       git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s client_branch >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "git< version 1" log
+'
+
+stop_httpd
+
+test_done
index 865168ec6a7be7fc7778419f2dd1066e9c80c1bc..f5f46a95b4be9bca4ca30e70cbe75051a191c96a 100755 (executable)
@@ -214,6 +214,44 @@ test_expect_success TTY 'git tag as alias respects pager.tag with -l' '
        ! test -e paginated.out
 '
 
+test_expect_success TTY 'git branch defaults to paging' '
+       rm -f paginated.out &&
+       test_terminal git branch &&
+       test -e paginated.out
+'
+
+test_expect_success TTY 'git branch respects pager.branch' '
+       rm -f paginated.out &&
+       test_terminal git -c pager.branch=false branch &&
+       ! test -e paginated.out
+'
+
+test_expect_success TTY 'git branch respects --no-pager' '
+       rm -f paginated.out &&
+       test_terminal git --no-pager branch &&
+       ! test -e paginated.out
+'
+
+test_expect_success TTY 'git branch --edit-description ignores pager.branch' '
+       rm -f paginated.out editor.used &&
+       write_script editor <<-\EOF &&
+               echo "New description" >"$1"
+               touch editor.used
+       EOF
+       EDITOR=./editor test_terminal git -c pager.branch branch --edit-description &&
+       ! test -e paginated.out &&
+       test -e editor.used
+'
+
+test_expect_success TTY 'git branch --set-upstream-to ignores pager.branch' '
+       rm -f paginated.out &&
+       git branch other &&
+       test_when_finished "git branch -D other" &&
+       test_terminal git -c pager.branch branch --set-upstream-to=other &&
+       test_when_finished "git branch --unset-upstream" &&
+       ! test -e paginated.out
+'
+
 # A colored commit log will begin with an appropriate ANSI escape
 # for the first color; the text "commit" comes later.
 colorful() {
index 2a6679c2f596fb13a34786a4aa4b954e61c61113..1797f632a388d16f23a47c7693c7a3f0f0deabf6 100755 (executable)
@@ -60,6 +60,18 @@ test_expect_success setup '
                echo " line with leading space3"
                echo "line without leading space2"
        } >space &&
+       cat >hello.ps1 <<-\EOF &&
+       # No-op.
+       function dummy() {}
+
+       # Say hello.
+       function hello() {
+         echo "Hello world."
+       } # hello
+
+       # Still a no-op.
+       function dummy() {}
+       EOF
        git add . &&
        test_tick &&
        git commit -m initial
@@ -766,18 +778,27 @@ test_expect_success 'grep -W shows no trailing empty lines' '
        test_cmp expected actual
 '
 
-cat >expected <<EOF
-hello.c=       printf("Hello world.\n");
-hello.c:       return 0;
-hello.c-       /* char ?? */
-EOF
-
 test_expect_success 'grep -W with userdiff' '
        test_when_finished "rm -f .gitattributes" &&
-       git config diff.custom.xfuncname "(printf.*|})$" &&
-       echo "hello.c diff=custom" >.gitattributes &&
-       git grep -W return >actual &&
-       test_cmp expected actual
+       git config diff.custom.xfuncname "^function .*$" &&
+       echo "hello.ps1 diff=custom" >.gitattributes &&
+       git grep -W echo >function-context-userdiff-actual
+'
+
+test_expect_success ' includes preceding comment' '
+       grep "# Say hello" function-context-userdiff-actual
+'
+
+test_expect_success ' includes function line' '
+       grep "=function hello" function-context-userdiff-actual
+'
+
+test_expect_success ' includes matching line' '
+       grep ":  echo" function-context-userdiff-actual
+'
+
+test_expect_success ' includes last line of the function' '
+       grep "} # hello" function-context-userdiff-actual
 '
 
 for threads in $(test_seq 0 10)
@@ -1110,6 +1131,12 @@ test_expect_success PCRE 'grep -P pattern' '
        test_cmp expected actual
 '
 
+test_expect_success LIBPCRE2 "grep -P with (*NO_JIT) doesn't error out" '
+       git grep -P "(*NO_JIT)\p{Ps}.*?\p{Pe}" hello.c >actual &&
+       test_cmp expected actual
+
+'
+
 test_expect_success !PCRE 'grep -P pattern errors without PCRE' '
        test_must_fail git grep -P "foo.*bar"
 '
index 116bd6a70cf727f8dff496b9340b85146208e778..e7065df2bb028aff76f3169d84873a1a3d789571 100644 (file)
@@ -1028,6 +1028,8 @@ test -z "$NO_PERL" && test_set_prereq PERL
 test -z "$NO_PTHREADS" && test_set_prereq PTHREADS
 test -z "$NO_PYTHON" && test_set_prereq PYTHON
 test -n "$USE_LIBPCRE1$USE_LIBPCRE2" && test_set_prereq PCRE
+test -n "$USE_LIBPCRE1" && test_set_prereq LIBPCRE1
+test -n "$USE_LIBPCRE2" && test_set_prereq LIBPCRE2
 test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
 
 # Can we rely on git's output in the C locale?
index 6d5f3c0d39a886f5010115927d71f205a96d0e04..d5de18127c63a9d017578e0969743392d39526d4 100644 (file)
@@ -18,6 +18,7 @@
 #include "parse-options.h"
 #include "argv-array.h"
 #include "prio-queue.h"
+#include "protocol.h"
 
 static const char * const upload_pack_usage[] = {
        N_("git upload-pack [<options>] <dir>"),
@@ -1066,6 +1067,23 @@ int cmd_main(int argc, const char **argv)
                die("'%s' does not appear to be a git repository", dir);
 
        git_config(upload_pack_config, NULL);
-       upload_pack();
+
+       switch (determine_protocol_version_server()) {
+       case protocol_v1:
+               /*
+                * v1 is just the original protocol with a version string,
+                * so just fall through after writing the version string.
+                */
+               if (advertise_refs || !stateless_rpc)
+                       packet_write_fmt(1, "version 1\n");
+
+               /* fallthrough */
+       case protocol_v0:
+               upload_pack();
+               break;
+       case protocol_unknown_version:
+               BUG("unknown protocol version");
+       }
+
        return 0;
 }
index 6881445e4afc3482ba008ebbdab81b51167863fc..7778dc2b190f84fe2ab1e1539756a7e1f4254c3b 100644 (file)
@@ -121,6 +121,12 @@ static long match_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri,
        return xecfg->find_func(rec, len, buf, sz, xecfg->find_func_priv);
 }
 
+static int is_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri)
+{
+       char dummy[1];
+       return match_func_rec(xdf, xecfg, ri, dummy, sizeof(dummy)) >= 0;
+}
+
 struct func_line {
        long len;
        char buf[80];
@@ -178,7 +184,6 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
 
                        /* Appended chunk? */
                        if (i1 >= xe->xdf1.nrec) {
-                               char dummy[1];
                                long i2 = xch->i2;
 
                                /*
@@ -186,8 +191,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                                 * a whole function was added.
                                 */
                                while (i2 < xe->xdf2.nrec) {
-                                       if (match_func_rec(&xe->xdf2, xecfg, i2,
-                                               dummy, sizeof(dummy)) >= 0)
+                                       if (is_func_rec(&xe->xdf2, xecfg, i2))
                                                goto post_context_calculation;
                                        i2++;
                                }
@@ -200,6 +204,9 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                        }
 
                        fs1 = get_func_line(xe, xecfg, NULL, i1, -1);
+                       while (fs1 > 0 && !is_empty_rec(&xe->xdf1, fs1 - 1) &&
+                              !is_func_rec(&xe->xdf1, xecfg, fs1 - 1))
+                               fs1--;
                        if (fs1 < 0)
                                fs1 = 0;
                        if (fs1 < s1) {