summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/RelNotes/2.15.2.txt47
-rw-r--r--Documentation/RelNotes/2.16.0.txt58
-rw-r--r--Documentation/config.txt58
-rw-r--r--Documentation/git-branch.txt4
-rw-r--r--Documentation/git-checkout.txt10
-rw-r--r--Documentation/git-config.txt5
-rw-r--r--Documentation/git-log.txt7
-rw-r--r--Documentation/git-notes.txt2
-rw-r--r--Documentation/git-prune.txt9
-rw-r--r--Documentation/git-reflog.txt4
-rw-r--r--Documentation/git-send-email.txt6
-rw-r--r--Documentation/git-stash.txt4
-rw-r--r--Documentation/git.txt15
-rw-r--r--Documentation/githooks.txt20
-rw-r--r--Documentation/gitrepository-layout.txt4
-rw-r--r--Documentation/technical/http-protocol.txt8
-rw-r--r--Documentation/technical/pack-protocol.txt43
-rw-r--r--Makefile1
-rw-r--r--builtin/am.c2
-rw-r--r--builtin/checkout.c2
-rw-r--r--builtin/config.c10
-rw-r--r--builtin/diff.c2
-rw-r--r--builtin/log.c10
-rw-r--r--builtin/notes.c2
-rw-r--r--builtin/prune.c2
-rw-r--r--builtin/pull.c2
-rw-r--r--builtin/receive-pack.c17
-rw-r--r--builtin/reflog.c14
-rw-r--r--builtin/submodule--helper.c2
-rw-r--r--cache.h22
-rw-r--r--config.c10
-rw-r--r--config.h1
-rw-r--r--connect.c572
-rw-r--r--contrib/completion/git-completion.bash1
-rw-r--r--daemon.c71
-rw-r--r--diff-lib.c2
-rw-r--r--fetch-pack.c3
-rwxr-xr-xgenerate-cmdlist.sh2
-rw-r--r--git-gui/Makefile2
-rwxr-xr-xgit-send-email.perl4
-rwxr-xr-xgit-stash.sh18
-rw-r--r--grep.c26
-rw-r--r--hash.h57
-rw-r--r--http.c18
-rw-r--r--log-tree.c24
-rw-r--r--log-tree.h6
-rw-r--r--merge-recursive.c2
-rw-r--r--notes-merge.c2
-rw-r--r--perl/Git/Packet.pm25
-rw-r--r--pkt-line.c6
-rw-r--r--pkt-line.h1
-rw-r--r--pretty.c4
-rw-r--r--progress.c24
-rw-r--r--protocol.c79
-rw-r--r--protocol.h33
-rw-r--r--refs.c65
-rw-r--r--refs.h24
-rw-r--r--repository.c9
-rw-r--r--repository.h5
-rw-r--r--revision.c2
-rw-r--r--sequencer.c6
-rw-r--r--setup.c49
-rw-r--r--sha1_file.c61
-rw-r--r--submodule.c5
-rw-r--r--t/README22
-rw-r--r--t/helper/test-date.c12
-rwxr-xr-xt/interop/i5700-protocol-transition.sh68
-rw-r--r--t/lib-httpd/apache.conf7
-rwxr-xr-xt/lib-submodule-update.sh350
-rwxr-xr-xt/perf/aggregate.perl11
-rw-r--r--t/perf/lib-pack.sh25
-rwxr-xr-xt/perf/p5550-fetch-tags.sh25
-rwxr-xr-xt/perf/p5551-fetch-rescan.sh55
-rw-r--r--t/perf/perf-lib.sh4
-rwxr-xr-xt/perf/run89
-rw-r--r--t/t0021/rot13-filter.pl4
-rwxr-xr-xt/t1300-repo-config.sh30
-rwxr-xr-xt/t3903-stash.sh93
-rwxr-xr-xt/t4202-log.sh101
-rwxr-xr-xt/t5601-clone.sh78
-rwxr-xr-xt/t5603-clone-dirname.sh2
-rwxr-xr-xt/t5700-protocol-v1.sh294
-rwxr-xr-xt/t7810-grep.sh6
-rw-r--r--t/test-lib.sh2
-rw-r--r--upload-pack.c20
85 files changed, 2280 insertions, 629 deletions
diff --git a/Documentation/RelNotes/2.15.2.txt b/Documentation/RelNotes/2.15.2.txt
new file mode 100644
index 0000000000..9f7e28f8a2
--- /dev/null
+++ b/Documentation/RelNotes/2.15.2.txt
@@ -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.
diff --git a/Documentation/RelNotes/2.16.0.txt b/Documentation/RelNotes/2.16.0.txt
index c617e37dd8..431bd5e34a 100644
--- a/Documentation/RelNotes/2.16.0.txt
+++ b/Documentation/RelNotes/2.16.0.txt
@@ -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.
@@ -90,6 +89,13 @@ UI, Workflows & Features
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.
@@ -130,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.
@@ -206,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.
@@ -226,39 +238,31 @@ 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.
@@ -266,12 +270,40 @@ Fixes since v2.15
* 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).
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 531649cb40..c1598ee703 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -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
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 520c53b5e8..b3084c99c1 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -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`.
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index e108b0f74b..ca5fc9c798 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -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,
@@ -272,11 +274,11 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
commit, your HEAD becomes "detached" and you are no longer on
any branch (see below for details).
+
-As a special case, the `"@{-N}"` syntax for the N-th last branch/commit
-checks out branches (instead of detaching). You may also specify
-`-` which is synonymous with `"@{-1}"`.
+You can use the `"@{-N}"` syntax to refer to the N-th last
+branch/commit checked out using "git checkout" operation. You may
+also specify `-` which is synonymous to `"@{-1}`.
+
-As a further special case, you may use `"A...B"` as a shortcut for the
+As a special case, you may use `"A...B"` as a shortcut for the
merge base of `A` and `B` if there is exactly one merge base. You can
leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
index 4edd09fc6b..14da5fc157 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -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
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index 32246fdb00..5437f8b0f0 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -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.
diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 43677297f3..e8dec1b3c8 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -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
diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt
index 7a493c80f7..a37c0af931 100644
--- a/Documentation/git-prune.txt
+++ b/Documentation/git-prune.txt
@@ -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
diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt
index 44c736f1a8..472a6808cd 100644
--- a/Documentation/git-reflog.txt
+++ b/Documentation/git-reflog.txt
@@ -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
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index bac9014ac7..8060ea35c5 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -203,9 +203,9 @@ a password is obtained using 'git-credential'.
specify a full pathname of a sendmail-like program instead;
the program must support the `-i` option. Default value can
be specified by the `sendemail.smtpServer` configuration
- option; the built-in default is `/usr/sbin/sendmail` or
- `/usr/lib/sendmail` if such program is available, or
- `localhost` otherwise.
+ option; the built-in default is to search for `sendmail` in
+ `/usr/sbin`, `/usr/lib` and $PATH if such program is
+ available, falling back to `localhost` otherwise.
--smtp-server-port=<port>::
Specifies a port different from the default port (SMTP
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 8be661007d..056dfb8661 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -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
----------
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 483a1f3547..e75db104e3 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -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.
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 0bb0042d8c..b63f2ea860 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -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
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index adf9554ad2..c60bcad44a 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -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].
diff --git a/Documentation/technical/http-protocol.txt b/Documentation/technical/http-protocol.txt
index 1c561bdd92..a0e45f2889 100644
--- a/Documentation/technical/http-protocol.txt
+++ b/Documentation/technical/http-protocol.txt
@@ -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
diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt
index ed1eae8b83..cd31edc91e 100644
--- a/Documentation/technical/pack-protocol.txt
+++ b/Documentation/technical/pack-protocol.txt
@@ -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
diff --git a/Makefile b/Makefile
index e53750ca01..fef9c8d272 100644
--- 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
diff --git a/builtin/am.c b/builtin/am.c
index 02853b3e05..3d98e52085 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -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);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 3faae382de..e1e157d205 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -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);
diff --git a/builtin/config.c b/builtin/config.c
index d13daeeb55..ab5f95476e 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -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)
diff --git a/builtin/diff.c b/builtin/diff.c
index 9808d062a8..16bfb22f73 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -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;
diff --git a/builtin/log.c b/builtin/log.c
index 6c1fa896ad..14fdf39165 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -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)
diff --git a/builtin/notes.c b/builtin/notes.c
index a2a542f82b..1a2c7d92ad 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -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
};
diff --git a/builtin/prune.c b/builtin/prune.c
index cddabf26a9..d2fdae680a 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -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;
diff --git a/builtin/pull.c b/builtin/pull.c
index 166b777ed6..511dbbe0f6 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -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))
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 4d37a160d7..b7ce7c7f52 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -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();
}
diff --git a/builtin/reflog.c b/builtin/reflog.c
index ab31a3b6aa..2233725315 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -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);
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 2086f0eb08..a5c4a8a694 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -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 2e14345051..d3e240228c 100644
--- 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
@@ -451,6 +454,16 @@ static inline enum object_type object_type(unsigned int mode)
#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) */
diff --git a/config.c b/config.c
index 676786a028..e617c2018d 100644
--- 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 */
diff --git a/config.h b/config.h
index 524d411823..ef70a9cac1 100644
--- 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 *);
diff --git a/connect.c b/connect.c
index df56c0cbff..c3a014c5ba 100644
--- 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 *);
@@ -108,6 +110,118 @@ static void annotate_refs_with_symref_info(struct ref *ref)
}
/*
+ * 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
*/
struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
@@ -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;
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 35df6ce9ea..3683c772c5 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1922,6 +1922,7 @@ _git_pull ()
--*)
__gitcomp "
--rebase --no-rebase
+ --autostash --no-autostash
$__git_merge_options
$__git_fetch_options
"
diff --git a/daemon.c b/daemon.c
index 30747075f0..e37e343d0a 100644
--- 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 @@ error_return:
}
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;
}
diff --git a/diff-lib.c b/diff-lib.c
index 5173023cd3..8104603a3b 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -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;
}
diff --git a/fetch-pack.c b/fetch-pack.c
index 008b25d3db..9f6b07ad91 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -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);
diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh
index ab0d1b0c06..eeea4b67ea 100755
--- a/generate-cmdlist.sh
+++ b/generate-cmdlist.sh
@@ -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];
diff --git a/git-gui/Makefile b/git-gui/Makefile
index 918a8de369..f10caedaa7 100644
--- a/git-gui/Makefile
+++ b/git-gui/Makefile
@@ -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"; \
diff --git a/git-send-email.perl b/git-send-email.perl
index 2208dcc213..edcc6d3469 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -885,7 +885,9 @@ if (defined $initial_reply_to) {
}
if (!defined $smtp_server) {
- foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) {
+ my @sendmail_paths = qw( /usr/sbin/sendmail /usr/lib/sendmail );
+ push @sendmail_paths, map {"$_/sendmail"} split /:/, $ENV{PATH};
+ foreach (@sendmail_paths) {
if (-x $_) {
$smtp_server = $_;
last;
diff --git a/git-stash.sh b/git-stash.sh
index 4b74951440..1114005ce2 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -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/grep.c b/grep.c
index a69c05edc2..3d7cd0e96f 100644
--- 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");
diff --git a/hash.h b/hash.h
index 024d0d3d50..7d7a864f5d 100644
--- 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)
@@ -13,4 +15,59 @@
#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 713525f38e..215bebef1b 100644
--- 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(),
diff --git a/log-tree.c b/log-tree.c
index 3b904f0375..fca29d4799 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -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);
}
}
diff --git a/log-tree.h b/log-tree.h
index 48f11fb740..deba035187 100644
--- a/log-tree.h
+++ b/log-tree.h
@@ -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 *);
diff --git a/merge-recursive.c b/merge-recursive.c
index d00b274381..a4c280dfc7 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -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");
}
diff --git a/notes-merge.c b/notes-merge.c
index 4a83b0ebd5..0f6573cb17 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -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) {
diff --git a/perl/Git/Packet.pm b/perl/Git/Packet.pm
index 255b28c098..b75738bed4 100644
--- a/perl/Git/Packet.pm
+++ b/perl/Git/Packet.pm
@@ -17,7 +17,7 @@ our @EXPORT = qw(
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 {
diff --git a/pkt-line.c b/pkt-line.c
index 93ea311443..2827ca772a 100644
--- a/pkt-line.c
+++ b/pkt-line.c
@@ -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;
diff --git a/pkt-line.h b/pkt-line.h
index 66ef610fc4..3dad583e2d 100644
--- a/pkt-line.h
+++ b/pkt-line.h
@@ -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)));
diff --git a/pretty.c b/pretty.c
index 2f6b0ae6c1..f7ce490230 100644
--- 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/progress.c b/progress.c
index 289678d43d..5f87f4568f 100644
--- a/progress.c
+++ b/progress.c
@@ -34,7 +34,6 @@ struct progress {
unsigned total;
unsigned last_percent;
unsigned delay;
- unsigned delayed_percent_threshold;
struct throughput *throughput;
uint64_t start_ns;
};
@@ -83,20 +82,8 @@ static int display(struct progress *progress, unsigned n, const char *done)
{
const char *eol, *tp;
- if (progress->delay) {
- if (!progress_update || --progress->delay)
- return 0;
- if (progress->total) {
- unsigned percent = n * 100 / progress->total;
- if (percent > progress->delayed_percent_threshold) {
- /* inhibit this progress report entirely */
- clear_progress_signal();
- progress->delay = -1;
- progress->total = 0;
- return 0;
- }
- }
- }
+ if (progress->delay && (!progress_update || --progress->delay))
+ return 0;
progress->last_value = n;
tp = (progress->throughput) ? progress->throughput->display.buf : "";
@@ -206,7 +193,7 @@ int display_progress(struct progress *progress, unsigned n)
}
static struct progress *start_progress_delay(const char *title, unsigned total,
- unsigned percent_threshold, unsigned delay)
+ unsigned delay)
{
struct progress *progress = malloc(sizeof(*progress));
if (!progress) {
@@ -219,7 +206,6 @@ static struct progress *start_progress_delay(const char *title, unsigned total,
progress->total = total;
progress->last_value = -1;
progress->last_percent = -1;
- progress->delayed_percent_threshold = percent_threshold;
progress->delay = delay;
progress->throughput = NULL;
progress->start_ns = getnanotime();
@@ -229,12 +215,12 @@ static struct progress *start_progress_delay(const char *title, unsigned total,
struct progress *start_delayed_progress(const char *title, unsigned total)
{
- return start_progress_delay(title, total, 0, 2);
+ return start_progress_delay(title, total, 2);
}
struct progress *start_progress(const char *title, unsigned total)
{
- return start_progress_delay(title, total, 0, 0);
+ return start_progress_delay(title, total, 0);
}
void stop_progress(struct progress **p_progress)
diff --git a/protocol.c b/protocol.c
new file mode 100644
index 0000000000..43012b7eb6
--- /dev/null
+++ b/protocol.c
@@ -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
index 0000000000..1b2bc94a8d
--- /dev/null
+++ b/protocol.h
@@ -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 339d4318ee..20ba82b434 100644
--- 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 18582a408c..01be5ae32f 100644
--- 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, "?*[");
diff --git a/repository.c b/repository.c
index bb2fae5446..998413b8bb 100644
--- a/repository.c
+++ b/repository.c
@@ -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);
diff --git a/repository.h b/repository.h
index 7f5e24a0a2..0329e40c7f 100644
--- a/repository.h
+++ b/repository.h
@@ -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,
diff --git a/revision.c b/revision.c
index e2e691dd5a..f6a3da5cd9 100644
--- a/revision.c
+++ b/revision.c
@@ -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;
diff --git a/sequencer.c b/sequencer.c
index fa94ed652d..e90bc316bb 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -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 94768512b7..50c6b2ab11 100644
--- 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 @@ cleanup_return:
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;
}
diff --git a/sha1_file.c b/sha1_file.c
index 2fc8fa93b4..3da70ac650 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -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;
diff --git a/submodule.c b/submodule.c
index bb531e0e59..fa25888783 100644
--- a/submodule.c
+++ b/submodule.c
@@ -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;
diff --git a/t/README b/t/README
index 4b079e4494..b3f7b449c3 100644
--- 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.
diff --git a/t/helper/test-date.c b/t/helper/test-date.c
index f414a3ac67..ac83687970 100644
--- a/t/helper/test-date.c
+++ b/t/helper/test-date.c
@@ -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
index 0000000000..97e8e580ef
--- /dev/null
+++ b/t/interop/i5700-protocol-transition.sh
@@ -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
diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf
index 0642ae7e6e..df19436314 100644
--- a/t/lib-httpd/apache.conf
+++ b/t/lib-httpd/apache.conf
@@ -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/
diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh
index bb94c23209..38dadd2c29 100755
--- a/t/lib-submodule-update.sh
+++ b/t/lib-submodule-update.sh
@@ -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 &&
diff --git a/t/perf/aggregate.perl b/t/perf/aggregate.perl
index 1dbc85b214..e401208488 100755
--- a/t/perf/aggregate.perl
+++ b/t/perf/aggregate.perl
@@ -69,12 +69,17 @@ if (not @tests) {
@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 @@ for my $t (@subtests) {
my $firstr;
for my $i (0..$#dirs) {
my $d = $dirs[$i];
- $times{$prefixes{$d}.$t} = [get_times("test-results/$prefixes{$d}$t.times")];
+ $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
index 0000000000..d3865db286
--- /dev/null
+++ b/t/perf/lib-pack.sh
@@ -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
+}
diff --git a/t/perf/p5550-fetch-tags.sh b/t/perf/p5550-fetch-tags.sh
index a5dc39f86a..d0e0e019ea 100755
--- a/t/perf/p5550-fetch-tags.sh
+++ b/t/perf/p5550-fetch-tags.sh
@@ -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
index 0000000000..b99dc23e32
--- /dev/null
+++ b/t/perf/p5551-fetch-rescan.sh
@@ -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
diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh
index b50211b259..e4c343a6b7 100644
--- a/t/perf/perf-lib.sh
+++ b/t/perf/perf-lib.sh
@@ -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
diff --git a/t/perf/run b/t/perf/run
index beb4acc0e4..43e4de49ef 100755
--- a/t/perf/run
+++ b/t/perf/run
@@ -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 "$@"
diff --git a/t/t0021/rot13-filter.pl b/t/t0021/rot13-filter.pl
index 6fd7fa476b..f1678851de 100644
--- a/t/t0021/rot13-filter.pl
+++ b/t/t0021/rot13-filter.pl
@@ -70,7 +70,7 @@ print $debug "init handshake complete\n";
$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 @@ while (1) {
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";
}
diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh
index 364a537000..cbeb9bebee 100755
--- a/t/t1300-repo-config.sh
+++ b/t/t1300-repo-config.sh
@@ -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"
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 3b1ac1971a..39c7f2ebd7 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -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 &&
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 8f155da7a5..25b1f8cc73 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -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 &&
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 50e40abb11..0f895478f0 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -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
diff --git a/t/t5603-clone-dirname.sh b/t/t5603-clone-dirname.sh
index d5af758129..13b5e5eb9b 100755
--- a/t/t5603-clone-dirname.sh
+++ b/t/t5603-clone-dirname.sh
@@ -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
index 0000000000..ba86a44eb1
--- /dev/null
+++ b/t/t5700-protocol-v1.sh
@@ -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
diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh
index c02ca735b9..1797f632a3 100755
--- a/t/t7810-grep.sh
+++ b/t/t7810-grep.sh
@@ -1131,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"
'
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 116bd6a70c..e7065df2bb 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -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?
diff --git a/upload-pack.c b/upload-pack.c
index 6d5f3c0d39..d5de18127c 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -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;
}