Merge branch 'ra/decorate-limit-refs'
authorJunio C Hamano <gitster@pobox.com>
Wed, 13 Dec 2017 21:28:54 +0000 (13:28 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 13 Dec 2017 21:28:54 +0000 (13:28 -0800)
The tagnames "git log --decorate" uses to annotate the commits can
now be limited to subset of available refs with the two additional
options, --decorate-refs[-exclude]=<pattern>.

* ra/decorate-limit-refs:
log: add option to choose which refs to decorate

101 files changed:
Documentation/RelNotes/2.15.1.txt
Documentation/RelNotes/2.15.2.txt [new file with mode: 0644]
Documentation/RelNotes/2.16.0.txt
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-add.txt
Documentation/git-branch.txt
Documentation/git-checkout.txt
Documentation/git-config.txt
Documentation/git-merge-base.txt
Documentation/git-notes.txt
Documentation/git-prune.txt
Documentation/git-reflog.txt
Documentation/git-stash.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitrepository-layout.txt
Documentation/merge-strategies.txt
Documentation/technical/http-protocol.txt
Documentation/technical/pack-protocol.txt
Makefile
apply.c
branch.c
branch.h
builtin/add.c
builtin/am.c
builtin/branch.c
builtin/checkout.c
builtin/config.c
builtin/diff.c
builtin/notes.c
builtin/prune.c
builtin/pull.c
builtin/receive-pack.c
builtin/reflog.c
cache.h
config.c
config.h
connect.c
contrib/completion/git-completion.bash
daemon.c
diff-lib.c
diff.c
fetch-pack.c
generate-cmdlist.sh
git-gui/Makefile
git-rebase--am.sh
git-rebase.sh
git-stash.sh
git.c
grep.c
hash.h
http.c
merge-recursive.c
notes-merge.c
perl/Git/Packet.pm
pkt-line.c
pkt-line.h
protocol.c [new file with mode: 0644]
protocol.h [new file with mode: 0644]
read-cache.c
repository.c
repository.h
sequencer.c
setup.c
sha1_file.c
sha1_name.c
submodule.c
t/helper/test-date.c
t/interop/i5700-protocol-transition.sh [new file with mode: 0755]
t/lib-gpg.sh
t/lib-httpd/apache.conf
t/lib-submodule-update.sh
t/perf/aggregate.perl
t/perf/lib-pack.sh [new file with mode: 0644]
t/perf/p5550-fetch-tags.sh
t/perf/p5551-fetch-rescan.sh [new file with mode: 0755]
t/perf/perf-lib.sh
t/perf/run
t/t0021/rot13-filter.pl
t/t0025-crlf-renormalize.sh [new file with mode: 0755]
t/t1300-repo-config.sh
t/t1430-bad-ref-name.sh
t/t3310-notes-merge-manual-resolve.sh
t/t3320-notes-merge-worktrees.sh
t/t3400-rebase.sh
t/t3512-cherry-pick-submodule.sh
t/t3903-stash.sh
t/t4015-diff-whitespace.sh
t/t4051-diff-function-context.sh
t/t4051/hello.c
t/t4107-apply-ignore-whitespace.sh
t/t5601-clone.sh
t/t5603-clone-dirname.sh
t/t5700-protocol-v1.sh [new file with mode: 0755]
t/t7006-pager.sh
t/t7810-grep.sh
upload-pack.c
xdiff/xdiff.h
xdiff/xemit.c
xdiff/xutils.c
index 47f23b56fed17c5b54540ead9851591baddd1414..ec06704e637d538d510e8f3a110cfacb3e79d1e4 100644 (file)
@@ -13,7 +13,7 @@ Fixes since v2.15
    latter, which has been fixed.
 
  * The experimental "color moved lines differently in diff output"
-   feature was buggy around "ignore whitespace changes" edges, whihch
+   feature was buggy around "ignore whitespace changes" edges, which
    has been corrected.
 
  * Instead of using custom line comparison and hashing functions to
@@ -24,7 +24,7 @@ Fixes since v2.15
    HEAD points at, which have been fixed.
 
  * "git commit", after making a commit, did not check for errors when
-   asking on what branch it made the commit, which has been correted.
+   asking on what branch it made the commit, which has been corrected.
 
  * "git status --ignored -u" did not stop at a working tree of a
    separate project that is embedded in an ignored directory and
@@ -35,7 +35,7 @@ Fixes since v2.15
    --recurse-submodules" has been fixed.
 
  * A recent regression in "git rebase -i" that broke execution of git
-   commands from subdirectories via "exec" insn has been fixed.
+   commands from subdirectories via "exec" instruction has been fixed.
 
  * "git check-ref-format --branch @{-1}" bit a "BUG()" when run
    outside a repository for obvious reasons; clarify the documentation
@@ -76,5 +76,13 @@ Fixes since v2.15
  * We used to add an empty alternate object database to the system
    that does not help anything; it has been corrected.
 
+ * Error checking in "git imap-send" for empty response has been
+   improved.
+
+ * An ancient bug in "git apply --ignore-space-change" codepath has
+   been fixed.
+
+ * There was a recent semantic mismerge in the codepath to write out a
+   section of a configuration section, which has been corrected.
 
 Also contains various documentation updates and code clean-ups.
diff --git a/Documentation/RelNotes/2.15.2.txt b/Documentation/RelNotes/2.15.2.txt
new file mode 100644 (file)
index 0000000..9f7e28f
--- /dev/null
@@ -0,0 +1,47 @@
+Git v2.15.2 Release Notes
+=========================
+
+Fixes since v2.15.1
+-------------------
+
+ * Recent update to the refs infrastructure implementation started
+   rewriting packed-refs file more often than before; this has been
+   optimized again for most trivial cases.
+
+ * The SubmittingPatches document has been converted to produce an
+   HTML version via AsciiDoc/Asciidoctor.
+
+ * Contrary to the documentation, "git pull -4/-6 other-args" did not
+   ask the underlying "git fetch" to go over IPv4/IPv6, which has been
+   corrected.
+
+ * When "git rebase" prepared an mailbox of changes and fed it to "git
+   am" to replay them, it was confused when a stray "From " happened
+   to be in the log message of one of the replayed changes.  This has
+   been corrected.
+
+ * Command line completion (in contrib/) has been taught about the
+   "--copy" option of "git branch".
+
+ * "git apply --inaccurate-eof" when used with "--ignore-space-change"
+   triggered an internal sanity check, which has been fixed.
+
+ * The sequencer machinery (used by "git cherry-pick A..B", and "git
+   rebase -i", among other things) would have lost a commit if stopped
+   due to an unlockable index file, which has been fixed.
+
+ * The three-way merge performed by "git cherry-pick" was confused
+   when a new submodule was added in the meantime, which has been
+   fixed (or "papered over").
+
+ * "git notes" sent its error message to its standard output stream,
+   which was corrected.
+
+ * A few scripts (both in production and tests) incorrectly redirected
+   their error output.  These have been corrected.
+
+ * Clarify and enhance documentation for "merge-base --fork-point", as
+   it was clear what it computed but not why/what for.
+
+
+Also contains various documentation updates and code clean-ups.
index f57acec73505bcc32c60f84d579c3bc72ee1a7b9..431bd5e34ace656c3462e5cb4cf6bd3b77adfece 100644 (file)
@@ -61,11 +61,41 @@ 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.
 
+ * The "diff" family of commands learned to ignore differences in
+   carriage return at the end of line.
+
+ * Places that know about "sendemail.to", like documentation and shell
+   completion (in contrib/) have been taught about "sendemail.tocmd",
+   too.
+
+ * "git add --renormalize ." is a new and safer way to record the fact
+   that you are correcting the end-of-line convention and other
+   "convert_to_git()" glitches in the in-repository data.
+
+ * "git branch" and "git checkout -b" are now forbidden from creating
+   a branch whose name is "HEAD".
+
+ * "git branch --list" learned to show its output through the pager by
+   default when the output is going to a terminal, which is controlled
+   by the pager.branch configuration variable.  This is similar to a
+   recent change to "git tag --list".
+
+ * "git grep -W", "git diff -W" and their friends learned a heuristic
+   to extend a pre-context beyond the line that matches the "function
+   pattern" (aka "diff.*.xfuncname") to include a comment block, if
+   exists, that immediately precedes it.
+
+ * "git config --expiry-date gc.reflogexpire" can read "2.weeks" from
+   the configuration and report it as a timestamp, just like "--int"
+   would read "1k" and report 1024, to help consumption by scripts.
+
+ * The shell completion (in contrib/) learned that "git pull" can take
+   the "--autostash" option.
+
 
 Performance, Internal Implementation, Development Support etc.
 
@@ -106,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.
 
 
@@ -119,7 +156,7 @@ Fixes since v2.15
    latter, which has been fixed.
 
  * The experimental "color moved lines differently in diff output"
-   feature was buggy around "ignore whitespace changes" edges, whihch
+   feature was buggy around "ignore whitespace changes" edges, which
    has been corrected.
 
  * Instead of using custom line comparison and hashing functions to
@@ -130,7 +167,7 @@ Fixes since v2.15
    HEAD points at, which have been fixed.
 
  * "git commit", after making a commit, did not check for errors when
-   asking on what branch it made the commit, which has been correted.
+   asking on what branch it made the commit, which has been corrected.
 
  * "git status --ignored -u" did not stop at a working tree of a
    separate project that is embedded in an ignored directory and
@@ -141,7 +178,7 @@ Fixes since v2.15
    --recurse-submodules" has been fixed.
 
  * A recent regression in "git rebase -i" that broke execution of git
-   commands from subdirectories via "exec" insn has been fixed.
+   commands from subdirectories via "exec" instruction has been fixed.
 
  * A (possibly flakey) test fix.
 
@@ -178,12 +215,10 @@ Fixes since v2.15
 
  * Error checking in "git imap-send" for empty response has been
    improved.
-   (merge 618ec81abb rs/imap-send-next-arg-fix later to maint).
 
  * 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.
@@ -198,11 +233,77 @@ Fixes since v2.15
    that does not help anything; it has been corrected.
 
  * Doc update around use of "format-patch --subject-prefix" etc.
-   (merge f6be7edcac ad/submitting-patches-title-decoration later to maint).
 
  * A fix for an ancient bug in "git apply --ignore-space-change" codepath.
-   (merge 6ce15ce576 rs/apply-fuzzy-match-fix later to maint).
+
+ * Clarify and enhance documentation for "merge-base --fork-point", as
+   it was clear what it computed but not why/what for.
+
+ * A few scripts (both in production and tests) incorrectly redirected
+   their error output.  These have been corrected.
+
+ * "git notes" sent its error message to its standard output stream,
+   which was corrected.
+
+ * 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").
+
+ * 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.
+
+ * "git apply --inaccurate-eof" when used with "--ignore-space-change"
+   triggered an internal sanity check, which has been fixed.
+
+ * Command line completion (in contrib/) has been taught about the
+   "--copy" option of "git branch".
+
+ * 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.
+
+ * There was a recent semantic mismerge in the codepath to write out a
+   section of a configuration section, which has been corrected.
+
+ * 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.
+
+ * 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.
+
+ * "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 f4e45cb3eb ma/bisect-leakfix later to maint).
-   (merge 4da72644b7 ma/reduce-heads-leakfix later to maint).
+   (merge 1a1fc2d5b5 rd/man-prune-progress later to maint).
+   (merge 0ba014035a rd/man-reflog-add-n later to maint).
+   (merge e54b63359f rd/doc-notes-prune-fix later to maint).
+   (merge ff4c9b413a sp/doc-info-attributes later to maint).
index 5f65fa92348385236f79091e944d17c65d7cf489..c1598ee7039fca0439a18b31f186052bbb40bbce 100644 (file)
@@ -2108,15 +2108,40 @@ matched against are those given directly to Git commands.  This means any URLs
 visited as a result of a redirection do not participate in matching.
 
 ssh.variant::
-       Depending on the value of the environment variables `GIT_SSH` or
-       `GIT_SSH_COMMAND`, or the config setting `core.sshCommand`, Git
-       auto-detects whether to adjust its command-line parameters for use
-       with plink or tortoiseplink, as opposed to the default (OpenSSH).
+       By default, Git determines the command line arguments to use
+       based on the basename of the configured SSH command (configured
+       using the environment variable `GIT_SSH` or `GIT_SSH_COMMAND` or
+       the config setting `core.sshCommand`). If the basename is
+       unrecognized, Git will attempt to detect support of OpenSSH
+       options by first invoking the configured SSH command with the
+       `-G` (print configuration) option and will subsequently use
+       OpenSSH options (if that is successful) or no options besides
+       the host and remote command (if it fails).
++
+The config variable `ssh.variant` can be set to override this detection.
+Valid values are `ssh` (to use OpenSSH options), `plink`, `putty`,
+`tortoiseplink`, `simple` (no options except the host and remote command).
+The default auto-detection can be explicitly requested using the value
+`auto`.  Any other value is treated as `ssh`.  This setting can also be
+overridden via the environment variable `GIT_SSH_VARIANT`.
++
+The current command-line parameters used for each variant are as
+follows:
 +
-The config variable `ssh.variant` can be set to override this auto-detection;
-valid values are `ssh`, `plink`, `putty` or `tortoiseplink`. Any other value
-will be treated as normal ssh. This setting can be overridden via the
-environment variable `GIT_SSH_VARIANT`.
+--
+
+* `ssh` - [-p port] [-4] [-6] [-o option] [username@]host command
+
+* `simple` - [username@]host command
+
+* `plink` or `putty` - [-P port] [-4] [-6] [username@]host command
+
+* `tortoiseplink` - [-P port] [-4] [-6] -batch [username@]host command
+
+--
++
+Except for the `simple` variant, command-line parameters are likely to
+change as git gains new features.
 
 i18n.commitEncoding::
        Character encoding the commit messages are stored in; Git itself
@@ -2544,6 +2569,23 @@ The protocol names currently used by git are:
     `hg` to allow the `git-remote-hg` helper)
 --
 
+protocol.version::
+       Experimental. If set, clients will attempt to communicate with a
+       server using the specified protocol version.  If unset, no
+       attempt will be made by the client to communicate using a
+       particular protocol version, this results in protocol version 0
+       being used.
+       Supported versions:
++
+--
+
+* `0` - the original wire protocol.
+
+* `1` - the original wire protocol with the addition of a version string
+  in the initial response from the server.
+
+--
+
 pull.ff::
        By default, Git does not create an extra merge commit when merging
        a commit that is a descendant of the current commit. Instead, the
@@ -3007,6 +3049,7 @@ sendemail.smtpPass::
 sendemail.suppresscc::
 sendemail.suppressFrom::
 sendemail.to::
+sendemail.tocmd::
 sendemail.smtpDomain::
 sendemail.smtpServer::
 sendemail.smtpServerPort::
index dd0dba5b1d1951e762f0a43f1b2476bfa1029ad9..3c93c216831e722ec9b8b8a231f455df328e7fe2 100644 (file)
@@ -557,6 +557,9 @@ endif::git-format-patch[]
 --text::
        Treat all files as text.
 
+--ignore-cr-at-eol::
+       Ignore carrige-return at the end of line when doing a comparison.
+
 --ignore-space-at-eol::
        Ignore changes in whitespace at EOL.
 
index b700beaff5ad2eaa407bfb3818e016a5bb27d87c..d50fa339dcc523158896fda8cdccd1a2784dfdd7 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 [verse]
 'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
          [--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]]
-         [--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing]
+         [--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]
          [--chmod=(+|-)x] [--] [<pathspec>...]
 
 DESCRIPTION
@@ -175,6 +175,13 @@ for "git add --no-all <pathspec>...", i.e. ignored removed files.
        warning (e.g., if you are manually performing operations on
        submodules).
 
+--renormalize::
+       Apply the "clean" process freshly to all tracked files to
+       forcibly add them again to the index.  This is useful after
+       changing `core.autocrlf` configuration or the `text` attribute
+       in order to correct files added with wrong CRLF/LF line endings.
+       This option implies `-u`.
+
 --chmod=(+|-)x::
        Override the executable bit of the added files.  The executable
        bit is only changed in the index, the files on disk are left
index d6587c5e96f4dbcdb4176d9dd5f6a992c0e83f3b..b3084c99c1cabdccc690e4cec5071b22774bf6c8 100644 (file)
@@ -14,7 +14,7 @@ SYNOPSIS
        [(--merged | --no-merged) [<commit>]]
        [--contains [<commit]] [--no-contains [<commit>]]
        [--points-at <object>] [--format=<format>] [<pattern>...]
-'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
+'git branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>]
 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
 'git branch' --unset-upstream [<branchname>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
@@ -86,7 +86,7 @@ OPTIONS
 --delete::
        Delete a branch. The branch must be fully merged in its
        upstream branch, or in `HEAD` if no upstream was set with
-       `--track` or `--set-upstream`.
+       `--track` or `--set-upstream-to`.
 
 -D::
        Shortcut for `--delete --force`.
@@ -281,6 +281,12 @@ start-point is either a local or remote-tracking branch.
        and the object it points at.  The format is the same as
        that of linkgit:git-for-each-ref[1].
 
+CONFIGURATION
+-------------
+`pager.branch` is only respected when listing branches, i.e., when
+`--list` is used or implied. The default is to use a pager.
+See linkgit:git-config[1].
+
 Examples
 --------
 
index e108b0f74bb6dc4d9f3fc27e7c3fe571b19a9d2f..bfa64ca5c98c54b9b9465d45669f45ab887605cf 100644 (file)
@@ -264,6 +264,8 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
        local modifications in a submodule would be overwritten the checkout
        will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
        is used, the work trees of submodules will not be updated.
+       Just like linkgit:git-submodule[1], this will detach the
+       submodules HEAD.
 
 <branch>::
        Branch to checkout; if it refers to a branch (i.e., a name that,
index 4edd09fc6b0742042f3236598d8ecb793773d8ab..14da5fc157ee0ea7467e7b9bd9d33d465c1f7a3f 100644 (file)
@@ -180,6 +180,11 @@ See also <<FILES>>.
        value (but you can use `git config section.variable ~/`
        from the command line to let your shell do the expansion).
 
+--expiry-date::
+       `git config` will ensure that the output is converted from
+       a fixed or relative date-string to a timestamp. This option
+       has no effect when setting the value.
+
 -z::
 --null::
        For all options that output values and/or keys, always
index b968b64c380b4fbe2663da42355fc9e8fdd24679..502e00ec35ff1fd543dbd8361a317cb583ca1eba 100644 (file)
@@ -154,23 +154,71 @@ topic origin/master`, the history of remote-tracking branch
 `origin/master` may have been rewound and rebuilt, leading to a
 history of this shape:
 
-                        o---B1
+                        o---B2
                        /
-       ---o---o---B2--o---o---o---B (origin/master)
+       ---o---o---B1--o---o---o---B (origin/master)
                \
-                B3
+                B0
                  \
-                  Derived (topic)
+                  D0---D1---D (topic)
 
-where `origin/master` used to point at commits B3, B2, B1 and now it
+where `origin/master` used to point at commits B0, B1, B2 and now it
 points at B, and your `topic` branch was started on top of it back
-when `origin/master` was at B3. This mode uses the reflog of
-`origin/master` to find B3 as the fork point, so that the `topic`
-can be rebased on top of the updated `origin/master` by:
+when `origin/master` was at B0, and you built three commits, D0, D1,
+and D, on top of it.  Imagine that you now want to rebase the work
+you did on the topic on top of the updated origin/master.
+
+In such a case, `git merge-base origin/master topic` would return the
+parent of B0 in the above picture, but B0^..D is *not* the range of
+commits you would want to replay on top of B (it includes B0, which
+is not what you wrote; it is a commit the other side discarded when
+it moved its tip from B0 to B1).
+
+`git merge-base --fork-point origin/master topic` is designed to
+help in such a case.  It takes not only B but also B0, B1, and B2
+(i.e. old tips of the remote-tracking branches your repository's
+reflog knows about) into account to see on which commit your topic
+branch was built and finds B0, allowing you to replay only the
+commits on your topic, excluding the commits the other side later
+discarded.
+
+Hence
 
     $ fork_point=$(git merge-base --fork-point origin/master topic)
+
+will find B0, and
+
     $ git rebase --onto origin/master $fork_point topic
 
+will replay D0, D1 and D on top of B to create a new history of this
+shape:
+
+                        o---B2
+                       /
+       ---o---o---B1--o---o---o---B (origin/master)
+               \                   \
+                B0                  D0'--D1'--D' (topic - updated)
+                 \
+                  D0---D1---D (topic - old)
+
+A caveat is that older reflog entries in your repository may be
+expired by `git gc`.  If B0 no longer appears in the reflog of the
+remote-tracking branch `origin/master`, the `--fork-point` mode
+obviously cannot find it and fails, avoiding to give a random and
+useless result (such as the parent of B0, like the same command
+without the `--fork-point` option gives).
+
+Also, the remote-tracking branch you use the `--fork-point` mode
+with must be the one your topic forked from its tip.  If you forked
+from an older commit than the tip, this mode would not find the fork
+point (imagine in the above sample history B0 did not exist,
+origin/master started at B1, moved to B2 and then B, and you forked
+your topic at origin/master^ when origin/master was B1; the shape of
+the history would be the same as above, without B0, and the parent
+of B1 is what `git merge-base origin/master topic` correctly finds,
+but the `--fork-point` mode will not, because it is not one of the
+commits that used to be at the tip of origin/master).
+
 
 See also
 --------
index 43677297f3b85814c18c972a159b81c8076bc445..e8dec1b3c89a66dc2f9e78737fa7d243bee85d37 100644 (file)
@@ -18,7 +18,7 @@ SYNOPSIS
 'git notes' merge --commit [-v | -q]
 'git notes' merge --abort [-v | -q]
 'git notes' remove [--ignore-missing] [--stdin] [<object>...]
-'git notes' prune [-n | -v]
+'git notes' prune [-n] [-v]
 'git notes' get-ref
 
 
index 7a493c80f776092265abd4fb2575639e683bcf5c..a37c0af9313e8e47806c89a584c70c09517244d1 100644 (file)
@@ -9,7 +9,7 @@ git-prune - Prune all unreachable objects from the object database
 SYNOPSIS
 --------
 [verse]
-'git prune' [-n] [-v] [--expire <expire>] [--] [<head>...]
+'git prune' [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]
 
 DESCRIPTION
 -----------
@@ -42,12 +42,15 @@ OPTIONS
 --verbose::
        Report all removed objects.
 
-\--::
-       Do not interpret any more arguments as options.
+--progress::
+       Show progress.
 
 --expire <time>::
        Only expire loose objects older than <time>.
 
+\--::
+       Do not interpret any more arguments as options.
+
 <head>...::
        In addition to objects
        reachable from any of our references, keep objects
index 44c736f1a8e581bc072d5de3b719284f7376ec3b..472a6808cd60899f4554e998b9fb8e4f3103aac2 100644 (file)
@@ -20,9 +20,9 @@ depending on the subcommand:
 'git reflog' ['show'] [log-options] [<ref>]
 'git reflog expire' [--expire=<time>] [--expire-unreachable=<time>]
        [--rewrite] [--updateref] [--stale-fix]
-       [--dry-run] [--verbose] [--all | <refs>...]
+       [--dry-run | -n] [--verbose] [--all | <refs>...]
 'git reflog delete' [--rewrite] [--updateref]
-       [--dry-run] [--verbose] ref@\{specifier\}...
+       [--dry-run | -n] [--verbose] ref@\{specifier\}...
 'git reflog exists' <ref>
 
 Reference logs, or "reflogs", record when the tips of branches and
index 8be661007d03eaa55ee020883161b4d38c31e8cf..056dfb86612d0c5be0d01f16ac6b126fac80b92c 100644 (file)
@@ -175,14 +175,14 @@ create::
        return its object name, without storing it anywhere in the ref
        namespace.
        This is intended to be useful for scripts.  It is probably not
-       the command you want to use; see "save" above.
+       the command you want to use; see "push" above.
 
 store::
 
        Store a given stash created via 'git stash create' (which is a
        dangling merge commit) in the stash ref, updating the stash
        reflog.  This is intended to be useful for scripts.  It is
-       probably not the command you want to use; see "save" above.
+       probably not the command you want to use; see "push" above.
 
 DISCUSSION
 ----------
index 483a1f35475ea435e5c93121e67ad8085855bfc3..e75db104e3a33dde93a98cc09de97f4d4f0bc5eb 100644 (file)
@@ -522,11 +522,10 @@ other
        If either of these environment variables is set then 'git fetch'
        and 'git push' will use the specified command instead of 'ssh'
        when they need to connect to a remote system.
-       The command will be given exactly two or four arguments: the
-       'username@host' (or just 'host') from the URL and the shell
-       command to execute on that remote system, optionally preceded by
-       `-p` (literally) and the 'port' from the URL when it specifies
-       something other than the default SSH port.
+       The command-line parameters passed to the configured command are
+       determined by the ssh variant.  See `ssh.variant` option in
+       linkgit:git-config[1] for details.
+
 +
 `$GIT_SSH_COMMAND` takes precedence over `$GIT_SSH`, and is interpreted
 by the shell, which allows additional arguments to be included.
@@ -705,6 +704,12 @@ of clones and fetches.
        which feed potentially-untrusted URLS to git commands.  See
        linkgit:git-config[1] for more details.
 
+`GIT_PROTOCOL`::
+       For internal use only.  Used in handshaking the wire protocol.
+       Contains a colon ':' separated list of keys with optional values
+       'key[=value]'.  Presence of unknown keys and values must be
+       ignored.
+
 `GIT_OPTIONAL_LOCKS`::
        If set to `0`, Git will complete any requested operation without
        performing any optional sub-operations that require taking a lock.
index 4c68bc19d5108d1ba0c91b05d35acf8060eb2c9d..30687de81a6e40b3d9cc120f659817b1a8d03603 100644 (file)
@@ -232,8 +232,7 @@ From a clean working directory:
 
 -------------------------------------------------
 $ echo "* text=auto" >.gitattributes
-$ git read-tree --empty   # Clean index, force re-scan of working directory
-$ git add .
+$ git add --renormalize .
 $ git status        # Show files that will be normalized
 $ git commit -m "Introduce end-of-line normalization"
 -------------------------------------------------
@@ -328,6 +327,9 @@ You can declare that a filter turns a content that by itself is unusable
 into a usable content by setting the filter.<driver>.required configuration
 variable to `true`.
 
+Note: Whenever the clean filter is changed, the repo should be renormalized:
+$ git add --renormalize .
+
 For example, in .gitattributes, you would assign the `filter`
 attribute for paths.
 
index adf9554ad2f84cb69a6b0a940e4d3e0e336df9ff..c60bcad44aa581b2449a7a638b6487c1c73e8c23 100644 (file)
@@ -208,6 +208,10 @@ info/exclude::
        'git clean' look at it but the core Git commands do not look
        at it.  See also: linkgit:gitignore[5].
 
+info/attributes::
+       Defines which attributes to assign to a path, similar to per-directory
+       `.gitattributes` files.   See also: linkgit:gitattributes[5].
+
 info/sparse-checkout::
        This file stores sparse checkout patterns.
        See also: linkgit:git-read-tree[1].
index a09d5974639fa772ba86c5547fe70716fd044f73..fd5d748d1b508c9cef1063227b5c478952d3bc7e 100644 (file)
@@ -58,11 +58,12 @@ diff-algorithm=[patience|minimal|histogram|myers];;
 ignore-space-change;;
 ignore-all-space;;
 ignore-space-at-eol;;
+ignore-cr-at-eol;;
        Treats lines with the indicated type of whitespace change as
        unchanged for the sake of a three-way merge.  Whitespace
        changes mixed with other changes to a line are not ignored.
-       See also linkgit:git-diff[1] `-b`, `-w`, and
-       `--ignore-space-at-eol`.
+       See also linkgit:git-diff[1] `-b`, `-w`,
+       `--ignore-space-at-eol`, and `--ignore-cr-at-eol`.
 +
 * If 'their' version only introduces whitespace changes to a line,
   'our' version is used;
index 1c561bdd9204fe221512064f765bea9eb7ec5945..a0e45f2889e6e52db71f88d7368bbb2efc02616a 100644 (file)
@@ -219,6 +219,10 @@ smart server reply:
    S: 003c2cb58b79488a98d2721cea644875a8dd0026b115 refs/tags/v1.0\n
    S: 003fa3c2e2402b99163d1d59756e5f207ae21cccba4c refs/tags/v1.0^{}\n
 
+The client may send Extra Parameters (see
+Documentation/technical/pack-protocol.txt) as a colon-separated string
+in the Git-Protocol HTTP header.
+
 Dumb Server Response
 ^^^^^^^^^^^^^^^^^^^^
 Dumb servers MUST respond with the dumb server reply format.
@@ -269,7 +273,11 @@ the C locale ordering.  The stream SHOULD include the default ref
 named `HEAD` as the first ref.  The stream MUST include capability
 declarations behind a NUL on the first ref.
 
+The returned response contains "version 1" if "version=1" was sent as an
+Extra Parameter.
+
   smart_reply     =  PKT-LINE("# service=$servicename" LF)
+                    *1("version 1")
                     ref_list
                     "0000"
   ref_list        =  empty_list / non_empty_list
index ed1eae8b83a651d34f9ce2488f58d02a763fbfd4..cd31edc91ea1989adc7f2321327c0a97685fe62f 100644 (file)
@@ -39,6 +39,19 @@ communicates with that invoked process over the SSH connection.
 The file:// transport runs the 'upload-pack' or 'receive-pack'
 process locally and communicates with it over a pipe.
 
+Extra Parameters
+----------------
+
+The protocol provides a mechanism in which clients can send additional
+information in its first message to the server. These are called "Extra
+Parameters", and are supported by the Git, SSH, and HTTP protocols.
+
+Each Extra Parameter takes the form of `<key>=<value>` or `<key>`.
+
+Servers that receive any such Extra Parameters MUST ignore all
+unrecognized keys. Currently, the only Extra Parameter recognized is
+"version=1".
+
 Git Transport
 -------------
 
@@ -46,18 +59,25 @@ The Git transport starts off by sending the command and repository
 on the wire using the pkt-line format, followed by a NUL byte and a
 hostname parameter, terminated by a NUL byte.
 
-   0032git-upload-pack /project.git\0host=myserver.com\0
+   0033git-upload-pack /project.git\0host=myserver.com\0
+
+The transport may send Extra Parameters by adding an additional NUL
+byte, and then adding one or more NUL-terminated strings:
+
+   003egit-upload-pack /project.git\0host=myserver.com\0\0version=1\0
 
 --
-   git-proto-request = request-command SP pathname NUL [ host-parameter NUL ]
+   git-proto-request = request-command SP pathname NUL
+                      [ host-parameter NUL ] [ NUL extra-parameters ]
    request-command   = "git-upload-pack" / "git-receive-pack" /
                       "git-upload-archive"   ; case sensitive
    pathname          = *( %x01-ff ) ; exclude NUL
    host-parameter    = "host=" hostname [ ":" port ]
+   extra-parameters  = 1*extra-parameter
+   extra-parameter   = 1*( %x01-ff ) NUL
 --
 
-Only host-parameter is allowed in the git-proto-request. Clients
-MUST NOT attempt to send additional parameters. It is used for the
+host-parameter is used for the
 git-daemon name based virtual hosting.  See --interpolated-path
 option to git daemon, with the %H/%CH format characters.
 
@@ -117,6 +137,12 @@ we execute it without the leading '/'.
                     v
    ssh user@example.com "git-upload-pack '~alice/project.git'"
 
+Depending on the value of the `protocol.version` configuration variable,
+Git may attempt to send Extra Parameters as a colon-separated string in
+the GIT_PROTOCOL environment variable. This is done only if
+the `ssh.variant` configuration variable indicates that the ssh command
+supports passing environment variables as an argument.
+
 A few things to remember here:
 
 - The "command name" is spelled with dash (e.g. git-upload-pack), but
@@ -137,11 +163,13 @@ Reference Discovery
 -------------------
 
 When the client initially connects the server will immediately respond
-with a listing of each reference it has (all branches and tags) along
+with a version number (if "version=1" is sent as an Extra Parameter),
+and a listing of each reference it has (all branches and tags) along
 with the object name that each reference currently points to.
 
-   $ echo -e -n "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" |
+   $ echo -e -n "0044git-upload-pack /schacon/gitbook.git\0host=example.com\0\0version=1\0" |
       nc -v example.com 9418
+   000aversion 1
    00887217a7c7e582c46cec22a130adf4b9d7d950fba0 HEAD\0multi_ack thin-pack
                side-band side-band-64k ofs-delta shallow no-progress include-tag
    00441d3fcd5ced445d1abc402225c0b8a1299641f497 refs/heads/integration
@@ -165,7 +193,8 @@ immediately after the ref itself, if presented. A conforming server
 MUST peel the ref if it's an annotated tag.
 
 ----
-  advertised-refs  =  (no-refs / list-of-refs)
+  advertised-refs  =  *1("version 1")
+                     (no-refs / list-of-refs)
                      *shallow
                      flush-pkt
 
index e53750ca01afbd7d777e62aa221f6861f66b0703..fef9c8d2725d6806d9ef4516de64c6d68bd7a7fe 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -849,6 +849,7 @@ LIB_OBJS += pretty.o
 LIB_OBJS += prio-queue.o
 LIB_OBJS += progress.o
 LIB_OBJS += prompt.o
+LIB_OBJS += protocol.o
 LIB_OBJS += quote.o
 LIB_OBJS += reachable.o
 LIB_OBJS += read-cache.o
diff --git a/apply.c b/apply.c
index b8087bd29c5f233b28b685ec90a6a3cfaaa498d4..321a9fa68d491f7e5e89dc9e398b067f67a10c77 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -2953,6 +2953,8 @@ static int apply_one_fragment(struct apply_state *state,
            newlines.len > 0 && newlines.buf[newlines.len - 1] == '\n') {
                old--;
                strbuf_setlen(&newlines, newlines.len - 1);
+               preimage.line_allocated[preimage.nr - 1].len--;
+               postimage.line_allocated[postimage.nr - 1].len--;
        }
 
        leading = frag->leading;
index 62f7b0d8c2d6a89fde5abffb6c836d0ce7706ac4..fe1e1c3676b26a4f35feaf8137700b25916ad5ee 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -178,24 +178,40 @@ int read_branch_desc(struct strbuf *buf, const char *branch_name)
        return 0;
 }
 
-int validate_new_branchname(const char *name, struct strbuf *ref,
-                           int force, int attr_only)
+/*
+ * Check if 'name' can be a valid name for a branch; die otherwise.
+ * Return 1 if the named branch already exists; return 0 otherwise.
+ * Fill ref with the full refname for the branch.
+ */
+int validate_branchname(const char *name, struct strbuf *ref)
 {
        if (strbuf_check_branch_ref(ref, name))
                die(_("'%s' is not a valid branch name."), name);
 
-       if (!ref_exists(ref->buf))
+       return ref_exists(ref->buf);
+}
+
+/*
+ * Check if a branch 'name' can be created as a new branch; die otherwise.
+ * 'force' can be used when it is OK for the named branch already exists.
+ * Return 1 if the named branch already exists; return 0 otherwise.
+ * Fill ref with the full refname for the branch.
+ */
+int validate_new_branchname(const char *name, struct strbuf *ref, int force)
+{
+       const char *head;
+
+       if (!validate_branchname(name, ref))
                return 0;
-       else if (!force && !attr_only)
-               die(_("A branch named '%s' already exists."), ref->buf + strlen("refs/heads/"));
 
-       if (!attr_only) {
-               const char *head;
+       if (!force)
+               die(_("A branch named '%s' already exists."),
+                   ref->buf + strlen("refs/heads/"));
+
+       head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
+       if (!is_bare_repository() && head && !strcmp(head, ref->buf))
+               die(_("Cannot force update the current branch."));
 
-               head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
-               if (!is_bare_repository() && head && !strcmp(head, ref->buf))
-                       die(_("Cannot force update the current branch."));
-       }
        return 1;
 }
 
@@ -242,9 +258,9 @@ void create_branch(const char *name, const char *start_name,
        if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)
                explicit_tracking = 1;
 
-       if (validate_new_branchname(name, &ref, force,
-                                   track == BRANCH_TRACK_OVERRIDE ||
-                                   clobber_head)) {
+       if ((track == BRANCH_TRACK_OVERRIDE || clobber_head)
+           ? validate_branchname(name, &ref)
+           : validate_new_branchname(name, &ref, force)) {
                if (!force)
                        dont_change_ref = 1;
                else
index b07788558c0e76a701b571f73c0462dcf2abcc63..be5e5d13083a98e57e834d81afe5450f11202130 100644 (file)
--- a/branch.h
+++ b/branch.h
@@ -23,22 +23,19 @@ void create_branch(const char *name, const char *start_name,
                   int clobber_head, int quiet, enum branch_track track);
 
 /*
- * Validates that the requested branch may be created, returning the
- * interpreted ref in ref, force indicates whether (non-head) branches
- * may be overwritten. A non-zero return value indicates that the force
- * parameter was non-zero and the branch already exists.
- *
- * Contrary to all of the above, when attr_only is 1, the caller is
- * not interested in verifying if it is Ok to update the named
- * branch to point at a potentially different commit. It is merely
- * asking if it is OK to change some attribute for the named branch
- * (e.g. tracking upstream).
- *
- * NEEDSWORK: This needs to be split into two separate functions in the
- * longer run for sanity.
- *
+ * Check if 'name' can be a valid name for a branch; die otherwise.
+ * Return 1 if the named branch already exists; return 0 otherwise.
+ * Fill ref with the full refname for the branch.
+ */
+extern int validate_branchname(const char *name, struct strbuf *ref);
+
+/*
+ * Check if a branch 'name' can be created as a new branch; die otherwise.
+ * 'force' can be used when it is OK for the named branch already exists.
+ * Return 1 if the named branch already exists; return 0 otherwise.
+ * Fill ref with the full refname for the branch.
  */
-int validate_new_branchname(const char *name, struct strbuf *ref, int force, int attr_only);
+extern int validate_new_branchname(const char *name, struct strbuf *ref, int force);
 
 /*
  * Remove information about the state of working on the current
index 8d08e99e92c1fa12e1dcd4b079097b2b63d69089..bf01d89e28eaef0f8113d42c6b9d4142b8e912e4 100644 (file)
@@ -26,6 +26,7 @@ static const char * const builtin_add_usage[] = {
 };
 static int patch_interactive, add_interactive, edit_interactive;
 static int take_worktree_changes;
+static int add_renormalize;
 
 struct update_callback_data {
        int flags;
@@ -123,6 +124,25 @@ int add_files_to_cache(const char *prefix,
        return !!data.add_errors;
 }
 
+static int renormalize_tracked_files(const struct pathspec *pathspec, int flags)
+{
+       int i, retval = 0;
+
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+
+               if (ce_stage(ce))
+                       continue; /* do not touch unmerged paths */
+               if (!S_ISREG(ce->ce_mode) && !S_ISLNK(ce->ce_mode))
+                       continue; /* do not touch non blobs */
+               if (pathspec && !ce_path_match(ce, pathspec, NULL))
+                       continue;
+               retval |= add_file_to_cache(ce->name, flags | HASH_RENORMALIZE);
+       }
+
+       return retval;
+}
+
 static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec, int prefix)
 {
        char *seen;
@@ -276,6 +296,7 @@ static struct option builtin_add_options[] = {
        OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")),
        OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files")),
        OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")),
+       OPT_BOOL(0, "renormalize", &add_renormalize, N_("renormalize EOL of tracked files (implies -u)")),
        OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")),
        OPT_BOOL('A', "all", &addremove_explicit, N_("add changes from all tracked and untracked files")),
        { OPTION_CALLBACK, 0, "ignore-removal", &addremove_explicit,
@@ -406,7 +427,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                          chmod_arg[1] != 'x' || chmod_arg[2]))
                die(_("--chmod param '%s' must be either -x or +x"), chmod_arg);
 
-       add_new_files = !take_worktree_changes && !refresh_only;
+       add_new_files = !take_worktree_changes && !refresh_only && !add_renormalize;
        require_pathspec = !(take_worktree_changes || (0 < addremove_explicit));
 
        hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
@@ -500,7 +521,10 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 
        plug_bulk_checkin();
 
-       exit_status |= add_files_to_cache(prefix, &pathspec, flags);
+       if (add_renormalize)
+               exit_status |= renormalize_tracked_files(&pathspec, flags);
+       else
+               exit_status |= add_files_to_cache(prefix, &pathspec, flags);
 
        if (add_new_files)
                exit_status |= add_files(&dir, flags);
index 02853b3e05bfef638963dbfe91a7eccf3d40b9e6..3d98e52085711abc6fa5ff9fd16d2828156df375 100644 (file)
@@ -1433,7 +1433,7 @@ static void write_index_patch(const struct am_state *state)
        if (!get_oid_tree("HEAD", &head))
                tree = lookup_tree(&head);
        else
-               tree = lookup_tree(&empty_tree_oid);
+               tree = lookup_tree(the_hash_algo->empty_tree);
 
        fp = xfopen(am_path(state, "patch"), "w");
        init_revisions(&rev_info, NULL);
index 33fd5fcfd10be978f4d8ccdb6869eb05e86eb956..af95ad2192004ad3a712c15f1e50356daadb7c86 100644 (file)
@@ -463,7 +463,6 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
        struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
        struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
        int recovery = 0;
-       int clobber_head_ok;
 
        if (!oldname) {
                if (copy)
@@ -487,9 +486,10 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
         * A command like "git branch -M currentbranch currentbranch" cannot
         * cause the worktree to become inconsistent with HEAD, so allow it.
         */
-       clobber_head_ok = !strcmp(oldname, newname);
-
-       validate_new_branchname(newname, &newref, force, clobber_head_ok);
+       if (!strcmp(oldname, newname))
+               validate_branchname(newname, &newref);
+       else
+               validate_new_branchname(newname, &newref, force);
 
        reject_rebase_or_bisect_branch(oldref.buf);
 
@@ -675,6 +675,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                copy *= 2;
        }
 
+       if (list)
+               setup_auto_pager("branch", 1);
+
        if (delete) {
                if (!argc)
                        die(_("branch name required"));
@@ -793,9 +796,6 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        } else if (argc > 0 && argc <= 2) {
                struct branch *branch = branch_get(argv[0]);
 
-               if (!strcmp(argv[0], "HEAD"))
-                       die(_("it does not make sense to create 'HEAD' manually"));
-
                if (!branch)
                        die(_("no such branch '%s'"), argv[0]);
 
index 7d8bcc3833512bc262bd0a8d86db9169d991b320..e1e157d205a06ffad39a9685cab55b16c69d339c 100644 (file)
@@ -514,7 +514,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
                }
                tree = parse_tree_indirect(old->commit ?
                                           &old->commit->object.oid :
-                                          &empty_tree_oid);
+                                          the_hash_algo->empty_tree);
                init_tree_desc(&trees[0], tree->buffer, tree->size);
                tree = parse_tree_indirect(&new->commit->object.oid);
                init_tree_desc(&trees[1], tree->buffer, tree->size);
@@ -1287,11 +1287,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        if (opts.new_branch) {
                struct strbuf buf = STRBUF_INIT;
 
-               opts.branch_exists =
-                       validate_new_branchname(opts.new_branch, &buf,
-                                               !!opts.new_branch_force,
-                                               !!opts.new_branch_force);
-
+               if (opts.new_branch_force)
+                       opts.branch_exists = validate_branchname(opts.new_branch, &buf);
+               else
+                       opts.branch_exists =
+                               validate_new_branchname(opts.new_branch, &buf, 0);
                strbuf_release(&buf);
        }
 
index d13daeeb55927758ceec816f39412123d2ce5846..ab5f95476e6c726798fd84b4a6300577bf0fafba 100644 (file)
@@ -52,6 +52,7 @@ static int show_origin;
 #define TYPE_INT (1<<1)
 #define TYPE_BOOL_OR_INT (1<<2)
 #define TYPE_PATH (1<<3)
+#define TYPE_EXPIRY_DATE (1<<4)
 
 static struct option builtin_config_options[] = {
        OPT_GROUP(N_("Config file location")),
@@ -80,6 +81,7 @@ static struct option builtin_config_options[] = {
        OPT_BIT(0, "int", &types, N_("value is decimal number"), TYPE_INT),
        OPT_BIT(0, "bool-or-int", &types, N_("value is --bool or --int"), TYPE_BOOL_OR_INT),
        OPT_BIT(0, "path", &types, N_("value is a path (file or directory name)"), TYPE_PATH),
+       OPT_BIT(0, "expiry-date", &types, N_("value is an expiry date"), TYPE_EXPIRY_DATE),
        OPT_GROUP(N_("Other")),
        OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")),
        OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")),
@@ -159,6 +161,11 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value
                                return -1;
                        strbuf_addstr(buf, v);
                        free((char *)v);
+               } else if (types == TYPE_EXPIRY_DATE) {
+                       timestamp_t t;
+                       if (git_config_expiry_date(&t, key_, value_) < 0)
+                               return -1;
+                       strbuf_addf(buf, "%"PRItime, t);
                } else if (value_) {
                        strbuf_addstr(buf, value_);
                } else {
@@ -273,12 +280,13 @@ static char *normalize_value(const char *key, const char *value)
        if (!value)
                return NULL;
 
-       if (types == 0 || types == TYPE_PATH)
+       if (types == 0 || types == TYPE_PATH || types == TYPE_EXPIRY_DATE)
                /*
                 * We don't do normalization for TYPE_PATH here: If
                 * the path is like ~/foobar/, we prefer to store
                 * "~/foobar/" in the config file, and to expand the ~
                 * when retrieving the value.
+                * Also don't do normalization for expiry dates.
                 */
                return xstrdup(value);
        if (types == TYPE_INT)
index 9808d062a80a2ff07699fb0ea4cbd29f560630d3..16bfb22f7381ee8e6967ab836686c5def7cff892 100644 (file)
@@ -379,7 +379,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
                                add_head_to_pending(&rev);
                                if (!rev.pending.nr) {
                                        struct tree *tree;
-                                       tree = lookup_tree(&empty_tree_oid);
+                                       tree = lookup_tree(the_hash_algo->empty_tree);
                                        add_pending_object(&rev, &tree->object, "HEAD");
                                }
                                break;
index d7754db143e21ed92e03a29ea7932aec599ea63f..1a2c7d92ad7e73246be8dc64946feb00c16f7261 100644 (file)
@@ -33,7 +33,7 @@ static const char * const git_notes_usage[] = {
        N_("git notes merge --commit [-v | -q]"),
        N_("git notes merge --abort [-v | -q]"),
        N_("git notes [--ref <notes-ref>] remove [<object>...]"),
-       N_("git notes [--ref <notes-ref>] prune [-n | -v]"),
+       N_("git notes [--ref <notes-ref>] prune [-n] [-v]"),
        N_("git notes [--ref <notes-ref>] get-ref"),
        NULL
 };
@@ -865,10 +865,10 @@ static int merge(int argc, const char **argv, const char *prefix)
                if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
                        die(_("failed to store link to current notes ref (%s)"),
                            default_notes_ref());
-               printf(_("Automatic notes merge failed. Fix conflicts in %s and "
-                        "commit the result with 'git notes merge --commit', or "
-                        "abort the merge with 'git notes merge --abort'.\n"),
-                      git_path(NOTES_MERGE_WORKTREE));
+               fprintf(stderr, _("Automatic notes merge failed. Fix conflicts in %s "
+                                 "and commit the result with 'git notes merge --commit', "
+                                 "or abort the merge with 'git notes merge --abort'.\n"),
+                       git_path(NOTES_MERGE_WORKTREE));
        }
 
        free_notes(t);
index cddabf26a95cc22dfcad9843554dc8af26c858bc..d2fdae680a1ebe75d55d4fb66a3bdd0b54829666 100644 (file)
@@ -8,7 +8,7 @@
 #include "progress.h"
 
 static const char * const prune_usage[] = {
-       N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"),
+       N_("git prune [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]"),
        NULL
 };
 static int show_only;
index f7e2c4f2ecd0ce5c24dce1fceb614d85013810cb..511dbbe0f6e25d8f0e8c6ce511cc0ff734adc9bc 100644 (file)
@@ -113,6 +113,8 @@ static char *opt_depth;
 static char *opt_unshallow;
 static char *opt_update_shallow;
 static char *opt_refmap;
+static char *opt_ipv4;
+static char *opt_ipv6;
 
 static struct option pull_options[] = {
        /* Shared options */
@@ -218,6 +220,12 @@ static struct option pull_options[] = {
        OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"),
                N_("specify fetch refmap"),
                PARSE_OPT_NONEG),
+       OPT_PASSTHRU('4',  "ipv4", &opt_ipv4, NULL,
+               N_("use IPv4 addresses only"),
+               PARSE_OPT_NOARG),
+       OPT_PASSTHRU('6',  "ipv6", &opt_ipv6, NULL,
+               N_("use IPv6 addresses only"),
+               PARSE_OPT_NOARG),
 
        OPT_END()
 };
@@ -522,6 +530,10 @@ static int run_fetch(const char *repo, const char **refspecs)
                argv_array_push(&args, opt_update_shallow);
        if (opt_refmap)
                argv_array_push(&args, opt_refmap);
+       if (opt_ipv4)
+               argv_array_push(&args, opt_ipv4);
+       if (opt_ipv6)
+               argv_array_push(&args, opt_ipv6);
 
        if (repo) {
                argv_array_push(&args, repo);
@@ -545,7 +557,7 @@ static int pull_into_void(const struct object_id *merge_head,
         * index/worktree changes that the user already made on the unborn
         * branch.
         */
-       if (checkout_fast_forward(&empty_tree_oid, merge_head, 0))
+       if (checkout_fast_forward(the_hash_algo->empty_tree, merge_head, 0))
                return 1;
 
        if (update_ref("initial pull", "HEAD", merge_head, curr_head, 0, UPDATE_REFS_DIE_ON_ERR))
index 4d37a160d759889e28602adc6064cde044248e4e..b7ce7c7f5275febbf6db6b71e3b610b09a4fe191 100644 (file)
@@ -24,6 +24,7 @@
 #include "tmp-objdir.h"
 #include "oidset.h"
 #include "packfile.h"
+#include "protocol.h"
 
 static const char * const receive_pack_usage[] = {
        N_("git receive-pack <git-dir>"),
@@ -1961,6 +1962,22 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
        else if (0 <= receive_unpack_limit)
                unpack_limit = receive_unpack_limit;
 
+       switch (determine_protocol_version_server()) {
+       case protocol_v1:
+               /*
+                * v1 is just the original protocol with a version string,
+                * so just fall through after writing the version string.
+                */
+               if (advertise_refs || !stateless_rpc)
+                       packet_write_fmt(1, "version 1\n");
+
+               /* fallthrough */
+       case protocol_v0:
+               break;
+       case protocol_unknown_version:
+               BUG("unknown protocol version");
+       }
+
        if (advertise_refs || !stateless_rpc) {
                write_head_info();
        }
index ab31a3b6aa1d0b2a11752104a89d8a80c7ebc762..2233725315ba32832ec5c866b7dbdf72103ac74d 100644 (file)
@@ -416,16 +416,6 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
        return ent;
 }
 
-static int parse_expire_cfg_value(const char *var, const char *value, timestamp_t *expire)
-{
-       if (!value)
-               return config_error_nonbool(var);
-       if (parse_expiry_date(value, expire))
-               return error(_("'%s' for '%s' is not a valid timestamp"),
-                            value, var);
-       return 0;
-}
-
 /* expiry timer slot */
 #define EXPIRE_TOTAL   01
 #define EXPIRE_UNREACH 02
@@ -443,11 +433,11 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)
 
        if (!strcmp(key, "reflogexpire")) {
                slot = EXPIRE_TOTAL;
-               if (parse_expire_cfg_value(var, value, &expire))
+               if (git_config_expiry_date(&expire, var, value))
                        return -1;
        } else if (!strcmp(key, "reflogexpireunreachable")) {
                slot = EXPIRE_UNREACH;
-               if (parse_expire_cfg_value(var, value, &expire))
+               if (git_config_expiry_date(&expire, var, value))
                        return -1;
        } else
                return git_default_config(var, value, cb);
diff --git a/cache.h b/cache.h
index f684c7933ba38041961f210425f31d2976190054..d3e240228c9c488f78ee9be06c9d151f1d2325b2 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -14,6 +14,7 @@
 #include "hash.h"
 #include "path.h"
 #include "sha1-array.h"
+#include "repository.h"
 
 #ifndef platform_SHA_CTX
 /*
@@ -77,6 +78,8 @@ struct object_id {
        unsigned char hash[GIT_MAX_RAWSZ];
 };
 
+#define the_hash_algo the_repository->hash_algo
+
 #if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
 #define DTYPE(de)      ((de)->d_type)
 #else
@@ -450,6 +453,16 @@ static inline enum object_type object_type(unsigned int mode)
 #define GIT_QUARANTINE_ENVIRONMENT "GIT_QUARANTINE_PATH"
 #define GIT_OPTIONAL_LOCKS_ENVIRONMENT "GIT_OPTIONAL_LOCKS"
 
+/*
+ * Environment variable used in handshaking the wire protocol.
+ * Contains a colon ':' separated list of keys with optional values
+ * 'key[=value]'.  Presence of unknown keys and values must be
+ * ignored.
+ */
+#define GIT_PROTOCOL_ENVIRONMENT "GIT_PROTOCOL"
+/* HTTP header used to handshake the wire protocol */
+#define GIT_PROTOCOL_HEADER "Git-Protocol"
+
 /*
  * This environment variable is expected to contain a boolean indicating
  * whether we should or should not treat:
@@ -711,6 +724,7 @@ extern int ie_modified(struct index_state *, const struct cache_entry *, struct
 
 #define HASH_WRITE_OBJECT 1
 #define HASH_FORMAT_CHECK 2
+#define HASH_RENORMALIZE  4
 extern int index_fd(struct object_id *oid, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
 extern int index_path(struct object_id *oid, const char *path, struct stat *st, unsigned flags);
 
@@ -896,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;
 };
@@ -1028,22 +1043,22 @@ extern const struct object_id empty_blob_oid;
 
 static inline int is_empty_blob_sha1(const unsigned char *sha1)
 {
-       return !hashcmp(sha1, EMPTY_BLOB_SHA1_BIN);
+       return !hashcmp(sha1, the_hash_algo->empty_blob->hash);
 }
 
 static inline int is_empty_blob_oid(const struct object_id *oid)
 {
-       return !hashcmp(oid->hash, EMPTY_BLOB_SHA1_BIN);
+       return !oidcmp(oid, the_hash_algo->empty_blob);
 }
 
 static inline int is_empty_tree_sha1(const unsigned char *sha1)
 {
-       return !hashcmp(sha1, EMPTY_TREE_SHA1_BIN);
+       return !hashcmp(sha1, the_hash_algo->empty_tree->hash);
 }
 
 static inline int is_empty_tree_oid(const struct object_id *oid)
 {
-       return !hashcmp(oid->hash, EMPTY_TREE_SHA1_BIN);
+       return !oidcmp(oid, the_hash_algo->empty_tree);
 }
 
 /* set default permissions by passing mode arguments to open(2) */
index 03ab56d3d8d55bb328572005e98539bd1e423f7e..e617c2018d22b6d389548a3573d2deb2ec608b99 100644 (file)
--- a/config.c
+++ b/config.c
@@ -990,6 +990,16 @@ int git_config_pathname(const char **dest, const char *var, const char *value)
        return 0;
 }
 
+int git_config_expiry_date(timestamp_t *timestamp, const char *var, const char *value)
+{
+       if (!value)
+               return config_error_nonbool(var);
+       if (parse_expiry_date(value, timestamp))
+               return error(_("'%s' for '%s' is not a valid timestamp"),
+                            value, var);
+       return 0;
+}
+
 static int git_default_core_config(const char *var, const char *value)
 {
        /* This needs a better name */
@@ -2329,7 +2339,7 @@ static ssize_t write_section(int fd, const char *key)
        struct strbuf sb = store_create_section(key);
        ssize_t ret;
 
-       ret = write_in_full(fd, sb.buf, sb.len) == sb.len;
+       ret = write_in_full(fd, sb.buf, sb.len);
        strbuf_release(&sb);
 
        return ret;
@@ -2824,7 +2834,7 @@ static int git_config_copy_or_rename_section_in_file(const char *config_filename
                         * multiple [branch "$name"] sections.
                         */
                        if (copystr.len > 0) {
-                               if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) {
+                               if (write_in_full(out_fd, copystr.buf, copystr.len) < 0) {
                                        ret = write_error(get_lock_file_path(&lock));
                                        goto out;
                                }
@@ -2886,7 +2896,7 @@ static int git_config_copy_or_rename_section_in_file(const char *config_filename
         * logic in the loop above.
         */
        if (copystr.len > 0) {
-               if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) {
+               if (write_in_full(out_fd, copystr.buf, copystr.len) < 0) {
                        ret = write_error(get_lock_file_path(&lock));
                        goto out;
                }
index 524d411823e1949dd180ada236a6f4edee8ef7eb..ef70a9cac1e6dc67df24d157a4e5de38edd8c984 100644 (file)
--- a/config.h
+++ b/config.h
@@ -58,6 +58,7 @@ extern int git_config_bool_or_int(const char *, const char *, int *);
 extern int git_config_bool(const char *, const char *);
 extern int git_config_string(const char **, const char *, const char *);
 extern int git_config_pathname(const char **, const char *, const char *);
+extern int git_config_expiry_date(timestamp_t *, const char *, const char *);
 extern int git_config_set_in_file_gently(const char *, const char *, const char *);
 extern void git_config_set_in_file(const char *, const char *, const char *);
 extern int git_config_set_gently(const char *, const char *);
index df56c0cbff4dfb1263917f6147bd7e1912418dd6..c3a014c5babf72ee4c0d135fec264afb37b040de 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -11,6 +11,8 @@
 #include "string-list.h"
 #include "sha1-array.h"
 #include "transport.h"
+#include "strbuf.h"
+#include "protocol.h"
 
 static char *server_capabilities;
 static const char *parse_feature_value(const char *, const char *, int *);
@@ -107,6 +109,118 @@ static void annotate_refs_with_symref_info(struct ref *ref)
        string_list_clear(&symref, 0);
 }
 
+/*
+ * Read one line of a server's ref advertisement into packet_buffer.
+ */
+static int read_remote_ref(int in, char **src_buf, size_t *src_len,
+                          int *responded)
+{
+       int len = packet_read(in, src_buf, src_len,
+                             packet_buffer, sizeof(packet_buffer),
+                             PACKET_READ_GENTLE_ON_EOF |
+                             PACKET_READ_CHOMP_NEWLINE);
+       const char *arg;
+       if (len < 0)
+               die_initial_contact(*responded);
+       if (len > 4 && skip_prefix(packet_buffer, "ERR ", &arg))
+               die("remote error: %s", arg);
+
+       *responded = 1;
+
+       return len;
+}
+
+#define EXPECTING_PROTOCOL_VERSION 0
+#define EXPECTING_FIRST_REF 1
+#define EXPECTING_REF 2
+#define EXPECTING_SHALLOW 3
+
+/* Returns 1 if packet_buffer is a protocol version pkt-line, 0 otherwise. */
+static int process_protocol_version(void)
+{
+       switch (determine_protocol_version_client(packet_buffer)) {
+       case protocol_v1:
+               return 1;
+       case protocol_v0:
+               return 0;
+       default:
+               die("server is speaking an unknown protocol");
+       }
+}
+
+static void process_capabilities(int *len)
+{
+       int nul_location = strlen(packet_buffer);
+       if (nul_location == *len)
+               return;
+       server_capabilities = xstrdup(packet_buffer + nul_location + 1);
+       *len = nul_location;
+}
+
+static int process_dummy_ref(void)
+{
+       struct object_id oid;
+       const char *name;
+
+       if (parse_oid_hex(packet_buffer, &oid, &name))
+               return 0;
+       if (*name != ' ')
+               return 0;
+       name++;
+
+       return !oidcmp(&null_oid, &oid) && !strcmp(name, "capabilities^{}");
+}
+
+static void check_no_capabilities(int len)
+{
+       if (strlen(packet_buffer) != len)
+               warning("Ignoring capabilities after first line '%s'",
+                       packet_buffer + strlen(packet_buffer));
+}
+
+static int process_ref(int len, struct ref ***list, unsigned int flags,
+                      struct oid_array *extra_have)
+{
+       struct object_id old_oid;
+       const char *name;
+
+       if (parse_oid_hex(packet_buffer, &old_oid, &name))
+               return 0;
+       if (*name != ' ')
+               return 0;
+       name++;
+
+       if (extra_have && !strcmp(name, ".have")) {
+               oid_array_append(extra_have, &old_oid);
+       } else if (!strcmp(name, "capabilities^{}")) {
+               die("protocol error: unexpected capabilities^{}");
+       } else if (check_ref(name, flags)) {
+               struct ref *ref = alloc_ref(name);
+               oidcpy(&ref->old_oid, &old_oid);
+               **list = ref;
+               *list = &ref->next;
+       }
+       check_no_capabilities(len);
+       return 1;
+}
+
+static int process_shallow(int len, struct oid_array *shallow_points)
+{
+       const char *arg;
+       struct object_id old_oid;
+
+       if (!skip_prefix(packet_buffer, "shallow ", &arg))
+               return 0;
+
+       if (get_oid_hex(arg, &old_oid))
+               die("protocol error: expected shallow sha-1, got '%s'", arg);
+       if (!shallow_points)
+               die("repository on the other end cannot be shallow");
+       oid_array_append(shallow_points, &old_oid);
+       check_no_capabilities(len);
+       return 1;
+}
+
 /*
  * Read all the refs from the other end
  */
@@ -123,76 +237,41 @@ struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
         * willing to talk to us.  A hang-up before seeing any
         * response does not necessarily mean an ACL problem, though.
         */
-       int saw_response;
-       int got_dummy_ref_with_capabilities_declaration = 0;
+       int responded = 0;
+       int len;
+       int state = EXPECTING_PROTOCOL_VERSION;
 
        *list = NULL;
-       for (saw_response = 0; ; saw_response = 1) {
-               struct ref *ref;
-               struct object_id old_oid;
-               char *name;
-               int len, name_len;
-               char *buffer = packet_buffer;
-               const char *arg;
-
-               len = packet_read(in, &src_buf, &src_len,
-                                 packet_buffer, sizeof(packet_buffer),
-                                 PACKET_READ_GENTLE_ON_EOF |
-                                 PACKET_READ_CHOMP_NEWLINE);
-               if (len < 0)
-                       die_initial_contact(saw_response);
-
-               if (!len)
-                       break;
-
-               if (len > 4 && skip_prefix(buffer, "ERR ", &arg))
-                       die("remote error: %s", arg);
-
-               if (len == GIT_SHA1_HEXSZ + strlen("shallow ") &&
-                       skip_prefix(buffer, "shallow ", &arg)) {
-                       if (get_oid_hex(arg, &old_oid))
-                               die("protocol error: expected shallow sha-1, got '%s'", arg);
-                       if (!shallow_points)
-                               die("repository on the other end cannot be shallow");
-                       oid_array_append(shallow_points, &old_oid);
-                       continue;
-               }
-
-               if (len < GIT_SHA1_HEXSZ + 2 || get_oid_hex(buffer, &old_oid) ||
-                       buffer[GIT_SHA1_HEXSZ] != ' ')
-                       die("protocol error: expected sha/ref, got '%s'", buffer);
-               name = buffer + GIT_SHA1_HEXSZ + 1;
-
-               name_len = strlen(name);
-               if (len != name_len + GIT_SHA1_HEXSZ + 1) {
-                       free(server_capabilities);
-                       server_capabilities = xstrdup(name + name_len + 1);
-               }
 
-               if (extra_have && !strcmp(name, ".have")) {
-                       oid_array_append(extra_have, &old_oid);
-                       continue;
-               }
-
-               if (!strcmp(name, "capabilities^{}")) {
-                       if (saw_response)
-                               die("protocol error: unexpected capabilities^{}");
-                       if (got_dummy_ref_with_capabilities_declaration)
-                               die("protocol error: multiple capabilities^{}");
-                       got_dummy_ref_with_capabilities_declaration = 1;
-                       continue;
+       while ((len = read_remote_ref(in, &src_buf, &src_len, &responded))) {
+               switch (state) {
+               case EXPECTING_PROTOCOL_VERSION:
+                       if (process_protocol_version()) {
+                               state = EXPECTING_FIRST_REF;
+                               break;
+                       }
+                       state = EXPECTING_FIRST_REF;
+                       /* fallthrough */
+               case EXPECTING_FIRST_REF:
+                       process_capabilities(&len);
+                       if (process_dummy_ref()) {
+                               state = EXPECTING_SHALLOW;
+                               break;
+                       }
+                       state = EXPECTING_REF;
+                       /* fallthrough */
+               case EXPECTING_REF:
+                       if (process_ref(len, &list, flags, extra_have))
+                               break;
+                       state = EXPECTING_SHALLOW;
+                       /* fallthrough */
+               case EXPECTING_SHALLOW:
+                       if (process_shallow(len, shallow_points))
+                               break;
+                       die("protocol error: unexpected '%s'", packet_buffer);
+               default:
+                       die("unexpected state %d", state);
                }
-
-               if (!check_ref(name, flags))
-                       continue;
-
-               if (got_dummy_ref_with_capabilities_declaration)
-                       die("protocol error: unexpected ref after capabilities^{}");
-
-               ref = alloc_ref(buffer + GIT_SHA1_HEXSZ + 1);
-               oidcpy(&ref->old_oid, &old_oid);
-               *list = ref;
-               list = &ref->next;
        }
 
        annotate_refs_with_symref_info(*orig_list);
@@ -503,12 +582,25 @@ static int git_tcp_connect_sock(char *host, int flags)
 #endif /* NO_IPV6 */
 
 
-static void git_tcp_connect(int fd[2], char *host, int flags)
+/*
+ * Dummy child_process returned by git_connect() if the transport protocol
+ * does not need fork(2).
+ */
+static struct child_process no_fork = CHILD_PROCESS_INIT;
+
+int git_connection_is_socket(struct child_process *conn)
+{
+       return conn == &no_fork;
+}
+
+static struct child_process *git_tcp_connect(int fd[2], char *host, int flags)
 {
        int sockfd = git_tcp_connect_sock(host, flags);
 
        fd[0] = sockfd;
        fd[1] = dup(sockfd);
+
+       return &no_fork;
 }
 
 
@@ -682,8 +774,6 @@ static enum protocol parse_connect_url(const char *url_orig, char **ret_host,
        return protocol;
 }
 
-static struct child_process no_fork = CHILD_PROCESS_INIT;
-
 static const char *get_ssh_command(void)
 {
        const char *ssh;
@@ -697,37 +787,47 @@ static const char *get_ssh_command(void)
        return NULL;
 }
 
-static int override_ssh_variant(int *port_option, int *needs_batch)
+enum ssh_variant {
+       VARIANT_AUTO,
+       VARIANT_SIMPLE,
+       VARIANT_SSH,
+       VARIANT_PLINK,
+       VARIANT_PUTTY,
+       VARIANT_TORTOISEPLINK,
+};
+
+static void override_ssh_variant(enum ssh_variant *ssh_variant)
 {
-       char *variant;
+       const char *variant = getenv("GIT_SSH_VARIANT");
 
-       variant = xstrdup_or_null(getenv("GIT_SSH_VARIANT"));
-       if (!variant &&
-           git_config_get_string("ssh.variant", &variant))
-               return 0;
+       if (!variant && git_config_get_string_const("ssh.variant", &variant))
+               return;
 
-       if (!strcmp(variant, "plink") || !strcmp(variant, "putty")) {
-               *port_option = 'P';
-               *needs_batch = 0;
-       } else if (!strcmp(variant, "tortoiseplink")) {
-               *port_option = 'P';
-               *needs_batch = 1;
-       } else {
-               *port_option = 'p';
-               *needs_batch = 0;
-       }
-       free(variant);
-       return 1;
+       if (!strcmp(variant, "auto"))
+               *ssh_variant = VARIANT_AUTO;
+       else if (!strcmp(variant, "plink"))
+               *ssh_variant = VARIANT_PLINK;
+       else if (!strcmp(variant, "putty"))
+               *ssh_variant = VARIANT_PUTTY;
+       else if (!strcmp(variant, "tortoiseplink"))
+               *ssh_variant = VARIANT_TORTOISEPLINK;
+       else if (!strcmp(variant, "simple"))
+               *ssh_variant = VARIANT_SIMPLE;
+       else
+               *ssh_variant = VARIANT_SSH;
 }
 
-static void handle_ssh_variant(const char *ssh_command, int is_cmdline,
-                              int *port_option, int *needs_batch)
+static enum ssh_variant determine_ssh_variant(const char *ssh_command,
+                                             int is_cmdline)
 {
+       enum ssh_variant ssh_variant = VARIANT_AUTO;
        const char *variant;
        char *p = NULL;
 
-       if (override_ssh_variant(port_option, needs_batch))
-               return;
+       override_ssh_variant(&ssh_variant);
+
+       if (ssh_variant != VARIANT_AUTO)
+               return ssh_variant;
 
        if (!is_cmdline) {
                p = xstrdup(ssh_command);
@@ -746,27 +846,200 @@ static void handle_ssh_variant(const char *ssh_command, int is_cmdline,
                        free(ssh_argv);
                } else {
                        free(p);
-                       return;
+                       return ssh_variant;
                }
        }
 
-       if (!strcasecmp(variant, "plink") ||
-           !strcasecmp(variant, "plink.exe"))
-               *port_option = 'P';
+       if (!strcasecmp(variant, "ssh") ||
+           !strcasecmp(variant, "ssh.exe"))
+               ssh_variant = VARIANT_SSH;
+       else if (!strcasecmp(variant, "plink") ||
+                !strcasecmp(variant, "plink.exe"))
+               ssh_variant = VARIANT_PLINK;
        else if (!strcasecmp(variant, "tortoiseplink") ||
-                !strcasecmp(variant, "tortoiseplink.exe")) {
-               *port_option = 'P';
-               *needs_batch = 1;
-       }
+                !strcasecmp(variant, "tortoiseplink.exe"))
+               ssh_variant = VARIANT_TORTOISEPLINK;
+
        free(p);
+       return ssh_variant;
+}
+
+/*
+ * Open a connection using Git's native protocol.
+ *
+ * The caller is responsible for freeing hostandport, but this function may
+ * modify it (for example, to truncate it to remove the port part).
+ */
+static struct child_process *git_connect_git(int fd[2], char *hostandport,
+                                            const char *path, const char *prog,
+                                            int flags)
+{
+       struct child_process *conn;
+       struct strbuf request = STRBUF_INIT;
+       /*
+        * Set up virtual host information based on where we will
+        * connect, unless the user has overridden us in
+        * the environment.
+        */
+       char *target_host = getenv("GIT_OVERRIDE_VIRTUAL_HOST");
+       if (target_host)
+               target_host = xstrdup(target_host);
+       else
+               target_host = xstrdup(hostandport);
+
+       transport_check_allowed("git");
+
+       /*
+        * These underlying connection commands die() if they
+        * cannot connect.
+        */
+       if (git_use_proxy(hostandport))
+               conn = git_proxy_connect(fd, hostandport);
+       else
+               conn = git_tcp_connect(fd, hostandport, flags);
+       /*
+        * Separate original protocol components prog and path
+        * from extended host header with a NUL byte.
+        *
+        * Note: Do not add any other headers here!  Doing so
+        * will cause older git-daemon servers to crash.
+        */
+       strbuf_addf(&request,
+                   "%s %s%chost=%s%c",
+                   prog, path, 0,
+                   target_host, 0);
+
+       /* If using a new version put that stuff here after a second null byte */
+       if (get_protocol_version_config() > 0) {
+               strbuf_addch(&request, '\0');
+               strbuf_addf(&request, "version=%d%c",
+                           get_protocol_version_config(), '\0');
+       }
+
+       packet_write(fd[1], request.buf, request.len);
+
+       free(target_host);
+       strbuf_release(&request);
+       return conn;
+}
+
+/*
+ * Append the appropriate environment variables to `env` and options to
+ * `args` for running ssh in Git's SSH-tunneled transport.
+ */
+static void push_ssh_options(struct argv_array *args, struct argv_array *env,
+                            enum ssh_variant variant, const char *port,
+                            int flags)
+{
+       if (variant == VARIANT_SSH &&
+           get_protocol_version_config() > 0) {
+               argv_array_push(args, "-o");
+               argv_array_push(args, "SendEnv=" GIT_PROTOCOL_ENVIRONMENT);
+               argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=version=%d",
+                                get_protocol_version_config());
+       }
+
+       if (flags & CONNECT_IPV4) {
+               switch (variant) {
+               case VARIANT_AUTO:
+                       BUG("VARIANT_AUTO passed to push_ssh_options");
+               case VARIANT_SIMPLE:
+                       die("ssh variant 'simple' does not support -4");
+               case VARIANT_SSH:
+               case VARIANT_PLINK:
+               case VARIANT_PUTTY:
+               case VARIANT_TORTOISEPLINK:
+                       argv_array_push(args, "-4");
+               }
+       } else if (flags & CONNECT_IPV6) {
+               switch (variant) {
+               case VARIANT_AUTO:
+                       BUG("VARIANT_AUTO passed to push_ssh_options");
+               case VARIANT_SIMPLE:
+                       die("ssh variant 'simple' does not support -6");
+               case VARIANT_SSH:
+               case VARIANT_PLINK:
+               case VARIANT_PUTTY:
+               case VARIANT_TORTOISEPLINK:
+                       argv_array_push(args, "-6");
+               }
+       }
+
+       if (variant == VARIANT_TORTOISEPLINK)
+               argv_array_push(args, "-batch");
+
+       if (port) {
+               switch (variant) {
+               case VARIANT_AUTO:
+                       BUG("VARIANT_AUTO passed to push_ssh_options");
+               case VARIANT_SIMPLE:
+                       die("ssh variant 'simple' does not support setting port");
+               case VARIANT_SSH:
+                       argv_array_push(args, "-p");
+                       break;
+               case VARIANT_PLINK:
+               case VARIANT_PUTTY:
+               case VARIANT_TORTOISEPLINK:
+                       argv_array_push(args, "-P");
+               }
+
+               argv_array_push(args, port);
+       }
+}
+
+/* Prepare a child_process for use by Git's SSH-tunneled transport. */
+static void fill_ssh_args(struct child_process *conn, const char *ssh_host,
+                         const char *port, int flags)
+{
+       const char *ssh;
+       enum ssh_variant variant;
+
+       if (looks_like_command_line_option(ssh_host))
+               die("strange hostname '%s' blocked", ssh_host);
+
+       ssh = get_ssh_command();
+       if (ssh) {
+               variant = determine_ssh_variant(ssh, 1);
+       } else {
+               /*
+                * GIT_SSH is the no-shell version of
+                * GIT_SSH_COMMAND (and must remain so for
+                * historical compatibility).
+                */
+               conn->use_shell = 0;
+
+               ssh = getenv("GIT_SSH");
+               if (!ssh)
+                       ssh = "ssh";
+               variant = determine_ssh_variant(ssh, 0);
+       }
+
+       if (variant == VARIANT_AUTO) {
+               struct child_process detect = CHILD_PROCESS_INIT;
+
+               detect.use_shell = conn->use_shell;
+               detect.no_stdin = detect.no_stdout = detect.no_stderr = 1;
+
+               argv_array_push(&detect.args, ssh);
+               argv_array_push(&detect.args, "-G");
+               push_ssh_options(&detect.args, &detect.env_array,
+                                VARIANT_SSH, port, flags);
+               argv_array_push(&detect.args, ssh_host);
+
+               variant = run_command(&detect) ? VARIANT_SIMPLE : VARIANT_SSH;
+       }
+
+       argv_array_push(&conn->args, ssh);
+       push_ssh_options(&conn->args, &conn->env_array, variant, port, flags);
+       argv_array_push(&conn->args, ssh_host);
 }
 
 /*
- * This returns a dummy child_process if the transport protocol does not
- * need fork(2), or a struct child_process object if it does.  Once done,
- * finish the connection with finish_connect() with the value returned from
- * this function (it is safe to call finish_connect() with NULL to support
- * the former case).
+ * This returns the dummy child_process `no_fork` if the transport protocol
+ * does not need fork(2), or a struct child_process object if it does.  Once
+ * done, finish the connection with finish_connect() with the value returned
+ * from this function (it is safe to call finish_connect() with NULL to
+ * support the former case).
  *
  * If it returns, the connect is successful; it just dies on errors (this
  * will hopefully be changed in a libification effort, to return NULL when
@@ -776,7 +1049,7 @@ struct child_process *git_connect(int fd[2], const char *url,
                                  const char *prog, int flags)
 {
        char *hostandport, *path;
-       struct child_process *conn = &no_fork;
+       struct child_process *conn;
        enum protocol protocol;
 
        /* Without this we cannot rely on waitpid() to tell
@@ -792,40 +1065,10 @@ struct child_process *git_connect(int fd[2], const char *url,
                printf("Diag: path=%s\n", path ? path : "NULL");
                conn = NULL;
        } else if (protocol == PROTO_GIT) {
-               /*
-                * Set up virtual host information based on where we will
-                * connect, unless the user has overridden us in
-                * the environment.
-                */
-               char *target_host = getenv("GIT_OVERRIDE_VIRTUAL_HOST");
-               if (target_host)
-                       target_host = xstrdup(target_host);
-               else
-                       target_host = xstrdup(hostandport);
-
-               transport_check_allowed("git");
-
-               /* These underlying connection commands die() if they
-                * cannot connect.
-                */
-               if (git_use_proxy(hostandport))
-                       conn = git_proxy_connect(fd, hostandport);
-               else
-                       git_tcp_connect(fd, hostandport, flags);
-               /*
-                * Separate original protocol components prog and path
-                * from extended host header with a NUL byte.
-                *
-                * Note: Do not add any other headers here!  Doing so
-                * will cause older git-daemon servers to crash.
-                */
-               packet_write_fmt(fd[1],
-                            "%s %s%chost=%s%c",
-                            prog, path, 0,
-                            target_host, 0);
-               free(target_host);
+               conn = git_connect_git(fd, hostandport, path, prog, flags);
        } else {
                struct strbuf cmd = STRBUF_INIT;
+               const char *const *var;
 
                conn = xmalloc(sizeof(*conn));
                child_process_init(conn);
@@ -838,13 +1081,12 @@ struct child_process *git_connect(int fd[2], const char *url,
                sq_quote_buf(&cmd, path);
 
                /* remove repo-local variables from the environment */
-               conn->env = local_repo_env;
+               for (var = local_repo_env; *var; var++)
+                       argv_array_push(&conn->env_array, *var);
+
                conn->use_shell = 1;
                conn->in = conn->out = -1;
                if (protocol == PROTO_SSH) {
-                       const char *ssh;
-                       int needs_batch = 0;
-                       int port_option = 'p';
                        char *ssh_host = hostandport;
                        const char *port = NULL;
                        transport_check_allowed("ssh");
@@ -866,46 +1108,13 @@ struct child_process *git_connect(int fd[2], const char *url,
                                strbuf_release(&cmd);
                                return NULL;
                        }
-
-                       if (looks_like_command_line_option(ssh_host))
-                               die("strange hostname '%s' blocked", ssh_host);
-
-                       ssh = get_ssh_command();
-                       if (ssh)
-                               handle_ssh_variant(ssh, 1, &port_option,
-                                                  &needs_batch);
-                       else {
-                               /*
-                                * GIT_SSH is the no-shell version of
-                                * GIT_SSH_COMMAND (and must remain so for
-                                * historical compatibility).
-                                */
-                               conn->use_shell = 0;
-
-                               ssh = getenv("GIT_SSH");
-                               if (!ssh)
-                                       ssh = "ssh";
-                               else
-                                       handle_ssh_variant(ssh, 0,
-                                                          &port_option,
-                                                          &needs_batch);
-                       }
-
-                       argv_array_push(&conn->args, ssh);
-                       if (flags & CONNECT_IPV4)
-                               argv_array_push(&conn->args, "-4");
-                       else if (flags & CONNECT_IPV6)
-                               argv_array_push(&conn->args, "-6");
-                       if (needs_batch)
-                               argv_array_push(&conn->args, "-batch");
-                       if (port) {
-                               argv_array_pushf(&conn->args,
-                                                "-%c", port_option);
-                               argv_array_push(&conn->args, port);
-                       }
-                       argv_array_push(&conn->args, ssh_host);
+                       fill_ssh_args(conn, ssh_host, port, flags);
                } else {
                        transport_check_allowed("file");
+                       if (get_protocol_version_config() > 0) {
+                               argv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT "=version=%d",
+                                                get_protocol_version_config());
+                       }
                }
                argv_array_push(&conn->args, cmd.buf);
 
@@ -921,11 +1130,6 @@ struct child_process *git_connect(int fd[2], const char *url,
        return conn;
 }
 
-int git_connection_is_socket(struct child_process *conn)
-{
-       return conn == &no_fork;
-}
-
 int finish_connect(struct child_process *conn)
 {
        int code;
index f07f16b28fe80de32ebc0431b5d1d8f5a64e19d8..3683c772c5586ccc1f6dfc8c047919d8d96aa891 100644 (file)
@@ -1204,7 +1204,7 @@ _git_branch ()
                        --color --no-color --verbose --abbrev= --no-abbrev
                        --track --no-track --contains --no-contains --merged --no-merged
                        --set-upstream-to= --edit-description --list
-                       --unset-upstream --delete --move --remotes
+                       --unset-upstream --delete --move --copy --remotes
                        --column --no-column --sort= --points-at
                        "
                ;;
@@ -1400,7 +1400,7 @@ __git_diff_common_options="--stat --numstat --shortstat --summary
                        --patch-with-stat --name-only --name-status --color
                        --no-color --color-words --no-renames --check
                        --full-index --binary --abbrev --diff-filter=
-                       --find-copies-harder
+                       --find-copies-harder --ignore-cr-at-eol
                        --text --ignore-space-at-eol --ignore-space-change
                        --ignore-all-space --ignore-blank-lines --exit-code
                        --quiet --ext-diff --no-ext-diff
@@ -1922,6 +1922,7 @@ _git_pull ()
        --*)
                __gitcomp "
                        --rebase --no-rebase
+                       --autostash --no-autostash
                        $__git_merge_options
                        $__git_fetch_options
                "
@@ -2641,6 +2642,7 @@ _git_config ()
                sendemail.suppressfrom
                sendemail.thread
                sendemail.to
+               sendemail.tocmd
                sendemail.validate
                sendemail.smtpbatchsize
                sendemail.smtprelogindelay
index 30747075f0c45942220f63202f8c984e3e0956a7..e37e343d0aec37fd74e7bdf694b99f59d022690a 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -282,7 +282,7 @@ static const char *path_ok(const char *directory, struct hostinfo *hi)
        return NULL;            /* Fallthrough. Deny by default */
 }
 
-typedef int (*daemon_service_fn)(void);
+typedef int (*daemon_service_fn)(const struct argv_array *env);
 struct daemon_service {
        const char *name;
        const char *config_name;
@@ -363,7 +363,7 @@ static int run_access_hook(struct daemon_service *service, const char *dir,
 }
 
 static int run_service(const char *dir, struct daemon_service *service,
-                      struct hostinfo *hi)
+                      struct hostinfo *hi, const struct argv_array *env)
 {
        const char *path;
        int enabled = service->enabled;
@@ -422,7 +422,7 @@ static int run_service(const char *dir, struct daemon_service *service,
         */
        signal(SIGTERM, SIG_IGN);
 
-       return service->fn();
+       return service->fn(env);
 }
 
 static void copy_to_log(int fd)
@@ -462,25 +462,34 @@ static int run_service_command(struct child_process *cld)
        return finish_command(cld);
 }
 
-static int upload_pack(void)
+static int upload_pack(const struct argv_array *env)
 {
        struct child_process cld = CHILD_PROCESS_INIT;
        argv_array_pushl(&cld.args, "upload-pack", "--strict", NULL);
        argv_array_pushf(&cld.args, "--timeout=%u", timeout);
+
+       argv_array_pushv(&cld.env_array, env->argv);
+
        return run_service_command(&cld);
 }
 
-static int upload_archive(void)
+static int upload_archive(const struct argv_array *env)
 {
        struct child_process cld = CHILD_PROCESS_INIT;
        argv_array_push(&cld.args, "upload-archive");
+
+       argv_array_pushv(&cld.env_array, env->argv);
+
        return run_service_command(&cld);
 }
 
-static int receive_pack(void)
+static int receive_pack(const struct argv_array *env)
 {
        struct child_process cld = CHILD_PROCESS_INIT;
        argv_array_push(&cld.args, "receive-pack");
+
+       argv_array_pushv(&cld.env_array, env->argv);
+
        return run_service_command(&cld);
 }
 
@@ -573,8 +582,11 @@ static void canonicalize_client(struct strbuf *out, const char *in)
 
 /*
  * Read the host as supplied by the client connection.
+ *
+ * Returns a pointer to the character after the NUL byte terminating the host
+ * arguemnt, or 'extra_args' if there is no host arguemnt.
  */
-static void parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
+static char *parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
 {
        char *val;
        int vallen;
@@ -602,6 +614,43 @@ static void parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
                if (extra_args < end && *extra_args)
                        die("Invalid request");
        }
+
+       return extra_args;
+}
+
+static void parse_extra_args(struct hostinfo *hi, struct argv_array *env,
+                            char *extra_args, int buflen)
+{
+       const char *end = extra_args + buflen;
+       struct strbuf git_protocol = STRBUF_INIT;
+
+       /* First look for the host argument */
+       extra_args = parse_host_arg(hi, extra_args, buflen);
+
+       /* Look for additional arguments places after a second NUL byte */
+       for (; extra_args < end; extra_args += strlen(extra_args) + 1) {
+               const char *arg = extra_args;
+
+               /*
+                * Parse the extra arguments, adding most to 'git_protocol'
+                * which will be used to set the 'GIT_PROTOCOL' envvar in the
+                * service that will be run.
+                *
+                * If there ends up being a particular arg in the future that
+                * git-daemon needs to parse specificly (like the 'host' arg)
+                * then it can be parsed here and not added to 'git_protocol'.
+                */
+               if (*arg) {
+                       if (git_protocol.len > 0)
+                               strbuf_addch(&git_protocol, ':');
+                       strbuf_addstr(&git_protocol, arg);
+               }
+       }
+
+       if (git_protocol.len > 0)
+               argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=%s",
+                                git_protocol.buf);
+       strbuf_release(&git_protocol);
 }
 
 /*
@@ -695,6 +744,7 @@ static int execute(void)
        int pktlen, len, i;
        char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT");
        struct hostinfo hi;
+       struct argv_array env = ARGV_ARRAY_INIT;
 
        hostinfo_init(&hi);
 
@@ -716,8 +766,9 @@ static int execute(void)
                pktlen--;
        }
 
+       /* parse additional args hidden behind a NUL byte */
        if (len != pktlen)
-               parse_host_arg(&hi, line + len + 1, pktlen - len - 1);
+               parse_extra_args(&hi, &env, line + len + 1, pktlen - len - 1);
 
        for (i = 0; i < ARRAY_SIZE(daemon_service); i++) {
                struct daemon_service *s = &(daemon_service[i]);
@@ -730,13 +781,15 @@ static int execute(void)
                         * Note: The directory here is probably context sensitive,
                         * and might depend on the actual service being performed.
                         */
-                       int rc = run_service(arg, s, &hi);
+                       int rc = run_service(arg, s, &hi, &env);
                        hostinfo_clear(&hi);
+                       argv_array_clear(&env);
                        return rc;
                }
        }
 
        hostinfo_clear(&hi);
+       argv_array_clear(&env);
        logerror("Protocol error: '%s'", line);
        return -1;
 }
index 5173023cd3c3e4390e9ddc08e898d813f6a32093..8104603a3b36f2fd73761db34efde44d5938f68f 100644 (file)
@@ -218,7 +218,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                        } else if (revs->diffopt.ita_invisible_in_index &&
                                   ce_intent_to_add(ce)) {
                                diff_addremove(&revs->diffopt, '+', ce->ce_mode,
-                                              &empty_tree_oid, 0,
+                                              the_hash_algo->empty_tree, 0,
                                               ce->name, 0);
                                continue;
                        }
diff --git a/diff.c b/diff.c
index 0763e89263efac5fe75a57f0c068049fae610851..2ebe2227b467e53bbd759c21e463646701071999 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -4129,9 +4129,7 @@ void diff_setup_done(struct diff_options *options)
         * inside contents.
         */
 
-       if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) ||
-           DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) ||
-           DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL))
+       if ((options->xdl_opts & XDF_WHITESPACE_FLAGS))
                options->flags.diff_from_contents = 1;
        else
                options->flags.diff_from_contents = 0;
@@ -4588,6 +4586,8 @@ int diff_opt_parse(struct diff_options *options,
                DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE);
        else if (!strcmp(arg, "--ignore-space-at-eol"))
                DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
+       else if (!strcmp(arg, "--ignore-cr-at-eol"))
+               DIFF_XDL_SET(options, IGNORE_CR_AT_EOL);
        else if (!strcmp(arg, "--ignore-blank-lines"))
                DIFF_XDL_SET(options, IGNORE_BLANK_LINES);
        else if (!strcmp(arg, "--indent-heuristic"))
index 008b25d3db08726b0fc1569f88fba76694bbcc12..9f6b07ad91f8c2c17e85004a0e71a8b69752d120 100644 (file)
@@ -716,7 +716,8 @@ static int everything_local(struct fetch_pack_args *args,
        for (ref = *refs; ref; ref = ref->next) {
                struct object *o;
 
-               if (!has_object_file(&ref->old_oid))
+               if (!has_object_file_with_flags(&ref->old_oid,
+                                               OBJECT_INFO_QUICK))
                        continue;
 
                o = parse_object(&ref->old_oid);
index ab0d1b0c064a72480d7eac454b297ce95a48c8f3..eeea4b67ea7f1ccee1770c18a36d018b5f3b32b0 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-echo "/* Automatically generated by $0 */
+echo "/* Automatically generated by generate-cmdlist.sh */
 struct cmdname_help {
        char name[16];
        char help[80];
index 918a8de369113bb022f5080ac9c516771f544577..f10caedaa7a3970628148258ed8dc9f6a5e8c827 100644 (file)
@@ -254,7 +254,7 @@ $(ALL_MSGFILES): %.msg : %.po
 lib/tclIndex: $(ALL_LIBFILES) GIT-GUI-VARS
        $(QUIET_INDEX)if echo \
          $(foreach p,$(PRELOAD_FILES),source $p\;) \
-         auto_mkindex lib '*.tcl' \
+         auto_mkindex lib $(patsubst lib/%,%,$(sort $(ALL_LIBFILES))) \
        | $(TCL_PATH) $(QUIET_2DEVNULL); then : ok; \
        else \
         echo >&2 "    * $(TCL_PATH) failed; using unoptimized loading"; \
index 6e64d40d6fb8fe1566dd26c3df534afe2278390c..14c50782e096966b860d49cbaed7658e9f56958f 100644 (file)
@@ -53,6 +53,7 @@ else
 
        git format-patch -k --stdout --full-index --cherry-pick --right-only \
                --src-prefix=a/ --dst-prefix=b/ --no-renames --no-cover-letter \
+               --pretty=mboxrd \
                $git_format_patch_opt \
                "$revisions" ${restrict_revision+^$restrict_revision} \
                >"$GIT_DIR/rebased-patches"
@@ -83,6 +84,7 @@ else
        fi
 
        git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" \
+               --patch-format=mboxrd \
                $allow_rerere_autoupdate \
                ${gpg_sign_opt:+"$gpg_sign_opt"} <"$GIT_DIR/rebased-patches"
        ret=$?
index 6344e8d5e38ca237dc5b5b67844f87f48aaa74ca..60b70f3defb5678af099e4c6bd7fe0610035a432 100755 (executable)
@@ -9,7 +9,7 @@ OPTIONS_STUCKLONG=t
 OPTIONS_SPEC="\
 git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] [<upstream>] [<branch>]
 git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] --root [<branch>]
-git-rebase --continue | --abort | --skip | --edit-todo
+git rebase --continue | --abort | --skip | --edit-todo
 --
  Available options are
 v,verbose!         display a diffstat of what changed upstream
@@ -166,7 +166,7 @@ apply_autostash () {
        if test -f "$state_dir/autostash"
        then
                stash_sha1=$(cat "$state_dir/autostash")
-               if git stash apply $stash_sha1 2>&1 >/dev/null
+               if git stash apply $stash_sha1 >/dev/null 2>&1
                then
                        echo "$(gettext 'Applied autostash.')" >&2
                else
@@ -216,7 +216,7 @@ run_pre_rebase_hook () {
 }
 
 test -f "$apply_dir"/applying &&
-       die "$(gettext "It looks like git-am is in progress. Cannot rebase.")"
+       die "$(gettext "It looks like 'git am' is in progress. Cannot rebase.")"
 
 if test -d "$apply_dir"
 then
index 4b7495144013b6a224571376a411cdcbcfd055aa..1114005ce2829011cde2e6de608a2b90df355fc9 100755 (executable)
@@ -76,6 +76,12 @@ create_stash () {
                        shift
                        stash_msg=${1?"BUG: create_stash () -m requires an argument"}
                        ;;
+               -m*)
+                       stash_msg=${1#-m}
+                       ;;
+               --message=*)
+                       stash_msg=${1#--message=}
+                       ;;
                -u|--include-untracked)
                        shift
                        untracked=${1?"BUG: create_stash () -u requires an argument"}
@@ -193,6 +199,12 @@ store_stash () {
                        shift
                        stash_msg="$1"
                        ;;
+               -m*)
+                       stash_msg=${1#-m}
+                       ;;
+               --message=*)
+                       stash_msg=${1#--message=}
+                       ;;
                -q|--quiet)
                        quiet=t
                        ;;
@@ -251,6 +263,12 @@ push_stash () {
                        test -z ${1+x} && usage
                        stash_msg=$1
                        ;;
+               -m*)
+                       stash_msg=${1#-m}
+                       ;;
+               --message=*)
+                       stash_msg=${1#--message=}
+                       ;;
                --help)
                        show_help
                        ;;
diff --git a/git.c b/git.c
index 9e96dd409017c643e8a9bebbc41281728978b0b8..c870b9719c21b2db23ea7ab5d56fdeda483cb02e 100644 (file)
--- a/git.c
+++ b/git.c
@@ -372,7 +372,7 @@ static struct cmd_struct commands[] = {
        { "archive", cmd_archive, RUN_SETUP_GENTLY },
        { "bisect--helper", cmd_bisect__helper, RUN_SETUP },
        { "blame", cmd_blame, RUN_SETUP },
-       { "branch", cmd_branch, RUN_SETUP },
+       { "branch", cmd_branch, RUN_SETUP | DELAY_PAGER_CONFIG },
        { "bundle", cmd_bundle, RUN_SETUP_GENTLY },
        { "cat-file", cmd_cat_file, RUN_SETUP },
        { "check-attr", cmd_check_attr, RUN_SETUP },
diff --git a/grep.c b/grep.c
index d0b9b6cdfa74537ed67dcfef4ae486466d7185f8..a69c05edc2e4fc6787076531b43a49c7ccdc1113 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -1476,31 +1476,52 @@ static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs,
        }
 }
 
+static int is_empty_line(const char *bol, const char *eol);
+
 static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
                             char *bol, char *end, unsigned lno)
 {
-       unsigned cur = lno, from = 1, funcname_lno = 0;
-       int funcname_needed = !!opt->funcname;
-
-       if (opt->funcbody && !match_funcname(opt, gs, bol, end))
-               funcname_needed = 2;
+       unsigned cur = lno, from = 1, funcname_lno = 0, orig_from;
+       int funcname_needed = !!opt->funcname, comment_needed = 0;
 
        if (opt->pre_context < lno)
                from = lno - opt->pre_context;
        if (from <= opt->last_shown)
                from = opt->last_shown + 1;
+       orig_from = from;
+       if (opt->funcbody) {
+               if (match_funcname(opt, gs, bol, end))
+                       comment_needed = 1;
+               else
+                       funcname_needed = 1;
+               from = opt->last_shown + 1;
+       }
 
        /* Rewind. */
-       while (bol > gs->buf &&
-              cur > (funcname_needed == 2 ? opt->last_shown + 1 : from)) {
+       while (bol > gs->buf && cur > from) {
+               char *next_bol = bol;
                char *eol = --bol;
 
                while (bol > gs->buf && bol[-1] != '\n')
                        bol--;
                cur--;
+               if (comment_needed && (is_empty_line(bol, eol) ||
+                                      match_funcname(opt, gs, bol, eol))) {
+                       comment_needed = 0;
+                       from = orig_from;
+                       if (cur < from) {
+                               cur++;
+                               bol = next_bol;
+                               break;
+                       }
+               }
                if (funcname_needed && match_funcname(opt, gs, bol, eol)) {
                        funcname_lno = cur;
                        funcname_needed = 0;
+                       if (opt->funcbody)
+                               comment_needed = 1;
+                       else
+                               from = orig_from;
                }
        }
 
diff --git a/hash.h b/hash.h
index 024d0d3d50b174aac1d948e9ecfa7ae92020355a..7d7a864f5dd43bb53ee6b31d391df3e15cbdbb08 100644 (file)
--- a/hash.h
+++ b/hash.h
@@ -1,6 +1,8 @@
 #ifndef HASH_H
 #define HASH_H
 
+#include "git-compat-util.h"
+
 #if defined(SHA1_PPC)
 #include "ppc/sha1.h"
 #elif defined(SHA1_APPLE)
 #include "block-sha1/sha1.h"
 #endif
 
+/*
+ * Note that these constants are suitable for indexing the hash_algos array and
+ * comparing against each other, but are otherwise arbitrary, so they should not
+ * be exposed to the user or serialized to disk.  To know whether a
+ * git_hash_algo struct points to some usable hash function, test the format_id
+ * field for being non-zero.  Use the name field for user-visible situations and
+ * the format_id field for fixed-length fields on disk.
+ */
+/* An unknown hash function. */
+#define GIT_HASH_UNKNOWN 0
+/* SHA-1 */
+#define GIT_HASH_SHA1 1
+/* Number of algorithms supported (including unknown). */
+#define GIT_HASH_NALGOS (GIT_HASH_SHA1 + 1)
+
+typedef void (*git_hash_init_fn)(void *ctx);
+typedef void (*git_hash_update_fn)(void *ctx, const void *in, size_t len);
+typedef void (*git_hash_final_fn)(unsigned char *hash, void *ctx);
+
+struct git_hash_algo {
+       /*
+        * The name of the algorithm, as appears in the config file and in
+        * messages.
+        */
+       const char *name;
+
+       /* A four-byte version identifier, used in pack indices. */
+       uint32_t format_id;
+
+       /* The size of a hash context (e.g. git_SHA_CTX). */
+       size_t ctxsz;
+
+       /* The length of the hash in binary. */
+       size_t rawsz;
+
+       /* The length of the hash in hex characters. */
+       size_t hexsz;
+
+       /* The hash initialization function. */
+       git_hash_init_fn init_fn;
+
+       /* The hash update function. */
+       git_hash_update_fn update_fn;
+
+       /* The hash finalization function. */
+       git_hash_final_fn final_fn;
+
+       /* The OID of the empty tree. */
+       const struct object_id *empty_tree;
+
+       /* The OID of the empty blob. */
+       const struct object_id *empty_blob;
+};
+extern const struct git_hash_algo hash_algos[GIT_HASH_NALGOS];
+
 #endif
diff --git a/http.c b/http.c
index 713525f38ed3f254aed2b0dd65fece7972444072..215bebef1bfb935355a765027fc8605fccbc3d1b 100644 (file)
--- a/http.c
+++ b/http.c
@@ -12,6 +12,7 @@
 #include "gettext.h"
 #include "transport.h"
 #include "packfile.h"
+#include "protocol.h"
 
 static struct trace_key trace_curl = TRACE_KEY_INIT(CURL);
 #if LIBCURL_VERSION_NUM >= 0x070a08
@@ -898,6 +899,21 @@ static void set_from_env(const char **var, const char *envname)
                *var = val;
 }
 
+static void protocol_http_header(void)
+{
+       if (get_protocol_version_config() > 0) {
+               struct strbuf protocol_header = STRBUF_INIT;
+
+               strbuf_addf(&protocol_header, GIT_PROTOCOL_HEADER ": version=%d",
+                           get_protocol_version_config());
+
+
+               extra_http_headers = curl_slist_append(extra_http_headers,
+                                                      protocol_header.buf);
+               strbuf_release(&protocol_header);
+       }
+}
+
 void http_init(struct remote *remote, const char *url, int proactive_auth)
 {
        char *low_speed_limit;
@@ -928,6 +944,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
        if (remote)
                var_override(&http_proxy_authmethod, remote->http_proxy_authmethod);
 
+       protocol_http_header();
+
        pragma_header = curl_slist_append(http_copy_default_headers(),
                "Pragma: no-cache");
        no_pragma_header = curl_slist_append(http_copy_default_headers(),
index b48b15a6fd88a89c03b259ebc3efed361c7f4d4a..a4c280dfc78e943ec541f83ccb0b39921ccdf2b2 100644 (file)
@@ -1901,8 +1901,9 @@ static int process_entry(struct merge_options *o,
                        oid = b_oid;
                        conf = _("directory/file");
                }
-               if (dir_in_way(path, !o->call_depth,
-                              S_ISGITLINK(a_mode))) {
+               if (dir_in_way(path,
+                              !o->call_depth && !S_ISGITLINK(a_mode),
+                              0)) {
                        char *new_path = unique_path(o, path, add_branch);
                        clean_merge = 0;
                        output(o, 1, _("CONFLICT (%s): There is a directory with name %s in %s. "
@@ -2081,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");
        }
 
@@ -2252,6 +2253,8 @@ int parse_merge_opt(struct merge_options *o, const char *s)
                DIFF_XDL_SET(o, IGNORE_WHITESPACE);
        else if (!strcmp(s, "ignore-space-at-eol"))
                DIFF_XDL_SET(o, IGNORE_WHITESPACE_AT_EOL);
+       else if (!strcmp(s, "ignore-cr-at-eol"))
+               DIFF_XDL_SET(o, IGNORE_CR_AT_EOL);
        else if (!strcmp(s, "renormalize"))
                o->renormalize = 1;
        else if (!strcmp(s, "no-renormalize"))
index 4a83b0ebd5ce8b24da0b32820f5666571b099ecf..0f6573cb17cbbbc2219453bdca424fd7ebc156dc 100644 (file)
@@ -595,7 +595,7 @@ int notes_merge(struct notes_merge_options *o,
        bases = get_merge_bases(local, remote);
        if (!bases) {
                base_oid = &null_oid;
-               base_tree_oid = &empty_tree_oid;
+               base_tree_oid = the_hash_algo->empty_tree;
                if (o->verbosity >= 4)
                        printf("No merge base found; doing history-less merge\n");
        } else if (!bases->next) {
index 255b28c0988376ab7065078f411d5a53752bbf42..b75738bed4b52ac0085eb6a4919ba954d8fcfe98 100644 (file)
@@ -17,7 +17,7 @@ BEGIN
                        packet_compare_lists
                        packet_bin_read
                        packet_txt_read
-                       packet_required_key_val_read
+                       packet_key_val_read
                        packet_bin_write
                        packet_txt_write
                        packet_flush
@@ -68,28 +68,33 @@ sub packet_bin_read {
 
 sub remove_final_lf_or_die {
        my $buf = shift;
-       unless ( $buf =~ s/\n$// ) {
-               die "A non-binary line MUST be terminated by an LF.\n"
-                   . "Received: '$buf'";
+       if ( $buf =~ s/\n$// ) {
+               return $buf;
        }
-       return $buf;
+       die "A non-binary line MUST be terminated by an LF.\n"
+           . "Received: '$buf'";
 }
 
 sub packet_txt_read {
        my ( $res, $buf ) = packet_bin_read();
-       unless ( $res == -1 or $buf eq '' ) {
+       if ( $res != -1 and $buf ne '' ) {
                $buf = remove_final_lf_or_die($buf);
        }
        return ( $res, $buf );
 }
 
-sub packet_required_key_val_read {
+# Read a text packet, expecting that it is in the form "key=value" for
+# the given $key.  An EOF does not trigger any error and is reported
+# back to the caller (like packet_txt_read() does).  Die if the "key"
+# part of "key=value" does not match the given $key, or the value part
+# is empty.
+sub packet_key_val_read {
        my ( $key ) = @_;
        my ( $res, $buf ) = packet_txt_read();
-       unless ( $res == -1 or ( $buf =~ s/^$key=// and $buf ne '' ) ) {
-               die "bad $key: '$buf'";
+       if ( $res == -1 or ( $buf =~ s/^$key=// and $buf ne '' ) ) {
+               return ( $res, $buf );
        }
-       return ( $res, $buf );
+       die "bad $key: '$buf'";
 }
 
 sub packet_bin_write {
index 93ea311443a37b81b85a72dc569c715417d7ef29..2827ca772a3703f71bc588d0f6cacd5caa318fe7 100644 (file)
@@ -188,6 +188,12 @@ static int packet_write_gently(const int fd_out, const char *buf, size_t size)
        return 0;
 }
 
+void packet_write(int fd_out, const char *buf, size_t size)
+{
+       if (packet_write_gently(fd_out, buf, size))
+               die_errno("packet write failed");
+}
+
 void packet_buf_write(struct strbuf *buf, const char *fmt, ...)
 {
        va_list args;
index 66ef610fc4f7de2f6b7572f1e5fb36438c5dec19..3dad583e2d02264c4a831c939ae0e13a54de2ff6 100644 (file)
@@ -22,6 +22,7 @@
 void packet_flush(int fd);
 void packet_write_fmt(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
 void packet_buf_flush(struct strbuf *buf);
+void packet_write(int fd_out, const char *buf, size_t size);
 void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
 int packet_flush_gently(int fd);
 int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
diff --git a/protocol.c b/protocol.c
new file mode 100644 (file)
index 0000000..43012b7
--- /dev/null
@@ -0,0 +1,79 @@
+#include "cache.h"
+#include "config.h"
+#include "protocol.h"
+
+static enum protocol_version parse_protocol_version(const char *value)
+{
+       if (!strcmp(value, "0"))
+               return protocol_v0;
+       else if (!strcmp(value, "1"))
+               return protocol_v1;
+       else
+               return protocol_unknown_version;
+}
+
+enum protocol_version get_protocol_version_config(void)
+{
+       const char *value;
+       if (!git_config_get_string_const("protocol.version", &value)) {
+               enum protocol_version version = parse_protocol_version(value);
+
+               if (version == protocol_unknown_version)
+                       die("unknown value for config 'protocol.version': %s",
+                           value);
+
+               return version;
+       }
+
+       return protocol_v0;
+}
+
+enum protocol_version determine_protocol_version_server(void)
+{
+       const char *git_protocol = getenv(GIT_PROTOCOL_ENVIRONMENT);
+       enum protocol_version version = protocol_v0;
+
+       /*
+        * Determine which protocol version the client has requested.  Since
+        * multiple 'version' keys can be sent by the client, indicating that
+        * the client is okay to speak any of them, select the greatest version
+        * that the client has requested.  This is due to the assumption that
+        * the most recent protocol version will be the most state-of-the-art.
+        */
+       if (git_protocol) {
+               struct string_list list = STRING_LIST_INIT_DUP;
+               const struct string_list_item *item;
+               string_list_split(&list, git_protocol, ':', -1);
+
+               for_each_string_list_item(item, &list) {
+                       const char *value;
+                       enum protocol_version v;
+
+                       if (skip_prefix(item->string, "version=", &value)) {
+                               v = parse_protocol_version(value);
+                               if (v > version)
+                                       version = v;
+                       }
+               }
+
+               string_list_clear(&list, 0);
+       }
+
+       return version;
+}
+
+enum protocol_version determine_protocol_version_client(const char *server_response)
+{
+       enum protocol_version version = protocol_v0;
+
+       if (skip_prefix(server_response, "version ", &server_response)) {
+               version = parse_protocol_version(server_response);
+
+               if (version == protocol_unknown_version)
+                       die("server is speaking an unknown protocol");
+               if (version == protocol_v0)
+                       die("protocol error: server explicitly said version 0");
+       }
+
+       return version;
+}
diff --git a/protocol.h b/protocol.h
new file mode 100644 (file)
index 0000000..1b2bc94
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef PROTOCOL_H
+#define PROTOCOL_H
+
+enum protocol_version {
+       protocol_unknown_version = -1,
+       protocol_v0 = 0,
+       protocol_v1 = 1,
+};
+
+/*
+ * Used by a client to determine which protocol version to request be used when
+ * communicating with a server, reflecting the configured value of the
+ * 'protocol.version' config.  If unconfigured, a value of 'protocol_v0' is
+ * returned.
+ */
+extern enum protocol_version get_protocol_version_config(void);
+
+/*
+ * Used by a server to determine which protocol version should be used based on
+ * a client's request, communicated via the 'GIT_PROTOCOL' environment variable
+ * by setting appropriate values for the key 'version'.  If a client doesn't
+ * request a particular protocol version, a default of 'protocol_v0' will be
+ * used.
+ */
+extern enum protocol_version determine_protocol_version_server(void);
+
+/*
+ * Used by a client to determine which protocol version the server is speaking
+ * based on the server's initial response.
+ */
+extern enum protocol_version determine_protocol_version_client(const char *server_response);
+
+#endif /* PROTOCOL_H */
index fbb4967c81976825e7d2c52da91391c6574c4b71..2eb81a66b941325dc339d61a0ab804a60cbb8be2 100644 (file)
@@ -641,13 +641,17 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
 {
        int size, namelen, was_same;
        mode_t st_mode = st->st_mode;
-       struct cache_entry *ce, *alias;
+       struct cache_entry *ce, *alias = NULL;
        unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE|CE_MATCH_RACY_IS_DIRTY;
        int verbose = flags & (ADD_CACHE_VERBOSE | ADD_CACHE_PRETEND);
        int pretend = flags & ADD_CACHE_PRETEND;
        int intent_only = flags & ADD_CACHE_INTENT;
        int add_option = (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE|
                          (intent_only ? ADD_CACHE_NEW_ONLY : 0));
+       int newflags = HASH_WRITE_OBJECT;
+
+       if (flags & HASH_RENORMALIZE)
+               newflags |= HASH_RENORMALIZE;
 
        if (!S_ISREG(st_mode) && !S_ISLNK(st_mode) && !S_ISDIR(st_mode))
                return error("%s: can only add regular files, symbolic links or git-directories", path);
@@ -688,19 +692,23 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
        if (ignore_case) {
                adjust_dirname_case(istate, ce->name);
        }
+       if (!(flags & HASH_RENORMALIZE)) {
+               alias = index_file_exists(istate, ce->name,
+                                         ce_namelen(ce), ignore_case);
+               if (alias &&
+                   !ce_stage(alias) &&
+                   !ie_match_stat(istate, alias, st, ce_option)) {
+                       /* Nothing changed, really */
+                       if (!S_ISGITLINK(alias->ce_mode))
+                               ce_mark_uptodate(alias);
+                       alias->ce_flags |= CE_ADDED;
 
-       alias = index_file_exists(istate, ce->name, ce_namelen(ce), ignore_case);
-       if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, ce_option)) {
-               /* Nothing changed, really */
-               if (!S_ISGITLINK(alias->ce_mode))
-                       ce_mark_uptodate(alias);
-               alias->ce_flags |= CE_ADDED;
-
-               free(ce);
-               return 0;
+                       free(ce);
+                       return 0;
+               }
        }
        if (!intent_only) {
-               if (index_path(&ce->oid, path, st, HASH_WRITE_OBJECT)) {
+               if (index_path(&ce->oid, path, st, newflags)) {
                        free(ce);
                        return error("unable to index file %s", path);
                }
index bb2fae5446b7ea8d2bfac87faa30c165f714d121..998413b8bb8deaddef76d93f67be4a1435c9e3a7 100644 (file)
@@ -5,7 +5,7 @@
 
 /* The main repository */
 static struct repository the_repo = {
-       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &the_index, 0, 0
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &the_index, NULL, 0, 0
 };
 struct repository *the_repository = &the_repo;
 
@@ -64,6 +64,11 @@ void repo_set_gitdir(struct repository *repo, const char *path)
        free(old_gitdir);
 }
 
+void repo_set_hash_algo(struct repository *repo, int hash_algo)
+{
+       repo->hash_algo = &hash_algos[hash_algo];
+}
+
 /*
  * Attempt to resolve and set the provided 'gitdir' for repository 'repo'.
  * Return 0 upon success and a non-zero value upon failure.
@@ -136,6 +141,8 @@ int repo_init(struct repository *repo, const char *gitdir, const char *worktree)
        if (read_and_verify_repository_format(&format, repo->commondir))
                goto error;
 
+       repo_set_hash_algo(repo, format.hash_algo);
+
        if (worktree)
                repo_set_worktree(repo, worktree);
 
index 7f5e24a0a24011393e9b1b9986895ebe8b3f10ab..0329e40c7f5e72dad3ba46328a8e3d6c29ed8e58 100644 (file)
@@ -4,6 +4,7 @@
 struct config_set;
 struct index_state;
 struct submodule_cache;
+struct git_hash_algo;
 
 struct repository {
        /* Environment */
@@ -67,6 +68,9 @@ struct repository {
         */
        struct index_state *index;
 
+       /* Repository's current hash algorithm, as serialized on disk. */
+       const struct git_hash_algo *hash_algo;
+
        /* Configurations */
        /*
         * Bit used during initialization to indicate if repository state (like
@@ -86,6 +90,7 @@ extern struct repository *the_repository;
 
 extern void repo_set_gitdir(struct repository *repo, const char *path);
 extern void repo_set_worktree(struct repository *repo, const char *path);
+extern void repo_set_hash_algo(struct repository *repo, int algo);
 extern int repo_init(struct repository *repo, const char *gitdir, const char *worktree);
 extern int repo_submodule_init(struct repository *submodule,
                               struct repository *superproject,
index 19dd575ed9b3b280a3fdabc9121a2e193d6984db..e90bc316bb7a232a40c332c6d1add255d3893869 100644 (file)
@@ -347,7 +347,7 @@ static int read_oneliner(struct strbuf *buf,
 
 static struct tree *empty_tree(void)
 {
-       return lookup_tree(&empty_tree_oid);
+       return lookup_tree(the_hash_algo->empty_tree);
 }
 
 static int error_dirty_index(struct replay_opts *opts)
@@ -438,7 +438,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        char **xopt;
        static struct lock_file index_lock;
 
-       hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
+       if (hold_locked_index(&index_lock, LOCK_REPORT_ON_ERROR) < 0)
+               return -1;
 
        read_cache();
 
@@ -705,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);
@@ -958,7 +959,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
        } else {
                unborn = get_oid("HEAD", &head);
                if (unborn)
-                       oidcpy(&head, &empty_tree_oid);
+                       oidcpy(&head, the_hash_algo->empty_tree);
                if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD",
                                       NULL, 0))
                        return error_dirty_index(opts);
diff --git a/setup.c b/setup.c
index 94768512b7913c4e46b90fc8226c183cbd7239d7..50c6b2ab11fa3579a17079c16a4fb7241df9b875 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -434,16 +434,15 @@ static int check_repo_format(const char *var, const char *value, void *vdata)
        return 0;
 }
 
-static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
+static int check_repository_format_gently(const char *gitdir, struct repository_format *candidate, int *nongit_ok)
 {
        struct strbuf sb = STRBUF_INIT;
        struct strbuf err = STRBUF_INIT;
-       struct repository_format candidate;
        int has_common;
 
        has_common = get_common_dir(&sb, gitdir);
        strbuf_addstr(&sb, "/config");
-       read_repository_format(&candidate, sb.buf);
+       read_repository_format(candidate, sb.buf);
        strbuf_release(&sb);
 
        /*
@@ -451,10 +450,10 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
         * we treat a missing config as a silent "ok", even when nongit_ok
         * is unset.
         */
-       if (candidate.version < 0)
+       if (candidate->version < 0)
                return 0;
 
-       if (verify_repository_format(&candidate, &err) < 0) {
+       if (verify_repository_format(candidate, &err) < 0) {
                if (nongit_ok) {
                        warning("%s", err.buf);
                        strbuf_release(&err);
@@ -464,21 +463,21 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
                die("%s", err.buf);
        }
 
-       repository_format_precious_objects = candidate.precious_objects;
-       string_list_clear(&candidate.unknown_extensions, 0);
+       repository_format_precious_objects = candidate->precious_objects;
+       string_list_clear(&candidate->unknown_extensions, 0);
        if (!has_common) {
-               if (candidate.is_bare != -1) {
-                       is_bare_repository_cfg = candidate.is_bare;
+               if (candidate->is_bare != -1) {
+                       is_bare_repository_cfg = candidate->is_bare;
                        if (is_bare_repository_cfg == 1)
                                inside_work_tree = -1;
                }
-               if (candidate.work_tree) {
+               if (candidate->work_tree) {
                        free(git_work_tree_cfg);
-                       git_work_tree_cfg = candidate.work_tree;
+                       git_work_tree_cfg = candidate->work_tree;
                        inside_work_tree = -1;
                }
        } else {
-               free(candidate.work_tree);
+               free(candidate->work_tree);
        }
 
        return 0;
@@ -489,6 +488,7 @@ int read_repository_format(struct repository_format *format, const char *path)
        memset(format, 0, sizeof(*format));
        format->version = -1;
        format->is_bare = -1;
+       format->hash_algo = GIT_HASH_SHA1;
        string_list_init(&format->unknown_extensions, 1);
        git_config_from_file(check_repo_format, path, format);
        return format->version;
@@ -625,6 +625,7 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)
 
 static const char *setup_explicit_git_dir(const char *gitdirenv,
                                          struct strbuf *cwd,
+                                         struct repository_format *repo_fmt,
                                          int *nongit_ok)
 {
        const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
@@ -650,7 +651,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
                die("Not a git repository: '%s'", gitdirenv);
        }
 
-       if (check_repository_format_gently(gitdirenv, nongit_ok)) {
+       if (check_repository_format_gently(gitdirenv, repo_fmt, nongit_ok)) {
                free(gitfile);
                return NULL;
        }
@@ -723,9 +724,10 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
 
 static const char *setup_discovered_git_dir(const char *gitdir,
                                            struct strbuf *cwd, int offset,
+                                           struct repository_format *repo_fmt,
                                            int *nongit_ok)
 {
-       if (check_repository_format_gently(gitdir, nongit_ok))
+       if (check_repository_format_gently(gitdir, repo_fmt, nongit_ok))
                return NULL;
 
        /* --work-tree is set without --git-dir; use discovered one */
@@ -737,7 +739,7 @@ static const char *setup_discovered_git_dir(const char *gitdir,
                        gitdir = to_free = real_pathdup(gitdir, 1);
                if (chdir(cwd->buf))
                        die_errno("Could not come back to cwd");
-               ret = setup_explicit_git_dir(gitdir, cwd, nongit_ok);
+               ret = setup_explicit_git_dir(gitdir, cwd, repo_fmt, nongit_ok);
                free(to_free);
                return ret;
        }
@@ -769,11 +771,12 @@ static const char *setup_discovered_git_dir(const char *gitdir,
 
 /* #16.1, #17.1, #20.1, #21.1, #22.1 (see t1510) */
 static const char *setup_bare_git_dir(struct strbuf *cwd, int offset,
+                                     struct repository_format *repo_fmt,
                                      int *nongit_ok)
 {
        int root_len;
 
-       if (check_repository_format_gently(".", nongit_ok))
+       if (check_repository_format_gently(".", repo_fmt, nongit_ok))
                return NULL;
 
        setenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, "0", 1);
@@ -785,7 +788,7 @@ static const char *setup_bare_git_dir(struct strbuf *cwd, int offset,
                gitdir = offset == cwd->len ? "." : xmemdupz(cwd->buf, offset);
                if (chdir(cwd->buf))
                        die_errno("Could not come back to cwd");
-               return setup_explicit_git_dir(gitdir, cwd, nongit_ok);
+               return setup_explicit_git_dir(gitdir, cwd, repo_fmt, nongit_ok);
        }
 
        inside_git_dir = 1;
@@ -1026,6 +1029,7 @@ const char *setup_git_directory_gently(int *nongit_ok)
        static struct strbuf cwd = STRBUF_INIT;
        struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT;
        const char *prefix;
+       struct repository_format repo_fmt;
 
        /*
         * We may have read an incomplete configuration before
@@ -1053,18 +1057,18 @@ const char *setup_git_directory_gently(int *nongit_ok)
                prefix = NULL;
                break;
        case GIT_DIR_EXPLICIT:
-               prefix = setup_explicit_git_dir(gitdir.buf, &cwd, nongit_ok);
+               prefix = setup_explicit_git_dir(gitdir.buf, &cwd, &repo_fmt, nongit_ok);
                break;
        case GIT_DIR_DISCOVERED:
                if (dir.len < cwd.len && chdir(dir.buf))
                        die(_("Cannot change to '%s'"), dir.buf);
                prefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len,
-                                                 nongit_ok);
+                                                 &repo_fmt, nongit_ok);
                break;
        case GIT_DIR_BARE:
                if (dir.len < cwd.len && chdir(dir.buf))
                        die(_("Cannot change to '%s'"), dir.buf);
-               prefix = setup_bare_git_dir(&cwd, dir.len, nongit_ok);
+               prefix = setup_bare_git_dir(&cwd, dir.len, &repo_fmt, nongit_ok);
                break;
        case GIT_DIR_HIT_CEILING:
                prefix = setup_nongit(cwd.buf, nongit_ok);
@@ -1110,6 +1114,8 @@ const char *setup_git_directory_gently(int *nongit_ok)
                        repo_set_gitdir(the_repository, gitdir);
                        setup_git_env();
                }
+               if (startup_info->have_repository)
+                       repo_set_hash_algo(the_repository, repo_fmt.hash_algo);
        }
 
        strbuf_release(&dir);
@@ -1171,7 +1177,8 @@ int git_config_perm(const char *var, const char *value)
 
 void check_repository_format(void)
 {
-       check_repository_format_gently(get_git_dir(), NULL);
+       struct repository_format repo_fmt;
+       check_repository_format_gently(get_git_dir(), &repo_fmt, NULL);
        startup_info->have_repository = 1;
 }
 
index 8a7c6b7eba8ab2f829c4504b9db4578c0daac25a..b44f5247caa9389f8d1c7643b7d9d99418d21f72 100644 (file)
@@ -39,6 +39,64 @@ const struct object_id empty_blob_oid = {
        EMPTY_BLOB_SHA1_BIN_LITERAL
 };
 
+static void git_hash_sha1_init(void *ctx)
+{
+       git_SHA1_Init((git_SHA_CTX *)ctx);
+}
+
+static void git_hash_sha1_update(void *ctx, const void *data, size_t len)
+{
+       git_SHA1_Update((git_SHA_CTX *)ctx, data, len);
+}
+
+static void git_hash_sha1_final(unsigned char *hash, void *ctx)
+{
+       git_SHA1_Final(hash, (git_SHA_CTX *)ctx);
+}
+
+static void git_hash_unknown_init(void *ctx)
+{
+       die("trying to init unknown hash");
+}
+
+static void git_hash_unknown_update(void *ctx, const void *data, size_t len)
+{
+       die("trying to update unknown hash");
+}
+
+static void git_hash_unknown_final(unsigned char *hash, void *ctx)
+{
+       die("trying to finalize unknown hash");
+}
+
+const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
+       {
+               NULL,
+               0x00000000,
+               0,
+               0,
+               0,
+               git_hash_unknown_init,
+               git_hash_unknown_update,
+               git_hash_unknown_final,
+               NULL,
+               NULL,
+       },
+       {
+               "sha-1",
+               /* "sha1", big-endian */
+               0x73686131,
+               sizeof(git_SHA_CTX),
+               GIT_SHA1_RAWSZ,
+               GIT_SHA1_HEXSZ,
+               git_hash_sha1_init,
+               git_hash_sha1_update,
+               git_hash_sha1_final,
+               &empty_tree_oid,
+               &empty_blob_oid,
+       },
+};
+
 /*
  * This is meant to hold a *small* number of objects that you would
  * want read_sha1_file() to be able to return, but yet you do not want
@@ -74,6 +132,18 @@ static struct cached_object *find_cached_object(const unsigned char *sha1)
        return NULL;
 }
 
+
+static enum safe_crlf get_safe_crlf(unsigned flags)
+{
+       if (flags & HASH_RENORMALIZE)
+               return SAFE_CRLF_RENORMALIZE;
+       else if (flags & HASH_WRITE_OBJECT)
+               return safe_crlf;
+       else
+               return SAFE_CRLF_FALSE;
+}
+
+
 int mkdir_in_gitdir(const char *path)
 {
        if (mkdir(path, 0777)) {
@@ -1152,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;
 
@@ -1679,7 +1752,7 @@ static int index_mem(struct object_id *oid, void *buf, size_t size,
        if ((type == OBJ_BLOB) && path) {
                struct strbuf nbuf = STRBUF_INIT;
                if (convert_to_git(&the_index, path, buf, size, &nbuf,
-                                  write_object ? safe_crlf : SAFE_CRLF_FALSE)) {
+                                  get_safe_crlf(flags))) {
                        buf = strbuf_detach(&nbuf, &size);
                        re_allocated = 1;
                }
@@ -1713,7 +1786,7 @@ static int index_stream_convert_blob(struct object_id *oid, int fd,
        assert(would_convert_to_git_filter_fd(path));
 
        convert_to_git_filter_fd(&the_index, path, fd, &sbuf,
-                                write_object ? safe_crlf : SAFE_CRLF_FALSE);
+                                get_safe_crlf(flags));
 
        if (write_object)
                ret = write_sha1_file(sbuf.buf, sbuf.len, typename(OBJ_BLOB),
index 9a2d5caf3b785ec629fcbc2325cd63e0ef72cf57..611c7d24ddee678470ba74cb3c2ca669a778b44a 100644 (file)
@@ -1438,9 +1438,19 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
                strbuf_branchname(sb, name, INTERPRET_BRANCH_LOCAL);
        else
                strbuf_addstr(sb, name);
-       if (name[0] == '-')
-               return -1;
+
+       /*
+        * This splice must be done even if we end up rejecting the
+        * name; builtin/branch.c::copy_or_rename_branch() still wants
+        * to see what the name expanded to so that "branch -m" can be
+        * used as a tool to correct earlier mistakes.
+        */
        strbuf_splice(sb, 0, 0, "refs/heads/", 11);
+
+       if (*name == '-' ||
+           !strcmp(sb->buf, "refs/heads/HEAD"))
+               return -1;
+
        return check_refname_format(sb->buf, 0);
 }
 
index bb531e0e59ce7f8fa5cde622101b7ef1b324a603..fa25888783aa9ba7ce5b290ba64ec3c1b7a87cec 100644 (file)
@@ -587,7 +587,7 @@ void show_submodule_inline_diff(struct diff_options *o, const char *path,
                struct object_id *one, struct object_id *two,
                unsigned dirty_submodule)
 {
-       const struct object_id *old = &empty_tree_oid, *new = &empty_tree_oid;
+       const struct object_id *old = the_hash_algo->empty_tree, *new = the_hash_algo->empty_tree;
        struct commit *left = NULL, *right = NULL;
        struct commit_list *merge_bases = NULL;
        struct child_process cp = CHILD_PROCESS_INIT;
@@ -1670,7 +1670,8 @@ int submodule_move_head(const char *path,
                        cp.dir = path;
 
                        prepare_submodule_repo_env(&cp.env_array);
-                       argv_array_pushl(&cp.args, "update-ref", "HEAD", new, NULL);
+                       argv_array_pushl(&cp.args, "update-ref", "HEAD",
+                                        "--no-deref", new, NULL);
 
                        if (run_command(&cp)) {
                                ret = -1;
index f414a3ac670fb2d77645f5d7301b6aa8f603379e..ac8368797073adfdf203f60d93df55a988443dbd 100644 (file)
@@ -5,6 +5,7 @@ static const char *usage_msg = "\n"
 "  test-date show:<format> [time_t]...\n"
 "  test-date parse [date]...\n"
 "  test-date approxidate [date]...\n"
+"  test-date timestamp [date]...\n"
 "  test-date is64bit\n"
 "  test-date time_t-is64bit\n";
 
@@ -71,6 +72,15 @@ static void parse_approxidate(const char **argv, struct timeval *now)
        }
 }
 
+static void parse_approx_timestamp(const char **argv, struct timeval *now)
+{
+       for (; *argv; argv++) {
+               timestamp_t t;
+               t = approxidate_relative(*argv, now);
+               printf("%s -> %"PRItime"\n", *argv, t);
+       }
+}
+
 int cmd_main(int argc, const char **argv)
 {
        struct timeval now;
@@ -95,6 +105,8 @@ int cmd_main(int argc, const char **argv)
                parse_dates(argv+1, &now);
        else if (!strcmp(*argv, "approxidate"))
                parse_approxidate(argv+1, &now);
+       else if (!strcmp(*argv, "timestamp"))
+               parse_approx_timestamp(argv+1, &now);
        else if (!strcmp(*argv, "is64bit"))
                return sizeof(timestamp_t) == 8 ? 0 : 1;
        else if (!strcmp(*argv, "time_t-is64bit"))
diff --git a/t/interop/i5700-protocol-transition.sh b/t/interop/i5700-protocol-transition.sh
new file mode 100755 (executable)
index 0000000..97e8e58
--- /dev/null
@@ -0,0 +1,68 @@
+#!/bin/sh
+
+VERSION_A=.
+VERSION_B=v2.0.0
+
+: ${LIB_GIT_DAEMON_PORT:=5700}
+LIB_GIT_DAEMON_COMMAND='git.b daemon'
+
+test_description='clone and fetch by client who is trying to use a new protocol'
+. ./interop-lib.sh
+. "$TEST_DIRECTORY"/lib-git-daemon.sh
+
+start_git_daemon --export-all
+
+repo=$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo
+
+test_expect_success "create repo served by $VERSION_B" '
+       git.b init "$repo" &&
+       git.b -C "$repo" commit --allow-empty -m one
+'
+
+test_expect_success "git:// clone with $VERSION_A and protocol v1" '
+       GIT_TRACE_PACKET=1 git.a -c protocol.version=1 clone "$GIT_DAEMON_URL/repo" child 2>log &&
+       git.a -C child log -1 --format=%s >actual &&
+       git.b -C "$repo" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+       grep "version=1" log
+'
+
+test_expect_success "git:// fetch with $VERSION_A and protocol v1" '
+       git.b -C "$repo" commit --allow-empty -m two &&
+       git.b -C "$repo" log -1 --format=%s >expect &&
+
+       GIT_TRACE_PACKET=1 git.a -C child -c protocol.version=1 fetch 2>log &&
+       git.a -C child log -1 --format=%s FETCH_HEAD >actual &&
+
+       test_cmp expect actual &&
+       grep "version=1" log &&
+       ! grep "version 1" log
+'
+
+stop_git_daemon
+
+test_expect_success "create repo served by $VERSION_B" '
+       git.b init parent &&
+       git.b -C parent commit --allow-empty -m one
+'
+
+test_expect_success "file:// clone with $VERSION_A and protocol v1" '
+       GIT_TRACE_PACKET=1 git.a -c protocol.version=1 clone --upload-pack="git.b upload-pack" parent child2 2>log &&
+       git.a -C child2 log -1 --format=%s >actual &&
+       git.b -C parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+       ! grep "version 1" log
+'
+
+test_expect_success "file:// fetch with $VERSION_A and protocol v1" '
+       git.b -C parent commit --allow-empty -m two &&
+       git.b -C parent log -1 --format=%s >expect &&
+
+       GIT_TRACE_PACKET=1 git.a -C child2 -c protocol.version=1 fetch --upload-pack="git.b upload-pack" 2>log &&
+       git.a -C child2 log -1 --format=%s FETCH_HEAD >actual &&
+
+       test_cmp expect actual &&
+       ! grep "version 1" log
+'
+
+test_done
index 43679a4c640e874aed8d3a378a378b1a6080fc1c..a5d3b2cbaad4edd078b8d58160dc4252ce8adec9 100755 (executable)
@@ -31,7 +31,7 @@ then
                chmod 0700 ./gpghome &&
                GNUPGHOME="$(pwd)/gpghome" &&
                export GNUPGHOME &&
-               (gpgconf --kill gpg-agent 2>&1 >/dev/null || : ) &&
+               (gpgconf --kill gpg-agent >/dev/null 2>&1 || : ) &&
                gpg --homedir "${GNUPGHOME}" 2>/dev/null --import \
                        "$TEST_DIRECTORY"/lib-gpg/keyring.gpg &&
                gpg --homedir "${GNUPGHOME}" 2>/dev/null --import-ownertrust \
index 0642ae7e6ef0fbbd9e7eacdebd061d258c8b4222..df19436314b7c6d16833044c5c53ba90eae62225 100644 (file)
@@ -67,6 +67,9 @@ LockFile accept.lock
 <IfModule !mod_unixd.c>
        LoadModule unixd_module modules/mod_unixd.so
 </IfModule>
+<IfModule !mod_setenvif.c>
+       LoadModule setenvif_module modules/mod_setenvif.so
+</IfModule>
 </IfVersion>
 
 PassEnv GIT_VALGRIND
@@ -76,6 +79,10 @@ PassEnv ASAN_OPTIONS
 PassEnv GIT_TRACE
 PassEnv GIT_CONFIG_NOSYSTEM
 
+<IfVersion >= 2.4>
+       SetEnvIf Git-Protocol ".*" GIT_PROTOCOL=$0
+</IfVersion>
+
 Alias /dumb/ www/
 Alias /auth/dumb/ www/auth/dumb/
 
index bb94c2320958eb1d1a319899697ecefc881e3043..38dadd2c2902f3621d54168d32a54d7863497968 100755 (executable)
@@ -554,6 +554,10 @@ test_submodule_switch_common() {
 #  - if succeeds, once "git submodule update" is invoked, the contents of
 #    submodule directories are updated
 #
+# If the command under test is known to not work with submodules in certain
+# conditions, set the appropriate KNOWN_FAILURE_* variable used in the tests
+# below to 1.
+#
 # Use as follows:
 #
 # my_func () {
@@ -622,19 +626,11 @@ test_submodule_forced_switch () {
 # - Removing a submodule with a git directory absorbs the submodules
 #   git directory first into the superproject.
 
-test_submodule_switch_recursing_with_args () {
-       cmd_args="$1"
-       command="git $cmd_args --recurse-submodules"
-       RESULTDS=success
-       if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1
-       then
-               RESULTDS=failure
-       fi
-       RESULTOI=success
-       if test "$KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED" = 1
-       then
-               RESULTOI=failure
-       fi
+# Internal function; use test_submodule_switch_recursing_with_args() or
+# test_submodule_forced_switch_recursing_with_args() instead.
+test_submodule_recursing_with_args_common() {
+       command="$1"
+
        ######################### Appearing submodule #########################
        # Switching to a commit letting a submodule appear checks it out ...
        test_expect_success "$command: added submodule is checked out" '
@@ -648,7 +644,7 @@ test_submodule_switch_recursing_with_args () {
                        test_submodule_content sub1 origin/add_sub1
                )
        '
-       # ... ignoring an empty existing directory ...
+       # ... ignoring an empty existing directory.
        test_expect_success "$command: added submodule is checked out in empty dir" '
                prolog &&
                reset_work_tree_to_interested no_submodule &&
@@ -661,34 +657,23 @@ test_submodule_switch_recursing_with_args () {
                        test_submodule_content sub1 origin/add_sub1
                )
        '
-       # ... unless there is an untracked file in its place.
-       test_expect_success "$command: added submodule doesn't remove untracked file with same name" '
-               prolog &&
-               reset_work_tree_to_interested no_submodule &&
-               (
-                       cd submodule_update &&
-                       git branch -t add_sub1 origin/add_sub1 &&
-                       : >sub1 &&
-                       test_must_fail $command add_sub1 &&
-                       test_superproject_content origin/no_submodule &&
-                       test_must_be_empty sub1
-               )
-       '
-       # ... but an ignored file is fine.
-       test_expect_$RESULTOI "$command: added submodule removes an untracked ignored file" '
-               test_when_finished "rm submodule_update/.git/info/exclude" &&
+       test_expect_success "$command: submodule branch is not changed, detach HEAD instead" '
                prolog &&
-               reset_work_tree_to_interested no_submodule &&
+               reset_work_tree_to_interested add_sub1 &&
                (
                        cd submodule_update &&
-                       git branch -t add_sub1 origin/add_sub1 &&
-                       : >sub1 &&
-                       echo sub1 >.git/info/exclude
-                       $command add_sub1 &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
+                       git -C sub1 checkout -b keep_branch &&
+                       git -C sub1 rev-parse HEAD >expect &&
+                       git branch -t check-keep origin/modify_sub1 &&
+                       $command check-keep &&
+                       test_superproject_content origin/modify_sub1 &&
+                       test_submodule_content sub1 origin/modify_sub1 &&
+                       git -C sub1 rev-parse keep_branch >actual &&
+                       test_cmp expect actual &&
+                       test_must_fail git -C sub1 symbolic-ref HEAD
                )
        '
+
        # Replacing a tracked file with a submodule produces a checked out submodule
        test_expect_success "$command: replace tracked file with submodule checks out submodule" '
                prolog &&
@@ -742,33 +727,6 @@ test_submodule_switch_recursing_with_args () {
                        test_git_directory_exists sub1
                )
        '
-       # Replacing a submodule with files in a directory must succeeds
-       # when the submodule is clean
-       test_expect_$RESULTDS "$command: replace submodule with a directory" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
-                       $command replace_sub1_with_directory &&
-                       test_superproject_content origin/replace_sub1_with_directory &&
-                       test_submodule_content sub1 origin/replace_sub1_with_directory
-               )
-       '
-       # ... absorbing a .git directory.
-       test_expect_$RESULTDS "$command: replace submodule containing a .git directory with a directory must absorb the git dir" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
-                       replace_gitfile_with_git_dir sub1 &&
-                       rm -rf .git/modules &&
-                       $command replace_sub1_with_directory &&
-                       test_superproject_content origin/replace_sub1_with_directory &&
-                       test_git_directory_exists sub1
-               )
-       '
 
        # Replacing it with a file ...
        test_expect_success "$command: replace submodule with a file" '
@@ -782,7 +740,11 @@ test_submodule_switch_recursing_with_args () {
                        test -f sub1
                )
        '
-
+       RESULTDS=success
+       if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1
+       then
+               RESULTDS=failure
+       fi
        # ... must check its local work tree for untracked files
        test_expect_$RESULTDS "$command: replace submodule with a file must fail with untracked files" '
                prolog &&
@@ -794,21 +756,7 @@ test_submodule_switch_recursing_with_args () {
                        test_must_fail $command replace_sub1_with_file &&
                        test_superproject_content origin/add_sub1 &&
                        test_submodule_content sub1 origin/add_sub1
-               )
-       '
-
-       # ... and ignored files are ignored
-       test_expect_success "$command: replace submodule with a file works ignores ignored files in submodule" '
-               test_when_finished "rm submodule_update/.git/modules/sub1/info/exclude" &&
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
-                       : >sub1/ignored &&
-                       $command replace_sub1_with_file &&
-                       test_superproject_content origin/replace_sub1_with_file &&
-                       test -f sub1
+                       test -f sub1/untracked_file
                )
        '
 
@@ -825,19 +773,6 @@ test_submodule_switch_recursing_with_args () {
                        test_submodule_content sub1 origin/modify_sub1
                )
        '
-
-       test_expect_success "git -c submodule.recurse=true $cmd_args: modified submodule updates submodule work tree" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t modify_sub1 origin/modify_sub1 &&
-                       git -c submodule.recurse=true $cmd_args modify_sub1 &&
-                       test_superproject_content origin/modify_sub1 &&
-                       test_submodule_content sub1 origin/modify_sub1
-               )
-       '
-
        # Updating a submodule to an invalid sha1 doesn't update the
        # superproject nor the submodule's work tree.
        test_expect_success "$command: updating to a missing submodule commit fails" '
@@ -851,126 +786,166 @@ test_submodule_switch_recursing_with_args () {
                        test_submodule_content sub1 origin/add_sub1
                )
        '
-
-       # recursing deeper than one level doesn't work yet.
-       test_expect_success "$command: modified submodule updates submodule recursively" '
-               prolog &&
-               reset_work_tree_to_interested add_nested_sub &&
-               (
-                       cd submodule_update &&
-                       git branch -t modify_sub1_recursively origin/modify_sub1_recursively &&
-                       $command modify_sub1_recursively &&
-                       test_superproject_content origin/modify_sub1_recursively &&
-                       test_submodule_content sub1 origin/modify_sub1_recursively &&
-                       test_submodule_content -C sub1 sub2 origin/modify_sub1_recursively
-               )
-       '
 }
 
-# Test that submodule contents are updated when switching between commits
-# that change a submodule, but throwing away local changes in
-# the superproject as well as the submodule is allowed.
-test_submodule_forced_switch_recursing_with_args () {
+# Declares and invokes several tests that, in various situations, checks that
+# the provided Git command, when invoked with --recurse-submodules:
+#  - succeeds in updating the worktree and index of a superproject to a target
+#    commit, or fails atomically (depending on the test situation)
+#  - if succeeds, the contents of submodule directories are updated
+#
+# Specify the Git command so that "git $GIT_COMMAND --recurse-submodules"
+# works.
+#
+# If the command under test is known to not work with submodules in certain
+# conditions, set the appropriate KNOWN_FAILURE_* variable used in the tests
+# below to 1.
+#
+# Use as follows:
+#
+# test_submodule_switch_recursing_with_args "$GIT_COMMAND"
+test_submodule_switch_recursing_with_args () {
        cmd_args="$1"
        command="git $cmd_args --recurse-submodules"
-       RESULT=success
+       test_submodule_recursing_with_args_common "$command"
+
+       RESULTDS=success
        if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1
        then
-               RESULT=failure
+               RESULTDS=failure
        fi
-       ######################### Appearing submodule #########################
-       # Switching to a commit letting a submodule appear creates empty dir ...
-       test_expect_success "$command: added submodule is checked out" '
+       RESULTOI=success
+       if test "$KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED" = 1
+       then
+               RESULTOI=failure
+       fi
+       # Switching to a commit letting a submodule appear cannot override an
+       # untracked file.
+       test_expect_success "$command: added submodule doesn't remove untracked file with same name" '
                prolog &&
                reset_work_tree_to_interested no_submodule &&
                (
                        cd submodule_update &&
                        git branch -t add_sub1 origin/add_sub1 &&
-                       $command add_sub1 &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
+                       : >sub1 &&
+                       test_must_fail $command add_sub1 &&
+                       test_superproject_content origin/no_submodule &&
+                       test_must_be_empty sub1
                )
        '
-       # ... and doesn't care if it already exists ...
-       test_expect_success "$command: added submodule ignores empty directory" '
+       # ... but an ignored file is fine.
+       test_expect_$RESULTOI "$command: added submodule removes an untracked ignored file" '
+               test_when_finished "rm submodule_update/.git/info/exclude" &&
                prolog &&
                reset_work_tree_to_interested no_submodule &&
                (
                        cd submodule_update &&
                        git branch -t add_sub1 origin/add_sub1 &&
-                       mkdir sub1 &&
+                       : >sub1 &&
+                       echo sub1 >.git/info/exclude
                        $command add_sub1 &&
                        test_superproject_content origin/add_sub1 &&
                        test_submodule_content sub1 origin/add_sub1
                )
        '
-       # ... not caring about an untracked file either
-       test_expect_success "$command: added submodule does remove untracked unignored file with same name when forced" '
+
+       # Replacing a submodule with files in a directory must succeeds
+       # when the submodule is clean
+       test_expect_$RESULTDS "$command: replace submodule with a directory" '
                prolog &&
-               reset_work_tree_to_interested no_submodule &&
+               reset_work_tree_to_interested add_sub1 &&
                (
                        cd submodule_update &&
-                       git branch -t add_sub1 origin/add_sub1 &&
-                       >sub1 &&
-                       $command add_sub1 &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
+                       git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
+                       $command replace_sub1_with_directory &&
+                       test_superproject_content origin/replace_sub1_with_directory &&
+                       test_submodule_content sub1 origin/replace_sub1_with_directory
                )
        '
-       # Replacing a tracked file with a submodule checks out the submodule
-       test_expect_success "$command: replace tracked file with submodule populates the submodule" '
+       # ... absorbing a .git directory.
+       test_expect_$RESULTDS "$command: replace submodule containing a .git directory with a directory must absorb the git dir" '
                prolog &&
-               reset_work_tree_to_interested replace_sub1_with_file &&
+               reset_work_tree_to_interested add_sub1 &&
                (
                        cd submodule_update &&
-                       git branch -t replace_file_with_sub1 origin/replace_file_with_sub1 &&
-                       $command replace_file_with_sub1 &&
-                       test_superproject_content origin/replace_file_with_sub1 &&
-                       test_submodule_content sub1 origin/replace_file_with_sub1
+                       git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
+                       replace_gitfile_with_git_dir sub1 &&
+                       rm -rf .git/modules &&
+                       $command replace_sub1_with_directory &&
+                       test_superproject_content origin/replace_sub1_with_directory &&
+                       test_git_directory_exists sub1
                )
        '
-       # ... as does removing a directory with tracked files with a
-       # submodule.
-       test_expect_success "$command: replace directory with submodule" '
+
+       # ... and ignored files are ignored
+       test_expect_success "$command: replace submodule with a file works ignores ignored files in submodule" '
+               test_when_finished "rm submodule_update/.git/modules/sub1/info/exclude" &&
                prolog &&
-               reset_work_tree_to_interested replace_sub1_with_directory &&
+               reset_work_tree_to_interested add_sub1 &&
                (
                        cd submodule_update &&
-                       git branch -t replace_directory_with_sub1 origin/replace_directory_with_sub1 &&
-                       $command replace_directory_with_sub1 &&
-                       test_superproject_content origin/replace_directory_with_sub1 &&
-                       test_submodule_content sub1 origin/replace_directory_with_sub1
+                       git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
+                       : >sub1/ignored &&
+                       $command replace_sub1_with_file &&
+                       test_superproject_content origin/replace_sub1_with_file &&
+                       test -f sub1
                )
        '
 
-       ######################## Disappearing submodule #######################
-       # Removing a submodule doesn't remove its work tree ...
-       test_expect_success "$command: removed submodule leaves submodule directory and its contents in place" '
+       test_expect_success "git -c submodule.recurse=true $cmd_args: modified submodule updates submodule work tree" '
                prolog &&
                reset_work_tree_to_interested add_sub1 &&
                (
                        cd submodule_update &&
-                       git branch -t remove_sub1 origin/remove_sub1 &&
-                       $command remove_sub1 &&
-                       test_superproject_content origin/remove_sub1 &&
-                       ! test -e sub1
+                       git branch -t modify_sub1 origin/modify_sub1 &&
+                       git -c submodule.recurse=true $cmd_args modify_sub1 &&
+                       test_superproject_content origin/modify_sub1 &&
+                       test_submodule_content sub1 origin/modify_sub1
                )
        '
-       # ... especially when it contains a .git directory.
-       test_expect_success "$command: removed submodule leaves submodule containing a .git directory alone" '
+
+       # recursing deeper than one level doesn't work yet.
+       test_expect_success "$command: modified submodule updates submodule recursively" '
                prolog &&
-               reset_work_tree_to_interested add_sub1 &&
+               reset_work_tree_to_interested add_nested_sub &&
                (
                        cd submodule_update &&
-                       git branch -t remove_sub1 origin/remove_sub1 &&
-                       replace_gitfile_with_git_dir sub1 &&
-                       rm -rf .git/modules/sub1 &&
-                       $command remove_sub1 &&
-                       test_superproject_content origin/remove_sub1 &&
-                       test_git_directory_exists sub1 &&
-                       ! test -e sub1
+                       git branch -t modify_sub1_recursively origin/modify_sub1_recursively &&
+                       $command modify_sub1_recursively &&
+                       test_superproject_content origin/modify_sub1_recursively &&
+                       test_submodule_content sub1 origin/modify_sub1_recursively &&
+                       test_submodule_content -C sub1 sub2 origin/modify_sub1_recursively
+               )
+       '
+}
+
+# Same as test_submodule_switch_recursing_with_args(), except that throwing
+# away local changes in the superproject is allowed.
+test_submodule_forced_switch_recursing_with_args () {
+       cmd_args="$1"
+       command="git $cmd_args --recurse-submodules"
+       test_submodule_recursing_with_args_common "$command"
+
+       RESULT=success
+       if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1
+       then
+               RESULT=failure
+       fi
+       # Switching to a commit letting a submodule appear does not care about
+       # an untracked file.
+       test_expect_success "$command: added submodule does remove untracked unignored file with same name when forced" '
+               prolog &&
+               reset_work_tree_to_interested no_submodule &&
+               (
+                       cd submodule_update &&
+                       git branch -t add_sub1 origin/add_sub1 &&
+                       >sub1 &&
+                       $command add_sub1 &&
+                       test_superproject_content origin/add_sub1 &&
+                       test_submodule_content sub1 origin/add_sub1
                )
        '
+
        # Replacing a submodule with files in a directory ...
        test_expect_success "$command: replace submodule with a directory" '
                prolog &&
@@ -997,17 +972,6 @@ test_submodule_forced_switch_recursing_with_args () {
                        test_git_directory_exists sub1
                )
        '
-       # Replacing it with a file
-       test_expect_success "$command: replace submodule with a file" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
-                       $command replace_sub1_with_file &&
-                       test_superproject_content origin/replace_sub1_with_file
-               )
-       '
 
        # ... even if the submodule contains ignored files
        test_expect_success "$command: replace submodule with a file ignoring ignored files" '
@@ -1022,46 +986,6 @@ test_submodule_forced_switch_recursing_with_args () {
                )
        '
 
-       # ... but stops for untracked files that would be lost
-       test_expect_$RESULT "$command: replace submodule with a file stops for untracked files" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
-                       : >sub1/untracked_file &&
-                       test_must_fail $command replace_sub1_with_file &&
-                       test_superproject_content origin/add_sub1 &&
-                       test -f sub1/untracked_file
-               )
-       '
-
-       ########################## Modified submodule #########################
-       # Updating a submodule sha1 updates the submodule's work tree
-       test_expect_success "$command: modified submodule updates submodule work tree" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t modify_sub1 origin/modify_sub1 &&
-                       $command modify_sub1 &&
-                       test_superproject_content origin/modify_sub1 &&
-                       test_submodule_content sub1 origin/modify_sub1
-               )
-       '
-       # Updating a submodule to an invalid sha1 doesn't update the
-       # submodule's work tree, subsequent update will fail
-       test_expect_success "$command: modified submodule does not update submodule work tree to invalid commit" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git branch -t invalid_sub1 origin/invalid_sub1 &&
-                       test_must_fail $command invalid_sub1 &&
-                       test_superproject_content origin/add_sub1 &&
-                       test_submodule_content sub1 origin/add_sub1
-               )
-       '
        # Updating a submodule from an invalid sha1 updates
        test_expect_success "$command: modified submodule does update submodule work tree from invalid commit" '
                prolog &&
index 1dbc85b21407dcfc83d9dc6a56e8e0f21c748c0c..e40120848837c1ef78cf75d1724c69226823db15 100755 (executable)
@@ -69,12 +69,17 @@ sub format_times {
        @tests = glob "p????-*.sh";
 }
 
+my $resultsdir = "test-results";
+if ($ENV{GIT_PERF_SUBSECTION} ne "") {
+       $resultsdir .= "/" . $ENV{GIT_PERF_SUBSECTION};
+}
+
 my @subtests;
 my %shorttests;
 for my $t (@tests) {
        $t =~ s{(?:.*/)?(p(\d+)-[^/]+)\.sh$}{$1} or die "bad test name: $t";
        my $n = $2;
-       my $fname = "test-results/$t.subtests";
+       my $fname = "$resultsdir/$t.subtests";
        open my $fp, "<", $fname or die "cannot open $fname: $!";
        for (<$fp>) {
                chomp;
@@ -98,7 +103,7 @@ sub read_descr {
 my %descrs;
 my $descrlen = 4; # "Test"
 for my $t (@subtests) {
-       $descrs{$t} = $shorttests{$t}.": ".read_descr("test-results/$t.descr");
+       $descrs{$t} = $shorttests{$t}.": ".read_descr("$resultsdir/$t.descr");
        $descrlen = length $descrs{$t} if length $descrs{$t}>$descrlen;
 }
 
@@ -138,7 +143,7 @@ sub have_slash {
        my $firstr;
        for my $i (0..$#dirs) {
                my $d = $dirs[$i];
-               $times{$prefixes{$d}.$t} = [get_times("test-results/$prefixes{$d}$t.times")];
+               $times{$prefixes{$d}.$t} = [get_times("$resultsdir/$prefixes{$d}$t.times")];
                my ($r,$u,$s) = @{$times{$prefixes{$d}.$t}};
                my $w = length format_times($r,$u,$s,$firstr);
                $colwidth[$i] = $w if $w > $colwidth[$i];
diff --git a/t/perf/lib-pack.sh b/t/perf/lib-pack.sh
new file mode 100644 (file)
index 0000000..d3865db
--- /dev/null
@@ -0,0 +1,25 @@
+# Helpers for dealing with large numbers of packs.
+
+# create $1 nonsense packs, each with a single blob
+create_packs () {
+       perl -le '
+               my ($n) = @ARGV;
+               for (1..$n) {
+                       print "blob";
+                       print "data <<EOF";
+                       print "$_";
+                       print "EOF";
+                       print "checkpoint"
+               }
+       ' "$@" |
+       git fast-import
+}
+
+# create a large number of packs, disabling any gc which might
+# cause us to repack them
+setup_many_packs () {
+       git config gc.auto 0 &&
+       git config gc.autopacklimit 0 &&
+       git config fastimport.unpacklimit 0 &&
+       create_packs 500
+}
index a5dc39f86af65d6f6b200b95c4f3c66d5f482ec1..d0e0e019ea364cb1cef3863d256aa5596e01eb56 100755 (executable)
@@ -20,6 +20,7 @@ start to show a noticeable performance problem on my machine, but without
 taking too long to set up and run the tests.
 '
 . ./perf-lib.sh
+. "$TEST_DIRECTORY/perf/lib-pack.sh"
 
 # make a long nonsense history on branch $1, consisting of $2 commits, each
 # with a unique file pointing to the blob at $2.
@@ -44,26 +45,6 @@ create_tags () {
        git update-ref --stdin
 }
 
-# create $1 nonsense packs, each with a single blob
-create_packs () {
-       perl -le '
-               my ($n) = @ARGV;
-               for (1..$n) {
-                       print "blob";
-                       print "data <<EOF";
-                       print "$_";
-                       print "EOF";
-               }
-       ' "$@" |
-       git fast-import &&
-
-       git cat-file --batch-all-objects --batch-check='%(objectname)' |
-       while read sha1
-       do
-               echo $sha1 | git pack-objects .git/objects/pack/pack
-       done
-}
-
 test_expect_success 'create parent and child' '
        git init parent &&
        git -C parent commit --allow-empty -m base &&
@@ -84,9 +65,7 @@ test_expect_success 'populate parent tags' '
 test_expect_success 'create child packs' '
        (
                cd child &&
-               git config gc.auto 0 &&
-               git config gc.autopacklimit 0 &&
-               create_packs 500
+               setup_many_packs
        )
 '
 
diff --git a/t/perf/p5551-fetch-rescan.sh b/t/perf/p5551-fetch-rescan.sh
new file mode 100755 (executable)
index 0000000..b99dc23
--- /dev/null
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+test_description='fetch performance with many packs
+
+It is common for fetch to consider objects that we might not have, and it is an
+easy mistake for the code to use a function like `parse_object` that might
+give the correct _answer_ on such an object, but do so slowly (due to
+re-scanning the pack directory for lookup failures).
+
+The resulting performance drop can be hard to notice in a real repository, but
+becomes quite large in a repository with a large number of packs. So this
+test creates a more pathological case, since any mistakes would produce a more
+noticeable slowdown.
+'
+. ./perf-lib.sh
+. "$TEST_DIRECTORY"/perf/lib-pack.sh
+
+test_expect_success 'create parent and child' '
+       git init parent &&
+       git clone parent child
+'
+
+
+test_expect_success 'create refs in the parent' '
+       (
+               cd parent &&
+               git commit --allow-empty -m foo &&
+               head=$(git rev-parse HEAD) &&
+               test_seq 1000 |
+               sed "s,.*,update refs/heads/& $head," |
+               $MODERN_GIT update-ref --stdin
+       )
+'
+
+test_expect_success 'create many packs in the child' '
+       (
+               cd child &&
+               setup_many_packs
+       )
+'
+
+test_perf 'fetch' '
+       # start at the same state for each iteration
+       obj=$($MODERN_GIT -C parent rev-parse HEAD) &&
+       (
+               cd child &&
+               $MODERN_GIT for-each-ref --format="delete %(refname)" refs/remotes |
+               $MODERN_GIT update-ref --stdin &&
+               rm -vf .git/objects/$(echo $obj | sed "s|^..|&/|") &&
+
+               git fetch
+       )
+'
+
+test_done
index b50211b2591d19904fa3c995b01aea2121db476a..e4c343a6b795b6f990fa278788ad65a0a5cdc4e6 100644 (file)
@@ -56,12 +56,10 @@ MODERN_GIT=$GIT_BUILD_DIR/bin-wrappers/git
 export MODERN_GIT
 
 perf_results_dir=$TEST_OUTPUT_DIRECTORY/test-results
+test -n "$GIT_PERF_SUBSECTION" && perf_results_dir="$perf_results_dir/$GIT_PERF_SUBSECTION"
 mkdir -p "$perf_results_dir"
 rm -f "$perf_results_dir"/$(basename "$0" .sh).subtests
 
-if test -z "$GIT_PERF_REPEAT_COUNT"; then
-       GIT_PERF_REPEAT_COUNT=3
-fi
 die_if_build_dir_not_repo () {
        if ! ( cd "$TEST_DIRECTORY/.." &&
                    git rev-parse --build-dir >/dev/null 2>&1 ); then
index beb4acc0e428d20280a649c428da8b0b3aa5b4c6..43e4de49ef2bea9ae09b502f28fbc0913e294571 100755 (executable)
@@ -2,9 +2,14 @@
 
 case "$1" in
        --help)
-               echo "usage: $0 [other_git_tree...] [--] [test_scripts]"
+               echo "usage: $0 [--config file] [other_git_tree...] [--] [test_scripts]"
                exit 0
                ;;
+       --config)
+               shift
+               GIT_PERF_CONFIG_FILE=$(cd "$(dirname "$1")"; pwd)/$(basename "$1")
+               export GIT_PERF_CONFIG_FILE
+               shift ;;
 esac
 
 die () {
@@ -29,8 +34,10 @@ unpack_git_rev () {
        (cd "$(git rev-parse --show-cdup)" && git archive --format=tar $rev) |
        (cd build/$rev && tar x)
 }
+
 build_git_rev () {
        rev=$1
+       name="$2"
        for config in config.mak config.mak.autogen config.status
        do
                if test -e "../../$config"
@@ -38,7 +45,7 @@ build_git_rev () {
                        cp "../../$config" "build/$rev/"
                fi
        done
-       echo "=== Building $rev ==="
+       echo "=== Building $rev ($name) ==="
        (
                cd build/$rev &&
                if test -n "$GIT_PERF_MAKE_COMMAND"
@@ -65,7 +72,7 @@ run_dirs_helper () {
                if [ ! -d build/$rev ]; then
                        unpack_git_rev $rev
                fi
-               build_git_rev $rev
+               build_git_rev $rev "$mydir"
                mydir=build/$rev
        fi
        if test "$mydir" = .; then
@@ -87,14 +94,78 @@ run_dirs () {
        done
 }
 
-GIT_PERF_AGGREGATING_LATER=t
-export GIT_PERF_AGGREGATING_LATER
+get_subsections () {
+       section="$1"
+       test -z "$GIT_PERF_CONFIG_FILE" && return
+       git config -f "$GIT_PERF_CONFIG_FILE" --name-only --get-regex "$section\..*\.[^.]+" |
+       sed -e "s/$section\.\(.*\)\..*/\1/" | sort | uniq
+}
+
+get_var_from_env_or_config () {
+       env_var="$1"
+       conf_sec="$2"
+       conf_var="$3"
+       # $4 can be set to a default value
+
+       # Do nothing if the env variable is already set
+       eval "test -z \"\${$env_var+x}\"" || return
+
+       test -z "$GIT_PERF_CONFIG_FILE" && return
+
+       # Check if the variable is in the config file
+       if test -n "$GIT_PERF_SUBSECTION"
+       then
+               var="$conf_sec.$GIT_PERF_SUBSECTION.$conf_var"
+               conf_value=$(git config -f "$GIT_PERF_CONFIG_FILE" "$var") &&
+               eval "$env_var=\"$conf_value\"" && return
+       fi
+       var="$conf_sec.$conf_var"
+       conf_value=$(git config -f "$GIT_PERF_CONFIG_FILE" "$var") &&
+       eval "$env_var=\"$conf_value\"" && return
+
+       test -n "${4+x}" && eval "$env_var=\"$4\""
+}
+
+run_subsection () {
+       get_var_from_env_or_config "GIT_PERF_REPEAT_COUNT" "perf" "repeatCount" 3
+       export GIT_PERF_REPEAT_COUNT
+
+       get_var_from_env_or_config "GIT_PERF_DIRS_OR_REVS" "perf" "dirsOrRevs"
+       set -- $GIT_PERF_DIRS_OR_REVS "$@"
+
+       get_var_from_env_or_config "GIT_PERF_MAKE_COMMAND" "perf" "makeCommand"
+       get_var_from_env_or_config "GIT_PERF_MAKE_OPTS" "perf" "makeOpts"
+
+       GIT_PERF_AGGREGATING_LATER=t
+       export GIT_PERF_AGGREGATING_LATER
+
+       if test $# = 0 -o "$1" = -- -o -f "$1"; then
+               set -- . "$@"
+       fi
+
+       run_dirs "$@"
+       ./aggregate.perl "$@"
+}
 
 cd "$(dirname $0)"
 . ../../GIT-BUILD-OPTIONS
 
-if test $# = 0 -o "$1" = -- -o -f "$1"; then
-       set -- . "$@"
+mkdir -p test-results
+get_subsections "perf" >test-results/run_subsections.names
+
+if test $(wc -l <test-results/run_subsections.names) -eq 0
+then
+       (
+               run_subsection "$@"
+       )
+else
+       while read -r subsec
+       do
+               (
+                       GIT_PERF_SUBSECTION="$subsec"
+                       export GIT_PERF_SUBSECTION
+                       echo "======== Run for subsection '$GIT_PERF_SUBSECTION' ========"
+                       run_subsection "$@"
+               )
+       done <test-results/run_subsections.names
 fi
-run_dirs "$@"
-./aggregate.perl "$@"
index 6fd7fa476be2a091f683174d57100cbc7ba19fb8..f1678851de954dd93ab0e2fc7912813ad5f09960 100644 (file)
@@ -70,7 +70,7 @@ sub rot13 {
 $debug->flush();
 
 while (1) {
-       my ( $res, $command ) = packet_required_key_val_read("command");
+       my ( $res, $command ) = packet_key_val_read("command");
        if ( $res == -1 ) {
                print $debug "STOP\n";
                exit();
@@ -106,7 +106,7 @@ sub rot13 {
                packet_txt_write("status=success");
                packet_flush();
        } else {
-               my ( $res, $pathname ) = packet_required_key_val_read("pathname");
+               my ( $res, $pathname ) = packet_key_val_read("pathname");
                if ( $res == -1 ) {
                        die "unexpected EOF while expecting pathname";
                }
diff --git a/t/t0025-crlf-renormalize.sh b/t/t0025-crlf-renormalize.sh
new file mode 100755 (executable)
index 0000000..9d9e02a
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+test_description='CRLF renormalization'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       git config core.autocrlf false &&
+       printf "LINEONE\nLINETWO\nLINETHREE\n" >LF.txt &&
+       printf "LINEONE\r\nLINETWO\r\nLINETHREE\r\n" >CRLF.txt &&
+       printf "LINEONE\r\nLINETWO\nLINETHREE\n" >CRLF_mix_LF.txt &&
+       git add . &&
+       git commit -m initial
+'
+
+test_expect_success 'renormalize CRLF in repo' '
+       echo "*.txt text=auto" >.gitattributes &&
+       git add --renormalize "*.txt" &&
+       cat >expect <<-\EOF &&
+       i/lf w/crlf attr/text=auto CRLF.txt
+       i/lf w/lf attr/text=auto LF.txt
+       i/lf w/mixed attr/text=auto CRLF_mix_LF.txt
+       EOF
+       git ls-files --eol |
+       sed -e "s/      / /g" -e "s/  */ /g" |
+       sort >actual &&
+       test_cmp expect actual
+'
+
+test_done
index 364a537000bbbdd43047bd3f9c52c950a96dcbda..cbeb9bebeea67c3654c279b2c12fed4825840d8f 100755 (executable)
@@ -901,6 +901,36 @@ test_expect_success 'get --path barfs on boolean variable' '
        test_must_fail git config --get --path path.bool
 '
 
+test_expect_success 'get --expiry-date' '
+       rel="3.weeks.5.days.00:00" &&
+       rel_out="$rel ->" &&
+       cat >.git/config <<-\EOF &&
+       [date]
+       valid1 = "3.weeks.5.days 00:00"
+       valid2 = "Fri Jun 4 15:46:55 2010"
+       valid3 = "2017/11/11 11:11:11PM"
+       valid4 = "2017/11/10 09:08:07 PM"
+       valid5 = "never"
+       invalid1 = "abc"
+       EOF
+       cat >expect <<-EOF &&
+       $(test-date timestamp $rel)
+       1275666415
+       1510441871
+       1510348087
+       0
+       EOF
+       {
+               echo "$rel_out $(git config --expiry-date date.valid1)"
+               git config --expiry-date date.valid2 &&
+               git config --expiry-date date.valid3 &&
+               git config --expiry-date date.valid4 &&
+               git config --expiry-date date.valid5
+       } >actual &&
+       test_cmp expect actual &&
+       test_must_fail git config --expiry-date date.invalid1
+'
+
 cat > expect << EOF
 [quote]
        leading = " test"
index e88349c8a0483b97ea38013de61d799003fd85b5..c7878a60edfdcf24f3c45367f58e62b0ce6dab71 100755 (executable)
@@ -331,4 +331,47 @@ test_expect_success 'update-ref --stdin -z fails delete with bad ref name' '
        grep "fatal: invalid ref format: ~a" err
 '
 
+test_expect_success 'branch rejects HEAD as a branch name' '
+       test_must_fail git branch HEAD HEAD^ &&
+       test_must_fail git show-ref refs/heads/HEAD
+'
+
+test_expect_success 'checkout -b rejects HEAD as a branch name' '
+       test_must_fail git checkout -B HEAD HEAD^ &&
+       test_must_fail git show-ref refs/heads/HEAD
+'
+
+test_expect_success 'update-ref can operate on refs/heads/HEAD' '
+       git update-ref refs/heads/HEAD HEAD^ &&
+       git show-ref refs/heads/HEAD &&
+       git update-ref -d refs/heads/HEAD &&
+       test_must_fail git show-ref refs/heads/HEAD
+'
+
+test_expect_success 'branch -d can remove refs/heads/HEAD' '
+       git update-ref refs/heads/HEAD HEAD^ &&
+       git branch -d HEAD &&
+       test_must_fail git show-ref refs/heads/HEAD
+'
+
+test_expect_success 'branch -m can rename refs/heads/HEAD' '
+       git update-ref refs/heads/HEAD HEAD^ &&
+       git branch -m HEAD tail &&
+       test_must_fail git show-ref refs/heads/HEAD &&
+       git show-ref refs/heads/tail
+'
+
+test_expect_success 'branch -d can remove refs/heads/-dash' '
+       git update-ref refs/heads/-dash HEAD^ &&
+       git branch -d -- -dash &&
+       test_must_fail git show-ref refs/heads/-dash
+'
+
+test_expect_success 'branch -m can rename refs/heads/-dash' '
+       git update-ref refs/heads/-dash HEAD^ &&
+       git branch -m -- -dash dash &&
+       test_must_fail git show-ref refs/heads/-dash &&
+       git show-ref refs/heads/dash
+'
+
 test_done
index baef2d692431169d83e0bc3aed11d9030ea4a24a..9c1bf6eb3d46f31c0e1816508dcc8fee4699f823 100755 (executable)
@@ -176,7 +176,7 @@ git rev-parse refs/notes/z > pre_merge_z
 test_expect_success 'merge z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
        git update-ref refs/notes/m refs/notes/y &&
        git config core.notesRef refs/notes/m &&
-       test_must_fail git notes merge z >output &&
+       test_must_fail git notes merge z >output 2>&1 &&
        # Output should point to where to resolve conflicts
        test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
        # Inspect merge conflicts
@@ -379,7 +379,7 @@ git rev-parse refs/notes/z > pre_merge_z
 test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
        git update-ref refs/notes/m refs/notes/y &&
        git config core.notesRef refs/notes/m &&
-       test_must_fail git notes merge z >output &&
+       test_must_fail git notes merge z >output 2>&1 &&
        # Output should point to where to resolve conflicts
        test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
        # Inspect merge conflicts
@@ -413,7 +413,7 @@ git rev-parse refs/notes/y > pre_merge_y
 git rev-parse refs/notes/z > pre_merge_z
 
 test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
-       test_must_fail git notes merge z >output &&
+       test_must_fail git notes merge z >output 2>&1 &&
        # Output should point to where to resolve conflicts
        test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
        # Inspect merge conflicts
@@ -494,7 +494,7 @@ cp expect_log_y expect_log_m
 
 test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
        git update-ref refs/notes/m refs/notes/y &&
-       test_must_fail git notes merge z >output &&
+       test_must_fail git notes merge z >output 2>&1 &&
        # Output should point to where to resolve conflicts
        test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
        # Inspect merge conflicts
index b9c3bc2487aa7dd4bb009d45f784e047f772f62d..10bfc8b94710caf8d33a3a1fa66ad3a4ba394c0f 100755 (executable)
@@ -61,7 +61,7 @@ test_expect_success 'merge z into x while mid-merge on y succeeds' '
        (
                cd worktree2 &&
                git config core.notesRef refs/notes/x &&
-               test_must_fail git notes merge z 2>&1 >out &&
+               test_must_fail git notes merge z >out 2>&1 &&
                test_i18ngrep "Automatic notes merge failed" out &&
                grep -v "A notes merge into refs/notes/x is already in-progress in" out
        ) &&
index f5fd15e55994bafc0f0a2c91b0d00455df13d18d..8ac58d5ea5e4b8b75deaa74f3d6bca29f37dbcb6 100755 (executable)
@@ -255,4 +255,26 @@ test_expect_success 'rebase commit with an ancient timestamp' '
        grep "author .* 34567 +0600$" actual
 '
 
+test_expect_success 'rebase with "From " line in commit message' '
+       git checkout -b preserve-from master~1 &&
+       cat >From_.msg <<EOF &&
+Somebody embedded an mbox in a commit message
+
+This is from so-and-so:
+
+From a@b Mon Sep 17 00:00:00 2001
+From: John Doe <nobody@example.com>
+Date: Sat, 11 Nov 2017 00:00:00 +0000
+Subject: not this message
+
+something
+EOF
+       >From_ &&
+       git add From_ &&
+       git commit -F From_.msg &&
+       git rebase master &&
+       git log -1 --pretty=format:%B >out &&
+       test_cmp From_.msg out
+'
+
 test_done
index 6863b7bb6fd94cbbd3fcc8f29ab02e344cac23c9..ce48c4fcca80b183927292cc1e5902cfe286f994 100755 (executable)
@@ -10,4 +10,40 @@ KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
 KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
 test_submodule_switch "git cherry-pick"
 
+test_expect_success 'unrelated submodule/file conflict is ignored' '
+       test_create_repo sub &&
+
+       touch sub/file &&
+       git -C sub add file &&
+       git -C sub commit -m "add a file in a submodule" &&
+
+       test_create_repo a_repo &&
+       (
+               cd a_repo &&
+               >a_file &&
+               git add a_file &&
+               git commit -m "add a file" &&
+
+               git branch test &&
+               git checkout test &&
+
+               mkdir sub &&
+               >sub/content &&
+               git add sub/content &&
+               git commit -m "add a regular folder with name sub" &&
+
+               echo "123" >a_file &&
+               git add a_file &&
+               git commit -m "modify a file" &&
+
+               git checkout master &&
+
+               git submodule add ../sub sub &&
+               git submodule update sub &&
+               git commit -m "add a submodule info folder with name sub" &&
+
+               git cherry-pick test
+       )
+'
+
 test_done
index 3b1ac1971af1d972b77b3dfe8ae4a56e00e1fa55..39c7f2ebd740db097d67b12a68fa35f3fa884fc8 100755 (executable)
@@ -804,6 +804,99 @@ test_expect_success 'push -m shows right message' '
        test_cmp expect actual
 '
 
+test_expect_success 'push -m also works without space' '
+       >foo &&
+       git add foo &&
+       git stash push -m"unspaced test message" &&
+       echo "stash@{0}: On master: unspaced test message" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'store -m foo shows right message' '
+       git stash clear &&
+       git reset --hard &&
+       echo quux >bazzy &&
+       git add bazzy &&
+       STASH_ID=$(git stash create) &&
+       git stash store -m "store m" $STASH_ID &&
+       echo "stash@{0}: store m" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'store -mfoo shows right message' '
+       git stash clear &&
+       git reset --hard &&
+       echo quux >bazzy &&
+       git add bazzy &&
+       STASH_ID=$(git stash create) &&
+       git stash store -m"store mfoo" $STASH_ID &&
+       echo "stash@{0}: store mfoo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'store --message=foo shows right message' '
+       git stash clear &&
+       git reset --hard &&
+       echo quux >bazzy &&
+       git add bazzy &&
+       STASH_ID=$(git stash create) &&
+       git stash store --message="store message=foo" $STASH_ID &&
+       echo "stash@{0}: store message=foo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'store --message foo shows right message' '
+       git stash clear &&
+       git reset --hard &&
+       echo quux >bazzy &&
+       git add bazzy &&
+       STASH_ID=$(git stash create) &&
+       git stash store --message "store message foo" $STASH_ID &&
+       echo "stash@{0}: store message foo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'push -mfoo uses right message' '
+       >foo &&
+       git add foo &&
+       git stash push -m"test mfoo" &&
+       echo "stash@{0}: On master: test mfoo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'push --message foo is synonym for -mfoo' '
+       >foo &&
+       git add foo &&
+       git stash push --message "test message foo" &&
+       echo "stash@{0}: On master: test message foo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'push --message=foo is synonym for -mfoo' '
+       >foo &&
+       git add foo &&
+       git stash push --message="test message=foo" &&
+       echo "stash@{0}: On master: test message=foo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'push -m shows right message' '
+       >foo &&
+       git add foo &&
+       git stash push -m "test m foo" &&
+       echo "stash@{0}: On master: test m foo" >expect &&
+       git stash list -1 >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'create stores correct message' '
        >foo &&
        git add foo &&
index 6c9a93b734542cc17fa1591525dc3c49019b77fe..559a7541a83eb8bd8b465aac154cf67f8783b13d 100755 (executable)
@@ -106,6 +106,8 @@ test_expect_success 'another test, without options' '
        git diff -w -b --ignore-space-at-eol >out &&
        test_cmp expect out &&
 
+       git diff -w --ignore-cr-at-eol >out &&
+       test_cmp expect out &&
 
        tr "Q_" "\015 " <<-\EOF >expect &&
        diff --git a/x b/x
@@ -128,6 +130,9 @@ test_expect_success 'another test, without options' '
        git diff -b --ignore-space-at-eol >out &&
        test_cmp expect out &&
 
+       git diff -b --ignore-cr-at-eol >out &&
+       test_cmp expect out &&
+
        tr "Q_" "\015 " <<-\EOF >expect &&
        diff --git a/x b/x
        index d99af23..22d9f73 100644
@@ -145,6 +150,29 @@ test_expect_success 'another test, without options' '
         CR at end
        EOF
        git diff --ignore-space-at-eol >out &&
+       test_cmp expect out &&
+
+       git diff --ignore-space-at-eol --ignore-cr-at-eol >out &&
+       test_cmp expect out &&
+
+       tr "Q_" "\015 " <<-\EOF >expect &&
+       diff --git a/x b/x
+       index_d99af23..22d9f73 100644
+       --- a/x
+       +++ b/x
+       @@ -1,6 +1,6 @@
+       -whitespace at beginning
+       -whitespace change
+       -whitespace in the middle
+       -whitespace at end
+       +_      whitespace at beginning
+       +whitespace_    _change
+       +white space in the middle
+       +whitespace at end__
+        unchanged line
+        CR at end
+       EOF
+       git diff --ignore-cr-at-eol >out &&
        test_cmp expect out
 '
 
index 3e6b485ecba1a59e3d69edc3ac5a6f0f1d327bd3..2d76a971c43f6850a1f443eab3e91bf3762fe952 100755 (executable)
@@ -85,6 +85,10 @@ test_expect_success 'setup' '
 
 check_diff changed_hello 'changed function'
 
+test_expect_success ' context includes comment' '
+       grep "^ .*Hello comment" changed_hello.diff
+'
+
 test_expect_success ' context includes begin' '
        grep "^ .*Begin of hello" changed_hello.diff
 '
index 63b1a1e4efbb4c5dee315ba56a6ad0658da0a9a5..73e767e1783d8ab97d7a45297a173b1bed9f00a2 100644 (file)
@@ -1,4 +1,7 @@
 
+/*
+ * Hello comment.
+ */
 static void hello(void)        // Begin of hello
 {
        /*
index 9e29b5262d3f2e8514d5742951e28a3a339218dc..ac72eeaf27a9b925d801052fb3d051189c0a832a 100755 (executable)
@@ -178,4 +178,18 @@ test_expect_success 'patch5 fails (--no-ignore-whitespace)' '
        test_must_fail git apply --no-ignore-whitespace patch5.patch
 '
 
+test_expect_success 'apply --ignore-space-change --inaccurate-eof' '
+       echo 1 >file &&
+       git apply --ignore-space-change --inaccurate-eof <<-\EOF &&
+       diff --git a/file b/file
+       --- a/file
+       +++ b/file
+       @@ -1 +1 @@
+       -1
+       +2
+       EOF
+       printf 2 >expect &&
+       test_cmp expect file
+'
+
 test_done
index 50e40abb11e1d317a2a58321f1007dde925c8da2..0f895478f02bcf4cc4408ce065e79e0314b66793 100755 (executable)
@@ -306,23 +306,21 @@ test_expect_success 'clone checking out a tag' '
        test_cmp fetch.expected fetch.actual
 '
 
-setup_ssh_wrapper () {
-       test_expect_success 'setup ssh wrapper' '
-               rm -f "$TRASH_DIRECTORY/ssh-wrapper$X" &&
-               cp "$GIT_BUILD_DIR/t/helper/test-fake-ssh$X" \
-                       "$TRASH_DIRECTORY/ssh-wrapper$X" &&
-               GIT_SSH="$TRASH_DIRECTORY/ssh-wrapper$X" &&
-               export GIT_SSH &&
-               export TRASH_DIRECTORY &&
-               >"$TRASH_DIRECTORY"/ssh-output
-       '
-}
+test_expect_success 'set up ssh wrapper' '
+       cp "$GIT_BUILD_DIR/t/helper/test-fake-ssh$X" \
+               "$TRASH_DIRECTORY/ssh$X" &&
+       GIT_SSH="$TRASH_DIRECTORY/ssh$X" &&
+       export GIT_SSH &&
+       export TRASH_DIRECTORY &&
+       >"$TRASH_DIRECTORY"/ssh-output
+'
 
 copy_ssh_wrapper_as () {
        rm -f "${1%$X}$X" &&
-       cp "$TRASH_DIRECTORY/ssh-wrapper$X" "${1%$X}$X" &&
+       cp "$TRASH_DIRECTORY/ssh$X" "${1%$X}$X" &&
+       test_when_finished "rm $(git rev-parse --sq-quote "${1%$X}$X")" &&
        GIT_SSH="${1%$X}$X" &&
-       export GIT_SSH
+       test_when_finished "GIT_SSH=\"\$TRASH_DIRECTORY/ssh\$X\""
 }
 
 expect_ssh () {
@@ -346,8 +344,6 @@ expect_ssh () {
        (cd "$TRASH_DIRECTORY" && test_cmp ssh-expect ssh-output)
 }
 
-setup_ssh_wrapper
-
 test_expect_success 'clone myhost:src uses ssh' '
        git clone myhost:src ssh-clone &&
        expect_ssh myhost src
@@ -364,9 +360,52 @@ test_expect_success 'bracketed hostnames are still ssh' '
        expect_ssh "-p 123" myhost src
 '
 
-test_expect_success 'uplink is not treated as putty' '
+test_expect_success 'OpenSSH variant passes -4' '
+       git clone -4 "[myhost:123]:src" ssh-ipv4-clone &&
+       expect_ssh "-4 -p 123" myhost src
+'
+
+test_expect_success 'variant can be overridden' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/putty" &&
+       git -c ssh.variant=putty clone -4 "[myhost:123]:src" ssh-putty-clone &&
+       expect_ssh "-4 -P 123" myhost src
+'
+
+test_expect_success 'variant=auto picks based on basename' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" &&
+       git -c ssh.variant=auto clone -4 "[myhost:123]:src" ssh-auto-clone &&
+       expect_ssh "-4 -P 123" myhost src
+'
+
+test_expect_success 'simple does not support -4/-6' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/simple" &&
+       test_must_fail git clone -4 "myhost:src" ssh-4-clone-simple
+'
+
+test_expect_success 'simple does not support port' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/simple" &&
+       test_must_fail git clone "[myhost:123]:src" ssh-bracket-clone-simple
+'
+
+test_expect_success 'uplink is treated as simple' '
        copy_ssh_wrapper_as "$TRASH_DIRECTORY/uplink" &&
-       git clone "[myhost:123]:src" ssh-bracket-clone-uplink &&
+       test_must_fail git clone "[myhost:123]:src" ssh-bracket-clone-uplink &&
+       git clone "myhost:src" ssh-clone-uplink &&
+       expect_ssh myhost src
+'
+
+test_expect_success 'OpenSSH-like uplink is treated as ssh' '
+       write_script "$TRASH_DIRECTORY/uplink" <<-EOF &&
+       if test "\$1" = "-G"
+       then
+               exit 0
+       fi &&
+       exec "\$TRASH_DIRECTORY/ssh$X" "\$@"
+       EOF
+       test_when_finished "rm -f \"\$TRASH_DIRECTORY/uplink\"" &&
+       GIT_SSH="$TRASH_DIRECTORY/uplink" &&
+       test_when_finished "GIT_SSH=\"\$TRASH_DIRECTORY/ssh\$X\"" &&
+       git clone "[myhost:123]:src" ssh-bracket-clone-sshlike-uplink &&
        expect_ssh "-p 123" myhost src
 '
 
@@ -418,12 +457,14 @@ test_expect_success 'ssh.variant overrides plink detection' '
 '
 
 test_expect_success 'GIT_SSH_VARIANT overrides plink detection to plink' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" &&
        GIT_SSH_VARIANT=plink \
        git clone "[myhost:123]:src" ssh-bracket-clone-variant-3 &&
        expect_ssh "-P 123" myhost src
 '
 
 test_expect_success 'GIT_SSH_VARIANT overrides plink to tortoiseplink' '
+       copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" &&
        GIT_SSH_VARIANT=tortoiseplink \
        git clone "[myhost:123]:src" ssh-bracket-clone-variant-4 &&
        expect_ssh "-batch -P 123" myhost src
@@ -435,9 +476,6 @@ test_expect_success 'clean failure on broken quoting' '
                git clone "[myhost:123]:src" sq-failure
 '
 
-# Reset the GIT_SSH environment variable for clone tests.
-setup_ssh_wrapper
-
 counter=0
 # $1 url
 # $2 none|host
index d5af7581297d6167b82d029a015e367c057ff7af..13b5e5eb9b9fa2aa96e0496eb9fa3e7049fdfb51 100755 (executable)
@@ -11,7 +11,9 @@ test_expect_success 'setup ssh wrapper' '
        git upload-pack "$TRASH_DIRECTORY"
        EOF
        GIT_SSH="$TRASH_DIRECTORY/ssh-wrapper" &&
+       GIT_SSH_VARIANT=ssh &&
        export GIT_SSH &&
+       export GIT_SSH_VARIANT &&
        export TRASH_DIRECTORY
 '
 
diff --git a/t/t5700-protocol-v1.sh b/t/t5700-protocol-v1.sh
new file mode 100755 (executable)
index 0000000..ba86a44
--- /dev/null
@@ -0,0 +1,294 @@
+#!/bin/sh
+
+test_description='test git wire-protocol transition'
+
+TEST_NO_CREATE_REPO=1
+
+. ./test-lib.sh
+
+# Test protocol v1 with 'git://' transport
+#
+. "$TEST_DIRECTORY"/lib-git-daemon.sh
+start_git_daemon --export-all --enable=receive-pack
+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent
+
+test_expect_success 'create repo to be served by git-daemon' '
+       git init "$daemon_parent" &&
+       test_commit -C "$daemon_parent" one
+'
+
+test_expect_success 'clone with git:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -c protocol.version=1 \
+               clone "$GIT_DAEMON_URL/parent" daemon_child 2>log &&
+
+       git -C daemon_child log -1 --format=%s >actual &&
+       git -C "$daemon_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Client requested to use protocol v1
+       grep "clone> .*\\\0\\\0version=1\\\0$" log &&
+       # Server responded using protocol v1
+       grep "clone< version 1" log
+'
+
+test_expect_success 'fetch with git:// using protocol v1' '
+       test_commit -C "$daemon_parent" two &&
+
+       GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \
+               fetch 2>log &&
+
+       git -C daemon_child log -1 --format=%s origin/master >actual &&
+       git -C "$daemon_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Client requested to use protocol v1
+       grep "fetch> .*\\\0\\\0version=1\\\0$" log &&
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'pull with git:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \
+               pull 2>log &&
+
+       git -C daemon_child log -1 --format=%s >actual &&
+       git -C "$daemon_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Client requested to use protocol v1
+       grep "fetch> .*\\\0\\\0version=1\\\0$" log &&
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'push with git:// using protocol v1' '
+       test_commit -C daemon_child three &&
+
+       # Push to another branch, as the target repository has the
+       # master branch checked out and we cannot push into it.
+       GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \
+               push origin HEAD:client_branch 2>log &&
+
+       git -C daemon_child log -1 --format=%s >actual &&
+       git -C "$daemon_parent" log -1 --format=%s client_branch >expect &&
+       test_cmp expect actual &&
+
+       # Client requested to use protocol v1
+       grep "push> .*\\\0\\\0version=1\\\0$" log &&
+       # Server responded using protocol v1
+       grep "push< version 1" log
+'
+
+stop_git_daemon
+
+# Test protocol v1 with 'file://' transport
+#
+test_expect_success 'create repo to be served by file:// transport' '
+       git init file_parent &&
+       test_commit -C file_parent one
+'
+
+test_expect_success 'clone with file:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -c protocol.version=1 \
+               clone "file://$(pwd)/file_parent" file_child 2>log &&
+
+       git -C file_child log -1 --format=%s >actual &&
+       git -C file_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "clone< version 1" log
+'
+
+test_expect_success 'fetch with file:// using protocol v1' '
+       test_commit -C file_parent two &&
+
+       GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \
+               fetch 2>log &&
+
+       git -C file_child log -1 --format=%s origin/master >actual &&
+       git -C file_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'pull with file:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \
+               pull 2>log &&
+
+       git -C file_child log -1 --format=%s >actual &&
+       git -C file_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'push with file:// using protocol v1' '
+       test_commit -C file_child three &&
+
+       # Push to another branch, as the target repository has the
+       # master branch checked out and we cannot push into it.
+       GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \
+               push origin HEAD:client_branch 2>log &&
+
+       git -C file_child log -1 --format=%s >actual &&
+       git -C file_parent log -1 --format=%s client_branch >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "push< version 1" log
+'
+
+# Test protocol v1 with 'ssh://' transport
+#
+test_expect_success 'setup ssh wrapper' '
+       GIT_SSH="$GIT_BUILD_DIR/t/helper/test-fake-ssh" &&
+       export GIT_SSH &&
+       GIT_SSH_VARIANT=ssh &&
+       export GIT_SSH_VARIANT &&
+       export TRASH_DIRECTORY &&
+       >"$TRASH_DIRECTORY"/ssh-output
+'
+
+expect_ssh () {
+       test_when_finished '(cd "$TRASH_DIRECTORY" && rm -f ssh-expect && >ssh-output)' &&
+       echo "ssh: -o SendEnv=GIT_PROTOCOL myhost $1 '$PWD/ssh_parent'" >"$TRASH_DIRECTORY/ssh-expect" &&
+       (cd "$TRASH_DIRECTORY" && test_cmp ssh-expect ssh-output)
+}
+
+test_expect_success 'create repo to be served by ssh:// transport' '
+       git init ssh_parent &&
+       test_commit -C ssh_parent one
+'
+
+test_expect_success 'clone with ssh:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -c protocol.version=1 \
+               clone "ssh://myhost:$(pwd)/ssh_parent" ssh_child 2>log &&
+       expect_ssh git-upload-pack &&
+
+       git -C ssh_child log -1 --format=%s >actual &&
+       git -C ssh_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "clone< version 1" log
+'
+
+test_expect_success 'fetch with ssh:// using protocol v1' '
+       test_commit -C ssh_parent two &&
+
+       GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \
+               fetch 2>log &&
+       expect_ssh git-upload-pack &&
+
+       git -C ssh_child log -1 --format=%s origin/master >actual &&
+       git -C ssh_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'pull with ssh:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \
+               pull 2>log &&
+       expect_ssh git-upload-pack &&
+
+       git -C ssh_child log -1 --format=%s >actual &&
+       git -C ssh_parent log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "fetch< version 1" log
+'
+
+test_expect_success 'push with ssh:// using protocol v1' '
+       test_commit -C ssh_child three &&
+
+       # Push to another branch, as the target repository has the
+       # master branch checked out and we cannot push into it.
+       GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \
+               push origin HEAD:client_branch 2>log &&
+       expect_ssh git-receive-pack &&
+
+       git -C ssh_child log -1 --format=%s >actual &&
+       git -C ssh_parent log -1 --format=%s client_branch >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "push< version 1" log
+'
+
+# Test protocol v1 with 'http://' transport
+#
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+test_expect_success 'create repo to be served by http:// transport' '
+       git init "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+       git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config http.receivepack true &&
+       test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" one
+'
+
+test_expect_success 'clone with http:// using protocol v1' '
+       GIT_TRACE_PACKET=1 GIT_TRACE_CURL=1 git -c protocol.version=1 \
+               clone "$HTTPD_URL/smart/http_parent" http_child 2>log &&
+
+       git -C http_child log -1 --format=%s >actual &&
+       git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Client requested to use protocol v1
+       grep "Git-Protocol: version=1" log &&
+       # Server responded using protocol v1
+       grep "git< version 1" log
+'
+
+test_expect_success 'fetch with http:// using protocol v1' '
+       test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" two &&
+
+       GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \
+               fetch 2>log &&
+
+       git -C http_child log -1 --format=%s origin/master >actual &&
+       git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "git< version 1" log
+'
+
+test_expect_success 'pull with http:// using protocol v1' '
+       GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \
+               pull 2>log &&
+
+       git -C http_child log -1 --format=%s >actual &&
+       git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "git< version 1" log
+'
+
+test_expect_success 'push with http:// using protocol v1' '
+       test_commit -C http_child three &&
+
+       # Push to another branch, as the target repository has the
+       # master branch checked out and we cannot push into it.
+       GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \
+               push origin HEAD:client_branch && #2>log &&
+
+       git -C http_child log -1 --format=%s >actual &&
+       git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s client_branch >expect &&
+       test_cmp expect actual &&
+
+       # Server responded using protocol v1
+       grep "git< version 1" log
+'
+
+stop_httpd
+
+test_done
index 865168ec6a7be7fc7778419f2dd1066e9c80c1bc..f5f46a95b4be9bca4ca30e70cbe75051a191c96a 100755 (executable)
@@ -214,6 +214,44 @@ test_expect_success TTY 'git tag as alias respects pager.tag with -l' '
        ! test -e paginated.out
 '
 
+test_expect_success TTY 'git branch defaults to paging' '
+       rm -f paginated.out &&
+       test_terminal git branch &&
+       test -e paginated.out
+'
+
+test_expect_success TTY 'git branch respects pager.branch' '
+       rm -f paginated.out &&
+       test_terminal git -c pager.branch=false branch &&
+       ! test -e paginated.out
+'
+
+test_expect_success TTY 'git branch respects --no-pager' '
+       rm -f paginated.out &&
+       test_terminal git --no-pager branch &&
+       ! test -e paginated.out
+'
+
+test_expect_success TTY 'git branch --edit-description ignores pager.branch' '
+       rm -f paginated.out editor.used &&
+       write_script editor <<-\EOF &&
+               echo "New description" >"$1"
+               touch editor.used
+       EOF
+       EDITOR=./editor test_terminal git -c pager.branch branch --edit-description &&
+       ! test -e paginated.out &&
+       test -e editor.used
+'
+
+test_expect_success TTY 'git branch --set-upstream-to ignores pager.branch' '
+       rm -f paginated.out &&
+       git branch other &&
+       test_when_finished "git branch -D other" &&
+       test_terminal git -c pager.branch branch --set-upstream-to=other &&
+       test_when_finished "git branch --unset-upstream" &&
+       ! test -e paginated.out
+'
+
 # A colored commit log will begin with an appropriate ANSI escape
 # for the first color; the text "commit" comes later.
 colorful() {
index 2a6679c2f596fb13a34786a4aa4b954e61c61113..c02ca735b9e31952811bfbb19048081dc546bd8e 100755 (executable)
@@ -60,6 +60,18 @@ test_expect_success setup '
                echo " line with leading space3"
                echo "line without leading space2"
        } >space &&
+       cat >hello.ps1 <<-\EOF &&
+       # No-op.
+       function dummy() {}
+
+       # Say hello.
+       function hello() {
+         echo "Hello world."
+       } # hello
+
+       # Still a no-op.
+       function dummy() {}
+       EOF
        git add . &&
        test_tick &&
        git commit -m initial
@@ -766,18 +778,27 @@ test_expect_success 'grep -W shows no trailing empty lines' '
        test_cmp expected actual
 '
 
-cat >expected <<EOF
-hello.c=       printf("Hello world.\n");
-hello.c:       return 0;
-hello.c-       /* char ?? */
-EOF
-
 test_expect_success 'grep -W with userdiff' '
        test_when_finished "rm -f .gitattributes" &&
-       git config diff.custom.xfuncname "(printf.*|})$" &&
-       echo "hello.c diff=custom" >.gitattributes &&
-       git grep -W return >actual &&
-       test_cmp expected actual
+       git config diff.custom.xfuncname "^function .*$" &&
+       echo "hello.ps1 diff=custom" >.gitattributes &&
+       git grep -W echo >function-context-userdiff-actual
+'
+
+test_expect_success ' includes preceding comment' '
+       grep "# Say hello" function-context-userdiff-actual
+'
+
+test_expect_success ' includes function line' '
+       grep "=function hello" function-context-userdiff-actual
+'
+
+test_expect_success ' includes matching line' '
+       grep ":  echo" function-context-userdiff-actual
+'
+
+test_expect_success ' includes last line of the function' '
+       grep "} # hello" function-context-userdiff-actual
 '
 
 for threads in $(test_seq 0 10)
index 6d5f3c0d39a886f5010115927d71f205a96d0e04..d5de18127c63a9d017578e0969743392d39526d4 100644 (file)
@@ -18,6 +18,7 @@
 #include "parse-options.h"
 #include "argv-array.h"
 #include "prio-queue.h"
+#include "protocol.h"
 
 static const char * const upload_pack_usage[] = {
        N_("git upload-pack [<options>] <dir>"),
@@ -1066,6 +1067,23 @@ int cmd_main(int argc, const char **argv)
                die("'%s' does not appear to be a git repository", dir);
 
        git_config(upload_pack_config, NULL);
-       upload_pack();
+
+       switch (determine_protocol_version_server()) {
+       case protocol_v1:
+               /*
+                * v1 is just the original protocol with a version string,
+                * so just fall through after writing the version string.
+                */
+               if (advertise_refs || !stateless_rpc)
+                       packet_write_fmt(1, "version 1\n");
+
+               /* fallthrough */
+       case protocol_v0:
+               upload_pack();
+               break;
+       case protocol_unknown_version:
+               BUG("unknown protocol version");
+       }
+
        return 0;
 }
index 785ecb0899454f55dc160a132b5cf932ec382f09..915591f7d4797ace61761195933ab170c6b70eff 100644 (file)
 extern "C" {
 #endif /* #ifdef __cplusplus */
 
+/* xpparm_t.flags */
+#define XDF_NEED_MINIMAL (1 << 0)
+
+#define XDF_IGNORE_WHITESPACE (1 << 1)
+#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 2)
+#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 3)
+#define XDF_IGNORE_CR_AT_EOL (1 << 4)
+#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | \
+                             XDF_IGNORE_WHITESPACE_CHANGE | \
+                             XDF_IGNORE_WHITESPACE_AT_EOL | \
+                             XDF_IGNORE_CR_AT_EOL)
 
-#define XDF_NEED_MINIMAL (1 << 1)
-#define XDF_IGNORE_WHITESPACE (1 << 2)
-#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3)
-#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4)
-#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL)
+#define XDF_IGNORE_BLANK_LINES (1 << 7)
 
-#define XDF_PATIENCE_DIFF (1 << 5)
-#define XDF_HISTOGRAM_DIFF (1 << 6)
+#define XDF_PATIENCE_DIFF (1 << 14)
+#define XDF_HISTOGRAM_DIFF (1 << 15)
 #define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF)
 #define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK)
 
-#define XDF_IGNORE_BLANK_LINES (1 << 7)
-
-#define XDF_INDENT_HEURISTIC (1 << 8)
+#define XDF_INDENT_HEURISTIC (1 << 23)
 
+/* xdemitconf_t.flags */
 #define XDL_EMIT_FUNCNAMES (1 << 0)
 #define XDL_EMIT_FUNCCONTEXT (1 << 2)
 
index 6881445e4afc3482ba008ebbdab81b51167863fc..7778dc2b190f84fe2ab1e1539756a7e1f4254c3b 100644 (file)
@@ -121,6 +121,12 @@ static long match_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri,
        return xecfg->find_func(rec, len, buf, sz, xecfg->find_func_priv);
 }
 
+static int is_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri)
+{
+       char dummy[1];
+       return match_func_rec(xdf, xecfg, ri, dummy, sizeof(dummy)) >= 0;
+}
+
 struct func_line {
        long len;
        char buf[80];
@@ -178,7 +184,6 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
 
                        /* Appended chunk? */
                        if (i1 >= xe->xdf1.nrec) {
-                               char dummy[1];
                                long i2 = xch->i2;
 
                                /*
@@ -186,8 +191,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                                 * a whole function was added.
                                 */
                                while (i2 < xe->xdf2.nrec) {
-                                       if (match_func_rec(&xe->xdf2, xecfg, i2,
-                                               dummy, sizeof(dummy)) >= 0)
+                                       if (is_func_rec(&xe->xdf2, xecfg, i2))
                                                goto post_context_calculation;
                                        i2++;
                                }
@@ -200,6 +204,9 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                        }
 
                        fs1 = get_func_line(xe, xecfg, NULL, i1, -1);
+                       while (fs1 > 0 && !is_empty_rec(&xe->xdf1, fs1 - 1) &&
+                              !is_func_rec(&xe->xdf1, xecfg, fs1 - 1))
+                               fs1--;
                        if (fs1 < 0)
                                fs1 = 0;
                        if (fs1 < s1) {
index 088001db4f45af338696ea3112559b7bc729a5d3..88e5995535467475216de82b1eeb744e744f3b4d 100644 (file)
@@ -156,6 +156,24 @@ int xdl_blankline(const char *line, long size, long flags)
        return (i == size);
 }
 
+/*
+ * Have we eaten everything on the line, except for an optional
+ * CR at the very end?
+ */
+static int ends_with_optional_cr(const char *l, long s, long i)
+{
+       int complete = s && l[s-1] == '\n';
+
+       if (complete)
+               s--;
+       if (s == i)
+               return 1;
+       /* do not ignore CR at the end of an incomplete line */
+       if (complete && s == i + 1 && l[i] == '\r')
+               return 1;
+       return 0;
+}
+
 int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
 {
        int i1, i2;
@@ -170,7 +188,8 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
 
        /*
         * -w matches everything that matches with -b, and -b in turn
-        * matches everything that matches with --ignore-space-at-eol.
+        * matches everything that matches with --ignore-space-at-eol,
+        * which in turn matches everything that matches with --ignore-cr-at-eol.
         *
         * Each flavor of ignoring needs different logic to skip whitespaces
         * while we have both sides to compare.
@@ -204,6 +223,14 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
                        i1++;
                        i2++;
                }
+       } else if (flags & XDF_IGNORE_CR_AT_EOL) {
+               /* Find the first difference and see how the line ends */
+               while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) {
+                       i1++;
+                       i2++;
+               }
+               return (ends_with_optional_cr(l1, s1, i1) &&
+                       ends_with_optional_cr(l2, s2, i2));
        }
 
        /*
@@ -230,9 +257,16 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data,
                char const *top, long flags) {
        unsigned long ha = 5381;
        char const *ptr = *data;
+       int cr_at_eol_only = (flags & XDF_WHITESPACE_FLAGS) == XDF_IGNORE_CR_AT_EOL;
 
        for (; ptr < top && *ptr != '\n'; ptr++) {
-               if (XDL_ISSPACE(*ptr)) {
+               if (cr_at_eol_only) {
+                       /* do not ignore CR at the end of an incomplete line */
+                       if (*ptr == '\r' &&
+                           (ptr + 1 < top && ptr[1] == '\n'))
+                               continue;
+               }
+               else if (XDL_ISSPACE(*ptr)) {
                        const char *ptr2 = ptr;
                        int at_eol;
                        while (ptr + 1 < top && XDL_ISSPACE(ptr[1])