]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'en/clean-cleanups'
authorJunio C Hamano <gitster@pobox.com>
Thu, 25 Jun 2020 19:27:45 +0000 (12:27 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 25 Jun 2020 19:27:45 +0000 (12:27 -0700)
Code clean-up of "git clean" resulted in a fix of recent
performance regression.

* en/clean-cleanups:
  clean: optimize and document cases where we recurse into subdirectories
  clean: consolidate handling of ignored parameters
  dir, clean: avoid disallowed behavior
  dir: fix a few confusing comments

104 files changed:
.github/CONTRIBUTING.md
Documentation/CodingGuidelines
Documentation/Makefile
Documentation/RelNotes/2.28.0.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/config/diff.txt
Documentation/config/feature.txt
Documentation/config/protocol.txt
Documentation/diff-options.txt
Documentation/git-bugreport.txt
Documentation/git-commit-graph.txt
Documentation/git-fast-import.txt
Documentation/git-sparse-checkout.txt
Documentation/git-worktree.txt
Documentation/git.txt
Documentation/gitremote-helpers.txt
Documentation/technical/http-protocol.txt
Documentation/technical/pack-protocol.txt
Documentation/technical/protocol-v2.txt
Documentation/technical/reftable.txt [new file with mode: 0644]
GIT-VERSION-GEN
Makefile
RelNotes
add-patch.c
bloom.c
bloom.h
bugreport.c
builtin/bisect--helper.c
builtin/checkout.c
builtin/clone.c
builtin/commit-graph.c
builtin/fetch-pack.c
builtin/fetch.c
builtin/merge.c
builtin/sparse-checkout.c
builtin/worktree.c
commit-graph.c
commit-graph.h
compat/vcbuild/scripts/clink.pl
connect.c
connect.h
contrib/completion/git-completion.bash
diff.c
dir.c
fast-import.c
fetch-pack.c
fsck.c
fuzz-commit-graph.c
git-add--interactive.perl
git-p4.py
help.c
http.c
http.h
imap-send.c
line-log.c
line-log.h
pkt-line.c
pkt-line.h
protocol.c
refs.h
refs/refs-internal.h
remote-curl.c
remote.h
revision.c
serve.c
t/README
t/helper/test-pkt-line.c
t/helper/test-regex.c
t/lib-httpd.sh
t/lib-httpd/apache.conf
t/lib-httpd/incomplete-body-upload-pack-v2-http.sh [new file with mode: 0644]
t/lib-httpd/incomplete-length-upload-pack-v2-http.sh [new file with mode: 0644]
t/t0002-gitfile.sh
t/t1091-sparse-checkout-builtin.sh
t/t1400-update-ref.sh
t/t1450-fsck.sh
t/t1506-rev-parse-diagnosis.sh
t/t2018-checkout-branch.sh
t/t2027-checkout-track.sh [new file with mode: 0755]
t/t2060-switch.sh
t/t2401-worktree-prune.sh
t/t2403-worktree-move.sh
t/t3701-add-interactive.sh
t/t4014-format-patch.sh
t/t4045-diff-relative.sh
t/t4210-log-i18n.sh
t/t4211-line-log.sh
t/t5318-commit-graph.sh
t/t5541-http-push-smart.sh
t/t5551-http-fetch-smart.sh
t/t5581-http-curl-verbose.sh
t/t5608-clone-2gb.sh
t/t5702-protocol-v2.sh
t/t6030-bisect-porcelain.sh
t/t6050-replace.sh
t/t6132-pathspec-exclude.sh
t/t9020-remote-svn.sh
t/t9300-fast-import.sh
t/t9902-completion.sh
t/test-lib.sh
trace.c
trace.h
transport.c
upload-pack.c

index e7b4e2f3c204c2c94c60222abbc702bd7d72de39..c8755e38de81caf60768c0309b5348f03a120fc1 100644 (file)
@@ -16,4 +16,7 @@ If you prefer video, then [this talk](https://www.youtube.com/watch?v=Q7i_qQW__q
 might be useful to you as the presenter walks you through the contribution
 process by example.
 
+Or, you can follow the ["My First Contribution"](https://git-scm.com/docs/MyFirstContribution)
+tutorial for another example of the contribution process.
+
 Your friendly Git community!
index 227f46ae403ea85a1bea17892aa4b038820b5f9b..45465bc0c98f5d88cfe1ade092d29b5dc32c1e23 100644 (file)
@@ -489,16 +489,11 @@ For Python scripts:
 
  - We follow PEP-8 (http://www.python.org/dev/peps/pep-0008/).
 
- - As a minimum, we aim to be compatible with Python 2.6 and 2.7.
+ - As a minimum, we aim to be compatible with Python 2.7.
 
  - Where required libraries do not restrict us to Python 2, we try to
    also be compatible with Python 3.1 and later.
 
- - When you must differentiate between Unicode literals and byte string
-   literals, it is OK to use the 'b' prefix.  Even though the Python
-   documentation for version 2.6 does not mention this prefix, it has
-   been supported since version 2.6.0.
-
 Error Messages
 
  - Do not end error messages with a full stop.
index 15d9d04f3164b939ec91695f4ec1c3b2fd6c00b3..ecd0b340b1c5ad0f94cabe257606016101b81c62 100644 (file)
@@ -93,6 +93,7 @@ TECH_DOCS += technical/protocol-capabilities
 TECH_DOCS += technical/protocol-common
 TECH_DOCS += technical/protocol-v2
 TECH_DOCS += technical/racy-git
+TECH_DOCS += technical/reftable
 TECH_DOCS += technical/send-pack-pipeline
 TECH_DOCS += technical/shallow
 TECH_DOCS += technical/signature-format
diff --git a/Documentation/RelNotes/2.28.0.txt b/Documentation/RelNotes/2.28.0.txt
new file mode 100644 (file)
index 0000000..55903d8
--- /dev/null
@@ -0,0 +1,118 @@
+Git 2.28 Release Notes
+======================
+
+Updates since v2.27
+-------------------
+
+Backward compatibility notes
+
+ * "feature.experimental" configuration variable is to let volunteers
+   easily opt into a set of newer features, which use of the v2
+   transport protocol is now a part of.
+
+
+UI, Workflows & Features
+
+ * The commands in the "diff" family learned to honor "diff.relative"
+   configuration variable.
+
+ * The check in "git fsck" to ensure that the tree objects are sorted
+   still had corner cases it missed unsorted entries.
+
+ * The interface to redact sensitive information in the trace output
+   has been simplified.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * Code optimization for a common case.
+   (merge 8777616e4d an/merge-single-strategy-optim later to maint).
+
+ * We've adopted a convention that any on-stack structure can be
+   initialized to have zero values in all fields with "= { 0 }",
+   even when the first field happens to be a pointer, but sparse
+   complained that a null pointer should be spelled NULL for a long
+   time.  Start using -Wno-universal-initializer option to squelch
+   it (the latest sparse has it on by default).
+
+ * "git log -L..." now takes advantage of the "which paths are touched
+   by this commit?" info stored in the commit-graph system.
+
+ * As FreeBSD is not the only platform whose regexp library reports
+   a REG_ILLSEQ error when fed invalid UTF-8, add logic to detect that
+   automatically and skip the affected tests.
+
+ * "git bugreport" learns to report what shell is in use.
+
+ * Support for GIT_CURL_VERBOSE has been rewritten in terms of
+   GIT_TRACE_CURL.
+
+ * Preliminary clean-ups around refs API, plus file format
+   specification documentation for the reftable backend.
+
+ * Workaround breakage in MSVC build, where "curl-config --cflags"
+   gives settings appropriate for GCC build.
+
+
+Fixes since v2.27
+-----------------
+
+ * The "--prepare-p4-only" option of "git p4" is supposed to stop
+   after replaying one changeset, but kept going (by mistake?)
+
+ * The error message from "git checkout -b foo -t bar baz" was
+   confusing.
+
+ * Some repositories in the wild have commits that record nonsense
+   committer timezone (e.g. rails.git); "git fast-import" learned an
+   option to pass these nonsense timestamps intact to allow recreating
+   existing repositories as-is.
+   (merge d42a2fb72f en/fast-import-looser-date later to maint).
+
+ * The command line completion script (in contrib/) tried to complete
+   "git stash -p" as if it were "git stash push -p", but it was too
+   aggressive and also affected "git stash show -p", which has been
+   corrected.
+   (merge fffd0cf520 vs/complete-stash-show-p-fix later to maint).
+
+ * On-the-wire protocol v2 easily falls into a deadlock between the
+   remote-curl helper and the fetch-pack process when the server side
+   prematurely throws an error and disconnects.  The communication has
+   been updated to make it more robust.
+
+ * "git checkout -p" did not handle a newly added path at all.
+   (merge 2c8bd8471a js/checkout-p-new-file later to maint).
+
+ * The code to parse "git bisect start" command line was lax in
+   validating the arguments.
+   (merge 4d9005ff5d cb/bisect-helper-parser-fix later to maint).
+
+ * Reduce memory usage during "diff --quiet" in a worktree with too
+   many stat-unmatched paths.
+   (merge d2d7fbe129 jk/diff-memuse-optim-with-stat-unmatch later to maint).
+
+ * The reflog entries for "git clone" and "git fetch" did not
+   anonymize the URL they operated on.
+   (merge 46da295a77 js/reflog-anonymize-for-clone-and-fetch later to maint).
+
+ * The behaviour of "sparse-checkout" in the state "git clone
+   --no-checkout" left was changed accidentally in 2.27, which has
+   been corrected.
+
+ * Use of negative pathspec, while collecting paths including
+   untracked ones in the working tree, was broken.
+
+ * The same worktree directory must be registered only once, but
+   "git worktree move" allowed this invariant to be violated, which
+   has been corrected.
+   (merge 810382ed37 es/worktree-duplicate-paths later to maint).
+
+ * The effect of sparse checkout settings on submodules is documented.
+   (merge e7d7c73249 en/sparse-with-submodule-doc later to maint).
+
+ * Other code cleanup, docfix, build fix, etc.
+   (merge 2c31a7aa44 jx/pkt-line-doc-count-fix later to maint).
+   (merge d63ae31962 cb/t5608-cleanup later to maint).
+   (merge 788db145c7 dl/t-readme-spell-git-correctly later to maint).
+   (merge 45a87a83bb dl/python-2.7-is-the-floor-version later to maint).
+   (merge b75a219904 es/advertise-contribution-doc later to maint).
index 4515cab5193ddf354a461aafb1b68dcc1ef2932e..ecf9438cf08069bc66150633e79bfb2bead1853a 100644 (file)
@@ -3,8 +3,9 @@ Submitting Patches
 
 == Guidelines
 
-Here are some guidelines for people who want to contribute their code
-to this software.
+Here are some guidelines for people who want to contribute their code to this
+software. There is also a link:MyFirstContribution.html[step-by-step tutorial]
+available which covers many of these same guidelines.
 
 [[base-branch]]
 === Decide what to base your work on.
index ff09f1cf737c062898ac40555402de097d360eb9..c3ae136eba6de181ac07b14f52c69bbbec28bc95 100644 (file)
@@ -105,6 +105,10 @@ diff.mnemonicPrefix::
 diff.noprefix::
        If set, 'git diff' does not show any source or destination prefix.
 
+diff.relative::
+       If set to 'true', 'git diff' does not show changes outside of the directory
+       and show pathnames relative to the current directory.
+
 diff.orderFile::
        File indicating how to order files within a diff.
        See the '-O' option to linkgit:git-diff[1] for details.
index 4e3a5c0cebc90d1222de23d89db10d6eebfcc4d4..28c33602d527fa152b2702dab8a07ce3a5373c82 100644 (file)
@@ -22,6 +22,10 @@ existing commit-graph file(s). Occasionally, these files will merge and the
 write may take longer. Having an updated commit-graph file helps performance
 of many Git commands, including `git merge-base`, `git push -f`, and
 `git log --graph`.
++
+* `protocol.version=2` speeds up fetches from repositories with many refs by
+allowing the client to specify which refs to list before the server lists
+them.
 
 feature.manyFiles::
        Enable config options that optimize for repos with many files in the
index 0b40141613e3d3dbc50f727c534229d5ee1da3cc..c46e9b3d00a97e2290f717b72ef9b1811dce57b7 100644 (file)
@@ -48,7 +48,8 @@ protocol.version::
        If set, clients will attempt to communicate with a server
        using the specified protocol version.  If the server does
        not support it, communication falls back to version 0.
-       If unset, the default is `0`.
+       If unset, the default is `0`, unless `feature.experimental`
+       is enabled, in which case the default is `2`.
        Supported versions:
 +
 --
index bb31f0c42b3f8a7b26f6eb01e7e1ad7c0b0fa008..7987d72b0212e1247ff5ea14b553d4a322c1f5f5 100644 (file)
@@ -643,15 +643,18 @@ ifndef::git-format-patch[]
 -R::
        Swap two inputs; that is, show differences from index or
        on-disk file to tree contents.
+endif::git-format-patch[]
 
 --relative[=<path>]::
+--no-relative::
        When run from a subdirectory of the project, it can be
        told to exclude changes outside the directory and show
        pathnames relative to it with this option.  When you are
        not in a subdirectory (e.g. in a bare repository), you
        can name which subdirectory to make the output relative
        to by giving a <path> as an argument.
-endif::git-format-patch[]
+       `--no-relative` can be used to countermand both `diff.relative` config
+       option and previous `--relative`.
 
 -a::
 --text::
index 9edad66a63145b848bb72f5cd05343e8cc98687a..66e88c2e312b10afeb44d3f4a861ed92c21e925e 100644 (file)
@@ -29,6 +29,7 @@ The following information is captured automatically:
  - uname sysname, release, version, and machine strings
  - Compiler-specific info string
  - A list of enabled hooks
+ - $SHELL
 
 This tool is invoked via the typical Git setup process, which means that in some
 cases, it might not be able to launch - for example, if a relevant config file
index a3d996787ba4c9e6f47a091d2136320f41e75609..8ca1764d3dcab4f37abe9f5e92dddb0d822b47f4 100644 (file)
@@ -47,8 +47,10 @@ with `--stdin-commits` or `--reachable`.)
 +
 With the `--stdin-commits` option, generate the new commit graph by
 walking commits starting at the commits specified in stdin as a list
-of OIDs in hex, one OID per line. (Cannot be combined with
-`--stdin-packs` or `--reachable`.)
+of OIDs in hex, one OID per line. OIDs that resolve to non-commits
+(either directly, or by peeling tags) are silently ignored. OIDs that
+are malformed, or do not exist generate an error. (Cannot be combined
+with `--stdin-packs` or `--reachable`.)
 +
 With the `--reachable` option, generate the new commit graph by walking
 commits starting at all refs. (Cannot be combined with `--stdin-commits`
index 77c6b3d0019de2d37be64f03b6f49fa93c3ce4ad..7d9aad2a7e1b33399b611b68e995ee2d368cd02d 100644 (file)
@@ -293,7 +293,14 @@ by users who are located in the same location and time zone.  In this
 case a reasonable offset from UTC could be assumed.
 +
 Unlike the `rfc2822` format, this format is very strict.  Any
-variation in formatting will cause fast-import to reject the value.
+variation in formatting will cause fast-import to reject the value,
+and some sanity checks on the numeric values may also be performed.
+
+`raw-permissive`::
+       This is the same as `raw` except that no sanity checks on
+       the numeric epoch and local offset are performed.  This can
+       be useful when trying to filter or import an existing history
+       with e.g. bogus timezone values.
 
 `rfc2822`::
        This is the standard email format as described by RFC 2822.
index 7c8943af7af3f61ebcc32c3a50b03de01024abcd..a0eeaeb02ee31073a5b3989cfbc00e3ab731c3e3 100644 (file)
@@ -200,10 +200,32 @@ directory.
 SUBMODULES
 ----------
 
-If your repository contains one or more submodules, then those submodules will
-appear based on which you initialized with the `git submodule` command. If
-your sparse-checkout patterns exclude an initialized submodule, then that
-submodule will still appear in your working directory.
+If your repository contains one or more submodules, then submodules
+are populated based on interactions with the `git submodule` command.
+Specifically, `git submodule init -- <path>` will ensure the submodule
+at `<path>` is present, while `git submodule deinit [-f] -- <path>`
+will remove the files for the submodule at `<path>` (including any
+untracked files, uncommitted changes, and unpushed history).  Similar
+to how sparse-checkout removes files from the working tree but still
+leaves entries in the index, deinitialized submodules are removed from
+the working directory but still have an entry in the index.
+
+Since submodules may have unpushed changes or untracked files,
+removing them could result in data loss.  Thus, changing sparse
+inclusion/exclusion rules will not cause an already checked out
+submodule to be removed from the working copy.  Said another way, just
+as `checkout` will not cause submodules to be automatically removed or
+initialized even when switching between branches that remove or add
+submodules, using `sparse-checkout` to reduce or expand the scope of
+"interesting" files will not cause submodules to be automatically
+deinitialized or initialized either.
+
+Further, the above facts mean that there are multiple reasons that
+"tracked" files might not be present in the working copy: sparsity
+pattern application from sparse-checkout, and submodule initialization
+state.  Thus, commands like `git grep` that work on tracked files in
+the working copy may return results that are limited by either or both
+of these restrictions.
 
 
 SEE ALSO
index 85d92c9761da226010d1fffe20bbbfc404680c66..4796c3c05ef7e55d883f6db03767d9953c97e6da 100644 (file)
@@ -126,7 +126,9 @@ OPTIONS
        locked working tree path, specify `--force` twice.
 +
 `move` refuses to move a locked working tree unless `--force` is specified
-twice.
+twice. If the destination is already assigned to some other working tree but is
+missing (for instance, if `<new-path>` was deleted manually), then `--force`
+allows the move to proceed; use --force twice if the destination is locked.
 +
 `remove` refuses to remove an unclean working tree unless `--force` is used.
 To remove a locked working tree, specify `--force` twice.
index 12890841c45cb38b1cae0f1149635b9f7cc385a0..3e50065198891b8f889d7905d8e0573ca791f02f 100644 (file)
@@ -721,8 +721,6 @@ of clones and fetches.
        Enables a curl full trace dump of all incoming and outgoing data,
        including descriptive information, of the git transport protocol.
        This is similar to doing curl `--trace-ascii` on the command line.
-       This option overrides setting the `GIT_CURL_VERBOSE` environment
-       variable.
        See `GIT_TRACE` for available trace output options.
 
 `GIT_TRACE_CURL_NO_DATA`::
@@ -777,11 +775,10 @@ for full details.
        See `GIT_TRACE2` for available trace output options and
        link:technical/api-trace2.html[Trace2 documentation] for full details.
 
-`GIT_REDACT_COOKIES`::
-       This can be set to a comma-separated list of strings. When a curl trace
-       is enabled (see `GIT_TRACE_CURL` above), whenever a "Cookies:" header
-       sent by the client is dumped, values of cookies whose key is in that
-       list (case-sensitive) are redacted.
+`GIT_TRACE_REDACT`::
+       By default, when tracing is activated, Git redacts the values of
+       cookies, the "Authorization:" header, and the "Proxy-Authorization:"
+       header. Set this variable to `0` to prevent this redaction.
 
 `GIT_LITERAL_PATHSPECS`::
        Setting this variable to `1` will cause Git to treat all
index f48a031dc346a307aa33aebc62686709a18673f6..93baeeb0295824ca90bf3ba81635e3d0b4470eb1 100644 (file)
@@ -405,7 +405,9 @@ Supported if the helper has the "connect" capability.
        trying to fall back).  After line feed terminating the positive
        (empty) response, the output of the service starts.  Messages
        (both request and response) must consist of zero or more
-       PKT-LINEs, terminating in a flush packet. The client must not
+       PKT-LINEs, terminating in a flush packet. Response messages will
+       then have a response end packet after the flush packet to
+       indicate the end of a response.  The client must not
        expect the server to store any state in between request-response
        pairs.  After the connection ends, the remote helper exits.
 +
index 9c5b6f0facbf41ce4ffa492dda6795bca229f170..51a79e63de9b77656ec225ea3e8d38289d9e4ae8 100644 (file)
@@ -216,7 +216,7 @@ smart server reply:
    S: 001e# service=git-upload-pack\n
    S: 0000
    S: 004895dcfa3633004da0049d3d0fa03f80589cbcaf31 refs/heads/maint\0multi_ack\n
-   S: 0042d049f6c27a2244e12041955e262a404c7faba355 refs/heads/master\n
+   S: 003fd049f6c27a2244e12041955e262a404c7faba355 refs/heads/master\n
    S: 003c2cb58b79488a98d2721cea644875a8dd0026b115 refs/tags/v1.0\n
    S: 003fa3c2e2402b99163d1d59756e5f207ae21cccba4c refs/tags/v1.0^{}\n
    S: 0000
index d5ce4eea8a19b96e84af2105cc52f8aebdf6164f..a4573d12ce82225c594c3290ec4ba9f65039c505 100644 (file)
@@ -96,7 +96,7 @@ Basically what the Git client is doing to connect to an 'upload-pack'
 process on the server side over the Git protocol is this:
 
    $ echo -e -n \
-     "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" |
+     "003agit-upload-pack /schacon/gitbook.git\0host=example.com\0" |
      nc -v example.com 9418
 
 
@@ -171,9 +171,9 @@ 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 "0044git-upload-pack /schacon/gitbook.git\0host=example.com\0\0version=1\0" |
+   $ echo -e -n "0045git-upload-pack /schacon/gitbook.git\0host=example.com\0\0version=1\0" |
       nc -v example.com 9418
-   000aversion 1
+   000eversion 1
    00887217a7c7e582c46cec22a130adf4b9d7d950fba0 HEAD\0multi_ack thin-pack
                side-band side-band-64k ofs-delta shallow no-progress include-tag
    00441d3fcd5ced445d1abc402225c0b8a1299641f497 refs/heads/integration
index 7e3766cafb39ccc2f37ec59f0dcd728afb58e587..3996d7089162981447cf805d07eaadc59d05d19d 100644 (file)
@@ -33,6 +33,8 @@ In protocol v2 these special packets will have the following semantics:
 
   * '0000' Flush Packet (flush-pkt) - indicates the end of a message
   * '0001' Delimiter Packet (delim-pkt) - separates sections of a message
+  * '0002' Message Packet (response-end-pkt) - indicates the end of a response
+    for stateless connections
 
 Initial Client Request
 ----------------------
diff --git a/Documentation/technical/reftable.txt b/Documentation/technical/reftable.txt
new file mode 100644 (file)
index 0000000..2951840
--- /dev/null
@@ -0,0 +1,1083 @@
+reftable
+--------
+
+Overview
+~~~~~~~~
+
+Problem statement
+^^^^^^^^^^^^^^^^^
+
+Some repositories contain a lot of references (e.g. android at 866k,
+rails at 31k). The existing packed-refs format takes up a lot of space
+(e.g. 62M), and does not scale with additional references. Lookup of a
+single reference requires linearly scanning the file.
+
+Atomic pushes modifying multiple references require copying the entire
+packed-refs file, which can be a considerable amount of data moved
+(e.g. 62M in, 62M out) for even small transactions (2 refs modified).
+
+Repositories with many loose references occupy a large number of disk
+blocks from the local file system, as each reference is its own file
+storing 41 bytes (and another file for the corresponding reflog). This
+negatively affects the number of inodes available when a large number of
+repositories are stored on the same filesystem. Readers can be penalized
+due to the larger number of syscalls required to traverse and read the
+`$GIT_DIR/refs` directory.
+
+
+Objectives
+^^^^^^^^^^
+
+* Near constant time lookup for any single reference, even when the
+repository is cold and not in process or kernel cache.
+* Near constant time verification if an object name is referred to by at least
+one reference (for allow-tip-sha1-in-want).
+* Efficient enumeration of an entire namespace, such as `refs/tags/`.
+* Support atomic push with `O(size_of_update)` operations.
+* Combine reflog storage with ref storage for small transactions.
+* Separate reflog storage for base refs and historical logs.
+
+Description
+^^^^^^^^^^^
+
+A reftable file is a portable binary file format customized for
+reference storage. References are sorted, enabling linear scans, binary
+search lookup, and range scans.
+
+Storage in the file is organized into variable sized blocks. Prefix
+compression is used within a single block to reduce disk space. Block
+size and alignment is tunable by the writer.
+
+Performance
+^^^^^^^^^^^
+
+Space used, packed-refs vs. reftable:
+
+[cols=",>,>,>,>,>",options="header",]
+|===============================================================
+|repository |packed-refs |reftable |% original |avg ref |avg obj
+|android |62.2 M |36.1 M |58.0% |33 bytes |5 bytes
+|rails |1.8 M |1.1 M |57.7% |29 bytes |4 bytes
+|git |78.7 K |48.1 K |61.0% |50 bytes |4 bytes
+|git (heads) |332 b |269 b |81.0% |33 bytes |0 bytes
+|===============================================================
+
+Scan (read 866k refs), by reference name lookup (single ref from 866k
+refs), and by SHA-1 lookup (refs with that SHA-1, from 866k refs):
+
+[cols=",>,>,>,>",options="header",]
+|=========================================================
+|format |cache |scan |by name |by SHA-1
+|packed-refs |cold |402 ms |409,660.1 usec |412,535.8 usec
+|packed-refs |hot | |6,844.6 usec |20,110.1 usec
+|reftable |cold |112 ms |33.9 usec |323.2 usec
+|reftable |hot | |20.2 usec |320.8 usec
+|=========================================================
+
+Space used for 149,932 log entries for 43,061 refs, reflog vs. reftable:
+
+[cols=",>,>",options="header",]
+|================================
+|format |size |avg entry
+|$GIT_DIR/logs |173 M |1209 bytes
+|reftable |5 M |37 bytes
+|================================
+
+Details
+~~~~~~~
+
+Peeling
+^^^^^^^
+
+References stored in a reftable are peeled, a record for an annotated
+(or signed) tag records both the tag object, and the object it refers
+to. This is analogous to storage in the packed-refs format.
+
+Reference name encoding
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Reference names are an uninterpreted sequence of bytes that must pass
+linkgit:git-check-ref-format[1] as a valid reference name.
+
+Key unicity
+^^^^^^^^^^^
+
+Each entry must have a unique key; repeated keys are disallowed.
+
+Network byte order
+^^^^^^^^^^^^^^^^^^
+
+All multi-byte, fixed width fields are in network byte order.
+
+Varint encoding
+^^^^^^^^^^^^^^^
+
+Varint encoding is identical to the ofs-delta encoding method used
+within pack files.
+
+Decoder works such as:
+
+....
+val = buf[ptr] & 0x7f
+while (buf[ptr] & 0x80) {
+  ptr++
+  val = ((val + 1) << 7) | (buf[ptr] & 0x7f)
+}
+....
+
+Ordering
+^^^^^^^^
+
+Blocks are lexicographically ordered by their first reference.
+
+Directory/file conflicts
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+The reftable format accepts both `refs/heads/foo` and
+`refs/heads/foo/bar` as distinct references.
+
+This property is useful for retaining log records in reftable, but may
+confuse versions of Git using `$GIT_DIR/refs` directory tree to maintain
+references. Users of reftable may choose to continue to reject `foo` and
+`foo/bar` type conflicts to prevent problems for peers.
+
+File format
+~~~~~~~~~~~
+
+Structure
+^^^^^^^^^
+
+A reftable file has the following high-level structure:
+
+....
+first_block {
+  header
+  first_ref_block
+}
+ref_block*
+ref_index*
+obj_block*
+obj_index*
+log_block*
+log_index*
+footer
+....
+
+A log-only file omits the `ref_block`, `ref_index`, `obj_block` and
+`obj_index` sections, containing only the file header and log block:
+
+....
+first_block {
+  header
+}
+log_block*
+log_index*
+footer
+....
+
+in a log-only file the first log block immediately follows the file
+header, without padding to block alignment.
+
+Block size
+^^^^^^^^^^
+
+The file's block size is arbitrarily determined by the writer, and does
+not have to be a power of 2. The block size must be larger than the
+longest reference name or log entry used in the repository, as
+references cannot span blocks.
+
+Powers of two that are friendly to the virtual memory system or
+filesystem (such as 4k or 8k) are recommended. Larger sizes (64k) can
+yield better compression, with a possible increased cost incurred by
+readers during access.
+
+The largest block size is `16777215` bytes (15.99 MiB).
+
+Block alignment
+^^^^^^^^^^^^^^^
+
+Writers may choose to align blocks at multiples of the block size by
+including `padding` filled with NUL bytes at the end of a block to round
+out to the chosen alignment. When alignment is used, writers must
+specify the alignment with the file header's `block_size` field.
+
+Block alignment is not required by the file format. Unaligned files must
+set `block_size = 0` in the file header, and omit `padding`. Unaligned
+files with more than one ref block must include the link:#Ref-index[ref
+index] to support fast lookup. Readers must be able to read both aligned
+and non-aligned files.
+
+Very small files (e.g. a single ref block) may omit `padding` and the ref
+index to reduce total file size.
+
+Header (version 1)
+^^^^^^^^^^^^^^^^^^
+
+A 24-byte header appears at the beginning of the file:
+
+....
+'REFT'
+uint8( version_number = 1 )
+uint24( block_size )
+uint64( min_update_index )
+uint64( max_update_index )
+....
+
+Aligned files must specify `block_size` to configure readers with the
+expected block alignment. Unaligned files must set `block_size = 0`.
+
+The `min_update_index` and `max_update_index` describe bounds for the
+`update_index` field of all log records in this file. When reftables are
+used in a stack for link:#Update-transactions[transactions], these
+fields can order the files such that the prior file's
+`max_update_index + 1` is the next file's `min_update_index`.
+
+Header (version 2)
+^^^^^^^^^^^^^^^^^^
+
+A 28-byte header appears at the beginning of the file:
+
+....
+'REFT'
+uint8( version_number = 2 )
+uint24( block_size )
+uint64( min_update_index )
+uint64( max_update_index )
+uint32( hash_id )
+....
+
+The header is identical to `version_number=1`, with the 4-byte hash ID
+("sha1" for SHA1 and "s256" for SHA-256) append to the header.
+
+For maximum backward compatibility, it is recommended to use version 1 when
+writing SHA1 reftables.
+
+First ref block
+^^^^^^^^^^^^^^^
+
+The first ref block shares the same block as the file header, and is 24
+bytes smaller than all other blocks in the file. The first block
+immediately begins after the file header, at position 24.
+
+If the first block is a log block (a log-only file), its block header
+begins immediately at position 24.
+
+Ref block format
+^^^^^^^^^^^^^^^^
+
+A ref block is written as:
+
+....
+'r'
+uint24( block_len )
+ref_record+
+uint24( restart_offset )+
+uint16( restart_count )
+
+padding?
+....
+
+Blocks begin with `block_type = 'r'` and a 3-byte `block_len` which
+encodes the number of bytes in the block up to, but not including the
+optional `padding`. This is always less than or equal to the file's
+block size. In the first ref block, `block_len` includes 24 bytes for
+the file header.
+
+The 2-byte `restart_count` stores the number of entries in the
+`restart_offset` list, which must not be empty. Readers can use
+`restart_count` to binary search between restarts before starting a
+linear scan.
+
+Exactly `restart_count` 3-byte `restart_offset` values precedes the
+`restart_count`. Offsets are relative to the start of the block and
+refer to the first byte of any `ref_record` whose name has not been
+prefix compressed. Entries in the `restart_offset` list must be sorted,
+ascending. Readers can start linear scans from any of these records.
+
+A variable number of `ref_record` fill the middle of the block,
+describing reference names and values. The format is described below.
+
+As the first ref block shares the first file block with the file header,
+all `restart_offset` in the first block are relative to the start of the
+file (position 0), and include the file header. This forces the first
+`restart_offset` to be `28`.
+
+ref record
+++++++++++
+
+A `ref_record` describes a single reference, storing both the name and
+its value(s). Records are formatted as:
+
+....
+varint( prefix_length )
+varint( (suffix_length << 3) | value_type )
+suffix
+varint( update_index_delta )
+value?
+....
+
+The `prefix_length` field specifies how many leading bytes of the prior
+reference record's name should be copied to obtain this reference's
+name. This must be 0 for the first reference in any block, and also must
+be 0 for any `ref_record` whose offset is listed in the `restart_offset`
+table at the end of the block.
+
+Recovering a reference name from any `ref_record` is a simple concat:
+
+....
+this_name = prior_name[0..prefix_length] + suffix
+....
+
+The `suffix_length` value provides the number of bytes available in
+`suffix` to copy from `suffix` to complete the reference name.
+
+The `update_index` that last modified the reference can be obtained by
+adding `update_index_delta` to the `min_update_index` from the file
+header: `min_update_index + update_index_delta`.
+
+The `value` follows. Its format is determined by `value_type`, one of
+the following:
+
+* `0x0`: deletion; no value data (see transactions, below)
+* `0x1`: one object name; value of the ref
+* `0x2`: two object names; value of the ref, peeled target
+* `0x3`: symbolic reference: `varint( target_len ) target`
+
+Symbolic references use `0x3`, followed by the complete name of the
+reference target. No compression is applied to the target name.
+
+Types `0x4..0x7` are reserved for future use.
+
+Ref index
+^^^^^^^^^
+
+The ref index stores the name of the last reference from every ref block
+in the file, enabling reduced disk seeks for lookups. Any reference can
+be found by searching the index, identifying the containing block, and
+searching within that block.
+
+The index may be organized into a multi-level index, where the 1st level
+index block points to additional ref index blocks (2nd level), which may
+in turn point to either additional index blocks (e.g. 3rd level) or ref
+blocks (leaf level). Disk reads required to access a ref go up with
+higher index levels. Multi-level indexes may be required to ensure no
+single index block exceeds the file format's max block size of
+`16777215` bytes (15.99 MiB). To achieve constant O(1) disk seeks for
+lookups the index must be a single level, which is permitted to exceed
+the file's configured block size, but not the format's max block size of
+15.99 MiB.
+
+If present, the ref index block(s) appears after the last ref block.
+
+If there are at least 4 ref blocks, a ref index block should be written
+to improve lookup times. Cold reads using the index require 2 disk reads
+(read index, read block), and binary searching < 4 blocks also requires
+<= 2 reads. Omitting the index block from smaller files saves space.
+
+If the file is unaligned and contains more than one ref block, the ref
+index must be written.
+
+Index block format:
+
+....
+'i'
+uint24( block_len )
+index_record+
+uint24( restart_offset )+
+uint16( restart_count )
+
+padding?
+....
+
+The index blocks begin with `block_type = 'i'` and a 3-byte `block_len`
+which encodes the number of bytes in the block, up to but not including
+the optional `padding`.
+
+The `restart_offset` and `restart_count` fields are identical in format,
+meaning and usage as in ref blocks.
+
+To reduce the number of reads required for random access in very large
+files the index block may be larger than other blocks. However, readers
+must hold the entire index in memory to benefit from this, so it's a
+time-space tradeoff in both file size and reader memory.
+
+Increasing the file's block size decreases the index size. Alternatively
+a multi-level index may be used, keeping index blocks within the file's
+block size, but increasing the number of blocks that need to be
+accessed.
+
+index record
+++++++++++++
+
+An index record describes the last entry in another block. Index records
+are written as:
+
+....
+varint( prefix_length )
+varint( (suffix_length << 3) | 0 )
+suffix
+varint( block_position )
+....
+
+Index records use prefix compression exactly like `ref_record`.
+
+Index records store `block_position` after the suffix, specifying the
+absolute position in bytes (from the start of the file) of the block
+that ends with this reference. Readers can seek to `block_position` to
+begin reading the block header.
+
+Readers must examine the block header at `block_position` to determine
+if the next block is another level index block, or the leaf-level ref
+block.
+
+Reading the index
++++++++++++++++++
+
+Readers loading the ref index must first read the footer (below) to
+obtain `ref_index_position`. If not present, the position will be 0. The
+`ref_index_position` is for the 1st level root of the ref index.
+
+Obj block format
+^^^^^^^^^^^^^^^^
+
+Object blocks are optional. Writers may choose to omit object blocks,
+especially if readers will not use the object name to ref mapping.
+
+Object blocks use unique, abbreviated 2-32 object name keys, mapping to
+ref blocks containing references pointing to that object directly, or as
+the peeled value of an annotated tag. Like ref blocks, object blocks use
+the file's standard block size. The abbrevation length is available in
+the footer as `obj_id_len`.
+
+To save space in small files, object blocks may be omitted if the ref
+index is not present, as brute force search will only need to read a few
+ref blocks. When missing, readers should brute force a linear search of
+all references to lookup by object name.
+
+An object block is written as:
+
+....
+'o'
+uint24( block_len )
+obj_record+
+uint24( restart_offset )+
+uint16( restart_count )
+
+padding?
+....
+
+Fields are identical to ref block. Binary search using the restart table
+works the same as in reference blocks.
+
+Because object names are abbreviated by writers to the shortest unique
+abbreviation within the reftable, obj key lengths have a variable length. Their
+length must be at least 2 bytes. Readers must compare only for common prefix
+match within an obj block or obj index.
+
+obj record
+++++++++++
+
+An `obj_record` describes a single object abbreviation, and the blocks
+containing references using that unique abbreviation:
+
+....
+varint( prefix_length )
+varint( (suffix_length << 3) | cnt_3 )
+suffix
+varint( cnt_large )?
+varint( position_delta )*
+....
+
+Like in reference blocks, abbreviations are prefix compressed within an
+obj block. On large reftables with many unique objects, higher block
+sizes (64k), and higher restart interval (128), a `prefix_length` of 2
+or 3 and `suffix_length` of 3 may be common in obj records (unique
+abbreviation of 5-6 raw bytes, 10-12 hex digits).
+
+Each record contains `position_count` number of positions for matching
+ref blocks. For 1-7 positions the count is stored in `cnt_3`. When
+`cnt_3 = 0` the actual count follows in a varint, `cnt_large`.
+
+The use of `cnt_3` bets most objects are pointed to by only a single
+reference, some may be pointed to by a couple of references, and very
+few (if any) are pointed to by more than 7 references.
+
+A special case exists when `cnt_3 = 0` and `cnt_large = 0`: there are no
+`position_delta`, but at least one reference starts with this
+abbreviation. A reader that needs exact reference names must scan all
+references to find which specific references have the desired object.
+Writers should use this format when the `position_delta` list would have
+overflowed the file's block size due to a high number of references
+pointing to the same object.
+
+The first `position_delta` is the position from the start of the file.
+Additional `position_delta` entries are sorted ascending and relative to
+the prior entry, e.g. a reader would perform:
+
+....
+pos = position_delta[0]
+prior = pos
+for (j = 1; j < position_count; j++) {
+  pos = prior + position_delta[j]
+  prior = pos
+}
+....
+
+With a position in hand, a reader must linearly scan the ref block,
+starting from the first `ref_record`, testing each reference's object names
+(for `value_type = 0x1` or `0x2`) for full equality. Faster searching by
+object name within a single ref block is not supported by the reftable format.
+Smaller block sizes reduce the number of candidates this step must
+consider.
+
+Obj index
+^^^^^^^^^
+
+The obj index stores the abbreviation from the last entry for every obj
+block in the file, enabling reduced disk seeks for all lookups. It is
+formatted exactly the same as the ref index, but refers to obj blocks.
+
+The obj index should be present if obj blocks are present, as obj blocks
+should only be written in larger files.
+
+Readers loading the obj index must first read the footer (below) to
+obtain `obj_index_position`. If not present, the position will be 0.
+
+Log block format
+^^^^^^^^^^^^^^^^
+
+Unlike ref and obj blocks, log blocks are always unaligned.
+
+Log blocks are variable in size, and do not match the `block_size`
+specified in the file header or footer. Writers should choose an
+appropriate buffer size to prepare a log block for deflation, such as
+`2 * block_size`.
+
+A log block is written as:
+
+....
+'g'
+uint24( block_len )
+zlib_deflate {
+  log_record+
+  uint24( restart_offset )+
+  uint16( restart_count )
+}
+....
+
+Log blocks look similar to ref blocks, except `block_type = 'g'`.
+
+The 4-byte block header is followed by the deflated block contents using
+zlib deflate. The `block_len` in the header is the inflated size
+(including 4-byte block header), and should be used by readers to
+preallocate the inflation output buffer. A log block's `block_len` may
+exceed the file's block size.
+
+Offsets within the log block (e.g. `restart_offset`) still include the
+4-byte header. Readers may prefer prefixing the inflation output buffer
+with the 4-byte header.
+
+Within the deflate container, a variable number of `log_record` describe
+reference changes. The log record format is described below. See ref
+block format (above) for a description of `restart_offset` and
+`restart_count`.
+
+Because log blocks have no alignment or padding between blocks, readers
+must keep track of the bytes consumed by the inflater to know where the
+next log block begins.
+
+log record
+++++++++++
+
+Log record keys are structured as:
+
+....
+ref_name '\0' reverse_int64( update_index )
+....
+
+where `update_index` is the unique transaction identifier. The
+`update_index` field must be unique within the scope of a `ref_name`.
+See the update transactions section below for further details.
+
+The `reverse_int64` function inverses the value so lexicographical
+ordering the network byte order encoding sorts the more recent records
+with higher `update_index` values first:
+
+....
+reverse_int64(int64 t) {
+  return 0xffffffffffffffff - t;
+}
+....
+
+Log records have a similar starting structure to ref and index records,
+utilizing the same prefix compression scheme applied to the log record
+key described above.
+
+....
+    varint( prefix_length )
+    varint( (suffix_length << 3) | log_type )
+    suffix
+    log_data {
+      old_id
+      new_id
+      varint( name_length    )  name
+      varint( email_length   )  email
+      varint( time_seconds )
+      sint16( tz_offset )
+      varint( message_length )  message
+    }?
+....
+
+Log record entries use `log_type` to indicate what follows:
+
+* `0x0`: deletion; no log data.
+* `0x1`: standard git reflog data using `log_data` above.
+
+The `log_type = 0x0` is mostly useful for `git stash drop`, removing an
+entry from the reflog of `refs/stash` in a transaction file (below),
+without needing to rewrite larger files. Readers reading a stack of
+reflogs must treat this as a deletion.
+
+For `log_type = 0x1`, the `log_data` section follows
+linkgit:git-update-ref[1] logging and includes:
+
+* two object names (old id, new id)
+* varint string of committer's name
+* varint string of committer's email
+* varint time in seconds since epoch (Jan 1, 1970)
+* 2-byte timezone offset in minutes (signed)
+* varint string of message
+
+`tz_offset` is the absolute number of minutes from GMT the committer was
+at the time of the update. For example `GMT-0800` is encoded in reftable
+as `sint16(-480)` and `GMT+0230` is `sint16(150)`.
+
+The committer email does not contain `<` or `>`, it's the value normally
+found between the `<>` in a git commit object header.
+
+The `message_length` may be 0, in which case there was no message
+supplied for the update.
+
+Contrary to traditional reflog (which is a file), renames are encoded as
+a combination of ref deletion and ref creation.  A deletion is a log
+record with a zero new_id, and a creation is a log record with a zero old_id.
+
+Reading the log
++++++++++++++++
+
+Readers accessing the log must first read the footer (below) to
+determine the `log_position`. The first block of the log begins at
+`log_position` bytes since the start of the file. The `log_position` is
+not block aligned.
+
+Importing logs
+++++++++++++++
+
+When importing from `$GIT_DIR/logs` writers should globally order all
+log records roughly by timestamp while preserving file order, and assign
+unique, increasing `update_index` values for each log line. Newer log
+records get higher `update_index` values.
+
+Although an import may write only a single reftable file, the reftable
+file must span many unique `update_index`, as each log line requires its
+own `update_index` to preserve semantics.
+
+Log index
+^^^^^^^^^
+
+The log index stores the log key
+(`refname \0 reverse_int64(update_index)`) for the last log record of
+every log block in the file, supporting bounded-time lookup.
+
+A log index block must be written if 2 or more log blocks are written to
+the file. If present, the log index appears after the last log block.
+There is no padding used to align the log index to block alignment.
+
+Log index format is identical to ref index, except the keys are 9 bytes
+longer to include `'\0'` and the 8-byte `reverse_int64(update_index)`.
+Records use `block_position` to refer to the start of a log block.
+
+Reading the index
++++++++++++++++++
+
+Readers loading the log index must first read the footer (below) to
+obtain `log_index_position`. If not present, the position will be 0.
+
+Footer
+^^^^^^
+
+After the last block of the file, a file footer is written. It begins
+like the file header, but is extended with additional data.
+
+....
+    HEADER
+
+    uint64( ref_index_position )
+    uint64( (obj_position << 5) | obj_id_len )
+    uint64( obj_index_position )
+
+    uint64( log_position )
+    uint64( log_index_position )
+
+    uint32( CRC-32 of above )
+....
+
+If a section is missing (e.g. ref index) the corresponding position
+field (e.g. `ref_index_position`) will be 0.
+
+* `obj_position`: byte position for the first obj block.
+* `obj_id_len`: number of bytes used to abbreviate object names in
+obj blocks.
+* `log_position`: byte position for the first log block.
+* `ref_index_position`: byte position for the start of the ref index.
+* `obj_index_position`: byte position for the start of the obj index.
+* `log_index_position`: byte position for the start of the log index.
+
+The size of the footer is 68 bytes for version 1, and 72 bytes for
+version 2.
+
+Reading the footer
+++++++++++++++++++
+
+Readers must first read the file start to determine the version
+number. Then they seek to `file_length - FOOTER_LENGTH` to access the
+footer. A trusted external source (such as `stat(2)`) is necessary to
+obtain `file_length`. When reading the footer, readers must verify:
+
+* 4-byte magic is correct
+* 1-byte version number is recognized
+* 4-byte CRC-32 matches the other 64 bytes (including magic, and
+version)
+
+Once verified, the other fields of the footer can be accessed.
+
+Empty tables
+++++++++++++
+
+A reftable may be empty. In this case, the file starts with a header
+and is immediately followed by a footer.
+
+Binary search
+^^^^^^^^^^^^^
+
+Binary search within a block is supported by the `restart_offset` fields
+at the end of the block. Readers can binary search through the restart
+table to locate between which two restart points the sought reference or
+key should appear.
+
+Each record identified by a `restart_offset` stores the complete key in
+the `suffix` field of the record, making the compare operation during
+binary search straightforward.
+
+Once a restart point lexicographically before the sought reference has
+been identified, readers can linearly scan through the following record
+entries to locate the sought record, terminating if the current record
+sorts after (and therefore the sought key is not present).
+
+Restart point selection
++++++++++++++++++++++++
+
+Writers determine the restart points at file creation. The process is
+arbitrary, but every 16 or 64 records is recommended. Every 16 may be
+more suitable for smaller block sizes (4k or 8k), every 64 for larger
+block sizes (64k).
+
+More frequent restart points reduces prefix compression and increases
+space consumed by the restart table, both of which increase file size.
+
+Less frequent restart points makes prefix compression more effective,
+decreasing overall file size, with increased penalties for readers
+walking through more records after the binary search step.
+
+A maximum of `65535` restart points per block is supported.
+
+Considerations
+~~~~~~~~~~~~~~
+
+Lightweight refs dominate
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The reftable format assumes the vast majority of references are single
+object names valued with common prefixes, such as Gerrit Code Review's
+`refs/changes/` namespace, GitHub's `refs/pulls/` namespace, or many
+lightweight tags in the `refs/tags/` namespace.
+
+Annotated tags storing the peeled object cost an additional object name per
+reference.
+
+Low overhead
+^^^^^^^^^^^^
+
+A reftable with very few references (e.g. git.git with 5 heads) is 269
+bytes for reftable, vs. 332 bytes for packed-refs. This supports
+reftable scaling down for transaction logs (below).
+
+Block size
+^^^^^^^^^^
+
+For a Gerrit Code Review type repository with many change refs, larger
+block sizes (64 KiB) and less frequent restart points (every 64) yield
+better compression due to more references within the block compressing
+against the prior reference.
+
+Larger block sizes reduce the index size, as the reftable will require
+fewer blocks to store the same number of references.
+
+Minimal disk seeks
+^^^^^^^^^^^^^^^^^^
+
+Assuming the index block has been loaded into memory, binary searching
+for any single reference requires exactly 1 disk seek to load the
+containing block.
+
+Scans and lookups dominate
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Scanning all references and lookup by name (or namespace such as
+`refs/heads/`) are the most common activities performed on repositories.
+Object names are stored directly with references to optimize this use case.
+
+Logs are infrequently read
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Logs are infrequently accessed, but can be large. Deflating log blocks
+saves disk space, with some increased penalty at read time.
+
+Logs are stored in an isolated section from refs, reducing the burden on
+reference readers that want to ignore logs. Further, historical logs can
+be isolated into log-only files.
+
+Logs are read backwards
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Logs are frequently accessed backwards (most recent N records for master
+to answer `master@{4}`), so log records are grouped by reference, and
+sorted descending by update index.
+
+Repository format
+~~~~~~~~~~~~~~~~~
+
+Version 1
+^^^^^^^^^
+
+A repository must set its `$GIT_DIR/config` to configure reftable:
+
+....
+[core]
+    repositoryformatversion = 1
+[extensions]
+    refStorage = reftable
+....
+
+Layout
+^^^^^^
+
+A collection of reftable files are stored in the `$GIT_DIR/reftable/`
+directory:
+
+....
+00000001-00000001.log
+00000002-00000002.ref
+00000003-00000003.ref
+....
+
+where reftable files are named by a unique name such as produced by the
+function `${min_update_index}-${max_update_index}.ref`.
+
+Log-only files use the `.log` extension, while ref-only and mixed ref
+and log files use `.ref`. extension.
+
+The stack ordering file is `$GIT_DIR/reftable/tables.list` and lists the
+current files, one per line, in order, from oldest (base) to newest
+(most recent):
+
+....
+$ cat .git/reftable/tables.list
+00000001-00000001.log
+00000002-00000002.ref
+00000003-00000003.ref
+....
+
+Readers must read `$GIT_DIR/reftable/tables.list` to determine which
+files are relevant right now, and search through the stack in reverse
+order (last reftable is examined first).
+
+Reftable files not listed in `tables.list` may be new (and about to be
+added to the stack by the active writer), or ancient and ready to be
+pruned.
+
+Backward compatibility
+^^^^^^^^^^^^^^^^^^^^^^
+
+Older clients should continue to recognize the directory as a git
+repository so they don't look for an enclosing repository in parent
+directories. To this end, a reftable-enabled repository must contain the
+following dummy files
+
+* `.git/HEAD`, a regular file containing `ref: refs/heads/.invalid`.
+* `.git/refs/`, a directory
+* `.git/refs/heads`, a regular file
+
+Readers
+^^^^^^^
+
+Readers can obtain a consistent snapshot of the reference space by
+following:
+
+1.  Open and read the `tables.list` file.
+2.  Open each of the reftable files that it mentions.
+3.  If any of the files is missing, goto 1.
+4.  Read from the now-open files as long as necessary.
+
+Update transactions
+^^^^^^^^^^^^^^^^^^^
+
+Although reftables are immutable, mutations are supported by writing a
+new reftable and atomically appending it to the stack:
+
+1.  Acquire `tables.list.lock`.
+2.  Read `tables.list` to determine current reftables.
+3.  Select `update_index` to be most recent file's
+`max_update_index + 1`.
+4.  Prepare temp reftable `tmp_XXXXXX`, including log entries.
+5.  Rename `tmp_XXXXXX` to `${update_index}-${update_index}.ref`.
+6.  Copy `tables.list` to `tables.list.lock`, appending file from (5).
+7.  Rename `tables.list.lock` to `tables.list`.
+
+During step 4 the new file's `min_update_index` and `max_update_index`
+are both set to the `update_index` selected by step 3. All log records
+for the transaction use the same `update_index` in their keys. This
+enables later correlation of which references were updated by the same
+transaction.
+
+Because a single `tables.list.lock` file is used to manage locking, the
+repository is single-threaded for writers. Writers may have to busy-spin
+(with backoff) around creating `tables.list.lock`, for up to an
+acceptable wait period, aborting if the repository is too busy to
+mutate. Application servers wrapped around repositories (e.g. Gerrit
+Code Review) can layer their own lock/wait queue to improve fairness to
+writers.
+
+Reference deletions
+^^^^^^^^^^^^^^^^^^^
+
+Deletion of any reference can be explicitly stored by setting the `type`
+to `0x0` and omitting the `value` field of the `ref_record`. This serves
+as a tombstone, overriding any assertions about the existence of the
+reference from earlier files in the stack.
+
+Compaction
+^^^^^^^^^^
+
+A partial stack of reftables can be compacted by merging references
+using a straightforward merge join across reftables, selecting the most
+recent value for output, and omitting deleted references that do not
+appear in remaining, lower reftables.
+
+A compacted reftable should set its `min_update_index` to the smallest
+of the input files' `min_update_index`, and its `max_update_index`
+likewise to the largest input `max_update_index`.
+
+For sake of illustration, assume the stack currently consists of
+reftable files (from oldest to newest): A, B, C, and D. The compactor is
+going to compact B and C, leaving A and D alone.
+
+1.  Obtain lock `tables.list.lock` and read the `tables.list` file.
+2.  Obtain locks `B.lock` and `C.lock`. Ownership of these locks
+prevents other processes from trying to compact these files.
+3.  Release `tables.list.lock`.
+4.  Compact `B` and `C` into a temp file
+`${min_update_index}-${max_update_index}_XXXXXX`.
+5.  Reacquire lock `tables.list.lock`.
+6.  Verify that `B` and `C` are still in the stack, in that order. This
+should always be the case, assuming that other processes are adhering to
+the locking protocol.
+7.  Rename `${min_update_index}-${max_update_index}_XXXXXX` to
+`${min_update_index}-${max_update_index}.ref`.
+8.  Write the new stack to `tables.list.lock`, replacing `B` and `C`
+with the file from (4).
+9.  Rename `tables.list.lock` to `tables.list`.
+10. Delete `B` and `C`, perhaps after a short sleep to avoid forcing
+readers to backtrack.
+
+This strategy permits compactions to proceed independently of updates.
+
+Each reftable (compacted or not) is uniquely identified by its name, so
+open reftables can be cached by their name.
+
+Alternatives considered
+~~~~~~~~~~~~~~~~~~~~~~~
+
+bzip packed-refs
+^^^^^^^^^^^^^^^^
+
+`bzip2` can significantly shrink a large packed-refs file (e.g. 62 MiB
+compresses to 23 MiB, 37%). However the bzip format does not support
+random access to a single reference. Readers must inflate and discard
+while performing a linear scan.
+
+Breaking packed-refs into chunks (individually compressing each chunk)
+would reduce the amount of data a reader must inflate, but still leaves
+the problem of indexing chunks to support readers efficiently locating
+the correct chunk.
+
+Given the compression achieved by reftable's encoding, it does not seem
+necessary to add the complexity of bzip/gzip/zlib.
+
+Michael Haggerty's alternate format
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Michael Haggerty proposed
+link:https://lore.kernel.org/git/CAMy9T_HCnyc1g8XWOOWhe7nN0aEFyyBskV2aOMb_fe%2BwGvEJ7A%40mail.gmail.com/[an
+alternate] format to reftable on the Git mailing list. This format uses
+smaller chunks, without the restart table, and avoids block alignment
+with padding. Reflog entries immediately follow each ref, and are thus
+interleaved between refs.
+
+Performance testing indicates reftable is faster for lookups (51%
+faster, 11.2 usec vs. 5.4 usec), although reftable produces a slightly
+larger file (+ ~3.2%, 28.3M vs 29.2M):
+
+[cols=">,>,>,>",options="header",]
+|=====================================
+|format |size |seek cold |seek hot
+|mh-alt |28.3 M |23.4 usec |11.2 usec
+|reftable |29.2 M |19.9 usec |5.4 usec
+|=====================================
+
+JGit Ketch RefTree
+^^^^^^^^^^^^^^^^^^
+
+https://dev.eclipse.org/mhonarc/lists/jgit-dev/msg03073.html[JGit Ketch]
+proposed
+link:https://lore.kernel.org/git/CAJo%3DhJvnAPNAdDcAAwAvU9C4RVeQdoS3Ev9WTguHx4fD0V_nOg%40mail.gmail.com/[RefTree],
+an encoding of references inside Git tree objects stored as part of the
+repository's object database.
+
+The RefTree format adds additional load on the object database storage
+layer (more loose objects, more objects in packs), and relies heavily on
+the packer's delta compression to save space. Namespaces which are flat
+(e.g. thousands of tags in refs/tags) initially create very large loose
+objects, and so RefTree does not address the problem of copying many
+references to modify a handful.
+
+Flat namespaces are not efficiently searchable in RefTree, as tree
+objects in canonical formatting cannot be binary searched. This fails
+the need to handle a large number of references in a single namespace,
+such as GitHub's `refs/pulls`, or a project with many tags.
+
+LMDB
+^^^^
+
+David Turner proposed
+https://lore.kernel.org/git/1455772670-21142-26-git-send-email-dturner@twopensource.com/[using
+LMDB], as LMDB is lightweight (64k of runtime code) and GPL-compatible
+license.
+
+A downside of LMDB is its reliance on a single C implementation. This
+makes embedding inside JGit (a popular reimplementation of Git)
+difficult, and hoisting onto virtual storage (for JGit DFS) virtually
+impossible.
+
+A common format that can be supported by all major Git implementations
+(git-core, JGit, libgit2) is strongly preferred.
index 06a5333ee638918a0939d9692fea4a0c932b349a..7b0cfeb92eb9f80a13ca16334e647f2ce70bef40 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.27.0
+DEF_VER=v2.27.GIT
 
 LF='
 '
index 90aa329eb7836824a7a45383e4b5b157124d815c..372139f1f244824e23684ce95e371d63244de48e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1186,7 +1186,7 @@ PTHREAD_CFLAGS =
 
 # For the 'sparse' target
 SPARSE_FLAGS ?=
-SP_EXTRA_FLAGS =
+SP_EXTRA_FLAGS = -Wno-universal-initializer
 
 # For the 'coccicheck' target; setting SPATCH_BATCH_SIZE higher will
 # usually result in less CPU usage at the cost of higher peak memory.
index f3d8527c2c0ceea3adfdbf077cb158e1f1d06a78..a7b4f6dc414595bb1474053e1002322a12f95739 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.27.0.txt
\ No newline at end of file
+Documentation/RelNotes/2.28.0.txt
\ No newline at end of file
index d8bfe379be4d50893b8b977efff9c79de54b38af..f899389e2cc2359d255f9a74430ef2a995fba004 100644 (file)
@@ -10,7 +10,7 @@
 #include "prompt.h"
 
 enum prompt_mode_type {
-       PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK,
+       PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_ADDITION, PROMPT_HUNK,
        PROMPT_MODE_MAX, /* must be last */
 };
 
@@ -33,6 +33,7 @@ static struct patch_mode patch_mode_add = {
        .prompt_mode = {
                N_("Stage mode change [y,n,q,a,d%s,?]? "),
                N_("Stage deletion [y,n,q,a,d%s,?]? "),
+               N_("Stage addition [y,n,q,a,d%s,?]? "),
                N_("Stage this hunk [y,n,q,a,d%s,?]? ")
        },
        .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -54,6 +55,7 @@ static struct patch_mode patch_mode_stash = {
        .prompt_mode = {
                N_("Stash mode change [y,n,q,a,d%s,?]? "),
                N_("Stash deletion [y,n,q,a,d%s,?]? "),
+               N_("Stash addition [y,n,q,a,d%s,?]? "),
                N_("Stash this hunk [y,n,q,a,d%s,?]? "),
        },
        .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -77,6 +79,7 @@ static struct patch_mode patch_mode_reset_head = {
        .prompt_mode = {
                N_("Unstage mode change [y,n,q,a,d%s,?]? "),
                N_("Unstage deletion [y,n,q,a,d%s,?]? "),
+               N_("Unstage addition [y,n,q,a,d%s,?]? "),
                N_("Unstage this hunk [y,n,q,a,d%s,?]? "),
        },
        .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -99,6 +102,7 @@ static struct patch_mode patch_mode_reset_nothead = {
        .prompt_mode = {
                N_("Apply mode change to index [y,n,q,a,d%s,?]? "),
                N_("Apply deletion to index [y,n,q,a,d%s,?]? "),
+               N_("Apply addition to index [y,n,q,a,d%s,?]? "),
                N_("Apply this hunk to index [y,n,q,a,d%s,?]? "),
        },
        .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -121,6 +125,7 @@ static struct patch_mode patch_mode_checkout_index = {
        .prompt_mode = {
                N_("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
                N_("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard addition from worktree [y,n,q,a,d%s,?]? "),
                N_("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
        },
        .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -143,6 +148,7 @@ static struct patch_mode patch_mode_checkout_head = {
        .prompt_mode = {
                N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
                N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard addition from index and worktree [y,n,q,a,d%s,?]? "),
                N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
        },
        .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -164,6 +170,7 @@ static struct patch_mode patch_mode_checkout_nothead = {
        .prompt_mode = {
                N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
                N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Apply addition to index and worktree [y,n,q,a,d%s,?]? "),
                N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
        },
        .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -186,6 +193,7 @@ static struct patch_mode patch_mode_worktree_head = {
        .prompt_mode = {
                N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
                N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard addition from index and worktree [y,n,q,a,d%s,?]? "),
                N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
        },
        .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -207,6 +215,7 @@ static struct patch_mode patch_mode_worktree_nothead = {
        .prompt_mode = {
                N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
                N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Apply addition to index and worktree [y,n,q,a,d%s,?]? "),
                N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
        },
        .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
@@ -248,7 +257,7 @@ struct add_p_state {
                struct hunk head;
                struct hunk *hunk;
                size_t hunk_nr, hunk_alloc;
-               unsigned deleted:1, mode_change:1,binary:1;
+               unsigned deleted:1, added:1, mode_change:1,binary:1;
        } *file_diff;
        size_t file_diff_nr;
 
@@ -442,7 +451,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
        pend = p + plain->len;
        while (p != pend) {
                char *eol = memchr(p, '\n', pend - p);
-               const char *deleted = NULL, *mode_change = NULL;
+               const char *deleted = NULL, *added = NULL, *mode_change = NULL;
 
                if (!eol)
                        eol = pend;
@@ -461,11 +470,12 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
                } else if (p == plain->buf)
                        BUG("diff starts with unexpected line:\n"
                            "%.*s\n", (int)(eol - p), p);
-               else if (file_diff->deleted)
+               else if (file_diff->deleted || file_diff->added)
                        ; /* keep the rest of the file in a single "hunk" */
                else if (starts_with(p, "@@ ") ||
                         (hunk == &file_diff->head &&
-                         skip_prefix(p, "deleted file", &deleted))) {
+                         (skip_prefix(p, "deleted file", &deleted) ||
+                          skip_prefix(p, "new file", &added)))) {
                        if (marker == '-' || marker == '+')
                                /*
                                 * Should not happen; previous hunk did not end
@@ -485,6 +495,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
 
                        if (deleted)
                                file_diff->deleted = 1;
+                       else if (added)
+                               file_diff->added = 1;
                        else if (parse_hunk_header(s, hunk) < 0)
                                return -1;
 
@@ -537,8 +549,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
                           starts_with(p, "Binary files "))
                        file_diff->binary = 1;
 
-               if (file_diff->deleted && file_diff->mode_change)
-                       BUG("diff contains delete *and* a mode change?!?\n%.*s",
+               if (!!file_diff->deleted + !!file_diff->added +
+                   !!file_diff->mode_change > 1)
+                       BUG("diff can only contain delete *or* add *or* a "
+                           "mode change?!?\n%.*s",
                            (int)(eol - (plain->buf + file_diff->head.start)),
                            plain->buf + file_diff->head.start);
 
@@ -1397,6 +1411,8 @@ static int patch_update_file(struct add_p_state *s,
 
                if (file_diff->deleted)
                        prompt_mode_type = PROMPT_DELETION;
+               else if (file_diff->added)
+                       prompt_mode_type = PROMPT_ADDITION;
                else if (file_diff->mode_change && !hunk_index)
                        prompt_mode_type = PROMPT_MODE_CHANGE;
                else
diff --git a/bloom.c b/bloom.c
index 9b86aa3f59ab5a7fdb3fb25474f1ab1fa28d65db..6c7611847ad05cb95103177ca00e7824f42b63c1 100644 (file)
--- a/bloom.c
+++ b/bloom.c
@@ -138,6 +138,11 @@ void fill_bloom_key(const char *data,
                key->hashes[i] = hash0 + i * hash1;
 }
 
+void clear_bloom_key(struct bloom_key *key)
+{
+       FREE_AND_NULL(key->hashes);
+}
+
 void add_key_to_filter(const struct bloom_key *key,
                       struct bloom_filter *filter,
                       const struct bloom_filter_settings *settings)
diff --git a/bloom.h b/bloom.h
index b2a8379a71bd7f844f7933afc5b48d84c51bb8df..d8fbb0fbf19631ccb33d2e57590f977467579102 100644 (file)
--- a/bloom.h
+++ b/bloom.h
@@ -72,6 +72,7 @@ void fill_bloom_key(const char *data,
                    size_t len,
                    struct bloom_key *key,
                    const struct bloom_filter_settings *settings);
+void clear_bloom_key(struct bloom_key *key);
 
 void add_key_to_filter(const struct bloom_key *key,
                       struct bloom_filter *filter,
index aa8a489c35e8f48799e6e3e3d39e585724c89c3d..28f4568b01f28bbbb8e6de4c07c92947533e6ef7 100644 (file)
@@ -9,6 +9,7 @@
 static void get_system_info(struct strbuf *sys_info)
 {
        struct utsname uname_info;
+       char *shell = NULL;
 
        /* get git version from native cmd */
        strbuf_addstr(sys_info, _("git version:\n"));
@@ -29,8 +30,13 @@ static void get_system_info(struct strbuf *sys_info)
 
        strbuf_addstr(sys_info, _("compiler info: "));
        get_compiler_info(sys_info);
+
        strbuf_addstr(sys_info, _("libc info: "));
        get_libc_info(sys_info);
+
+       shell = getenv("SHELL");
+       strbuf_addf(sys_info, "$SHELL (typically, interactive shell): %s\n",
+                   shell ? shell : "<unset>");
 }
 
 static void get_populated_hooks(struct strbuf *hook_info, int nongit)
index c1c40b516df7816b2cc0ce5cb20ee8beb7e9e000..ec4996282e36e2edcd4109321f83b921a24c5a32 100644 (file)
@@ -455,9 +455,12 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout,
                        no_checkout = 1;
                } else if (!strcmp(arg, "--term-good") ||
                         !strcmp(arg, "--term-old")) {
+                       i++;
+                       if (argc <= i)
+                               return error(_("'' is not a valid term"));
                        must_write_terms = 1;
                        free((void *) terms->term_good);
-                       terms->term_good = xstrdup(argv[++i]);
+                       terms->term_good = xstrdup(argv[i]);
                } else if (skip_prefix(arg, "--term-good=", &arg) ||
                           skip_prefix(arg, "--term-old=", &arg)) {
                        must_write_terms = 1;
@@ -465,16 +468,18 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout,
                        terms->term_good = xstrdup(arg);
                } else if (!strcmp(arg, "--term-bad") ||
                         !strcmp(arg, "--term-new")) {
+                       i++;
+                       if (argc <= i)
+                               return error(_("'' is not a valid term"));
                        must_write_terms = 1;
                        free((void *) terms->term_bad);
-                       terms->term_bad = xstrdup(argv[++i]);
+                       terms->term_bad = xstrdup(argv[i]);
                } else if (skip_prefix(arg, "--term-bad=", &arg) ||
                           skip_prefix(arg, "--term-new=", &arg)) {
                        must_write_terms = 1;
                        free((void *) terms->term_bad);
                        terms->term_bad = xstrdup(arg);
-               } else if (starts_with(arg, "--") &&
-                        !one_of(arg, "--term-good", "--term-bad", NULL)) {
+               } else if (starts_with(arg, "--")) {
                        return error(_("unrecognized option: '%s'"), arg);
                } else {
                        char *commit_id = xstrfmt("%s^{commit}", arg);
index e9d111bb8360d19c4c81fa6d8caa54b8049576fe..af849c644fec1852845b1dfdb6ce8daa903e0fb5 100644 (file)
@@ -621,9 +621,7 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
        init_checkout_metadata(&opts.meta, info->refname,
-                              info->commit ? &info->commit->object.oid :
-                              is_null_oid(&info->oid) ? &tree->object.oid :
-                              &info->oid,
+                              info->commit ? &info->commit->object.oid : &null_oid,
                               NULL);
        parse_tree(tree);
        init_tree_desc(&tree_desc, tree->buffer, tree->size);
@@ -1689,7 +1687,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
                 * Try to give more helpful suggestion.
                 * new_branch && argc > 1 will be caught later.
                 */
-               if (opts->new_branch && argc == 1)
+               if (opts->new_branch && argc == 1 && !new_branch_info.commit)
                        die(_("'%s' is not a commit and a branch '%s' cannot be created from it"),
                                argv[0], opts->new_branch);
 
index cb48a291caf9a364f21208e762f6b6e07a6ca7f6..2a8e3aaaed367cc094d8277bd2d1aa6ccb9210c4 100644 (file)
@@ -945,7 +945,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 {
        int is_bundle = 0, is_local;
        const char *repo_name, *repo, *work_tree, *git_dir;
-       char *path, *dir;
+       char *path, *dir, *display_repo = NULL;
        int dest_exists;
        const struct ref *refs, *remote_head;
        const struct ref *remote_head_points_at;
@@ -1000,10 +1000,11 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        path = get_repo_path(repo_name, &is_bundle);
        if (path)
                repo = absolute_pathdup(repo_name);
-       else if (!strchr(repo_name, ':'))
-               die(_("repository '%s' does not exist"), repo_name);
-       else
+       else if (strchr(repo_name, ':')) {
                repo = repo_name;
+               display_repo = transport_anonymize_url(repo);
+       } else
+               die(_("repository '%s' does not exist"), repo_name);
 
        /* no need to be strict, transport_set_option() will validate it again */
        if (option_depth && atoi(option_depth) < 1)
@@ -1020,7 +1021,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                die(_("destination path '%s' already exists and is not "
                        "an empty directory."), dir);
 
-       strbuf_addf(&reflog_msg, "clone: from %s", repo);
+       strbuf_addf(&reflog_msg, "clone: from %s",
+                   display_repo ? display_repo : repo);
+       free(display_repo);
 
        if (option_bare)
                work_tree = NULL;
index 15fe60317c7846df93d9975dc991ba3a67bd0758..75455da138d5f6efae26eb5d5280050aa351dee3 100644 (file)
@@ -6,6 +6,8 @@
 #include "repository.h"
 #include "commit-graph.h"
 #include "object-store.h"
+#include "progress.h"
+#include "tag.h"
 
 static char const * const builtin_commit_graph_usage[] = {
        N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"),
@@ -138,14 +140,37 @@ static int write_option_parse_split(const struct option *opt, const char *arg,
        return 0;
 }
 
+static int read_one_commit(struct oidset *commits, struct progress *progress,
+                          const char *hash)
+{
+       struct object *result;
+       struct object_id oid;
+       const char *end;
+
+       if (parse_oid_hex(hash, &oid, &end))
+               return error(_("unexpected non-hex object ID: %s"), hash);
+
+       result = deref_tag(the_repository, parse_object(the_repository, &oid),
+                          NULL, 0);
+       if (!result)
+               return error(_("invalid object: %s"), hash);
+       else if (object_as_type(the_repository, result, OBJ_COMMIT, 1))
+               oidset_insert(commits, &result->oid);
+
+       display_progress(progress, oidset_size(commits));
+
+       return 0;
+}
+
 static int graph_write(int argc, const char **argv)
 {
-       struct string_list *pack_indexes = NULL;
+       struct string_list pack_indexes = STRING_LIST_INIT_NODUP;
+       struct strbuf buf = STRBUF_INIT;
        struct oidset commits = OIDSET_INIT;
        struct object_directory *odb = NULL;
-       struct string_list lines;
        int result = 0;
        enum commit_graph_write_flags flags = 0;
+       struct progress *progress = NULL;
 
        static struct option builtin_commit_graph_write_options[] = {
                OPT_STRING(0, "object-dir", &opts.obj_dir,
@@ -209,44 +234,38 @@ static int graph_write(int argc, const char **argv)
                return 0;
        }
 
-       string_list_init(&lines, 0);
-       if (opts.stdin_packs || opts.stdin_commits) {
-               struct strbuf buf = STRBUF_INIT;
-
+       if (opts.stdin_packs) {
                while (strbuf_getline(&buf, stdin) != EOF)
-                       string_list_append(&lines, strbuf_detach(&buf, NULL));
-
-               if (opts.stdin_packs)
-                       pack_indexes = &lines;
-               if (opts.stdin_commits) {
-                       struct string_list_item *item;
-                       oidset_init(&commits, lines.nr);
-                       for_each_string_list_item(item, &lines) {
-                               struct object_id oid;
-                               const char *end;
-
-                               if (parse_oid_hex(item->string, &oid, &end)) {
-                                       error(_("unexpected non-hex object ID: "
-                                               "%s"), item->string);
-                                       return 1;
-                               }
-
-                               oidset_insert(&commits, &oid);
+                       string_list_append(&pack_indexes,
+                                          strbuf_detach(&buf, NULL));
+       } else if (opts.stdin_commits) {
+               oidset_init(&commits, 0);
+               if (opts.progress)
+                       progress = start_delayed_progress(
+                               _("Collecting commits from input"), 0);
+
+               while (strbuf_getline(&buf, stdin) != EOF) {
+                       if (read_one_commit(&commits, progress, buf.buf)) {
+                               result = 1;
+                               goto cleanup;
                        }
-                       flags |= COMMIT_GRAPH_WRITE_CHECK_OIDS;
                }
 
-               UNLEAK(buf);
+
        }
 
        if (write_commit_graph(odb,
-                              pack_indexes,
+                              opts.stdin_packs ? &pack_indexes : NULL,
                               opts.stdin_commits ? &commits : NULL,
                               flags,
                               &split_opts))
                result = 1;
 
-       UNLEAK(lines);
+cleanup:
+       string_list_clear(&pack_indexes, 0);
+       strbuf_release(&buf);
+       if (progress)
+               stop_progress(&progress);
        return result;
 }
 
index 47711000725b4fb7dc504c62cc2d4ac384f7d46b..94b0c89b8241f65e53d9c80a24de330cf35935ac 100644 (file)
@@ -224,7 +224,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
        version = discover_version(&reader);
        switch (version) {
        case protocol_v2:
-               get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL);
+               get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc);
                break;
        case protocol_v1:
        case protocol_v0:
index b5788c16bf43da12c81525498cf056a9b828962e..da11165ce2b0f0c4ef20a290f58fe6f376004fdc 100644 (file)
@@ -1758,8 +1758,13 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
 
        /* Record the command line for the reflog */
        strbuf_addstr(&default_rla, "fetch");
-       for (i = 1; i < argc; i++)
-               strbuf_addf(&default_rla, " %s", argv[i]);
+       for (i = 1; i < argc; i++) {
+               /* This handles non-URLs gracefully */
+               char *anon = transport_anonymize_url(argv[i]);
+
+               strbuf_addf(&default_rla, " %s", anon);
+               free(anon);
+       }
 
        fetch_config_from_gitmodules(&submodule_fetch_jobs_config,
                                     &recurse_submodules);
index ca6a5dc4bf782ef1ac88c817365483cc22c18f58..7da707bf55d94f0a151fb03954b3b107e408e551 100644 (file)
@@ -1656,7 +1656,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                                }
                                merge_was_ok = 1;
                        }
-                       cnt = evaluate_result();
+                       cnt = (use_strategies_nr > 1) ? evaluate_result() : 0;
                        if (best_cnt <= 0 || cnt <= best_cnt) {
                                best_strategy = use_strategies[i]->name;
                                best_cnt = cnt;
index 95d0882417211e4172bc028de7e54c1948030f08..595463be68ea05d9d188df468803041bdfcb5d02 100644 (file)
@@ -99,6 +99,10 @@ static int update_working_directory(struct pattern_list *pl)
        struct lock_file lock_file = LOCK_INIT;
        struct repository *r = the_repository;
 
+       /* If no branch has been checked out, there are no updates to make. */
+       if (is_index_unborn(r->index))
+               return UPDATE_SPARSITY_SUCCESS;
+
        memset(&o, 0, sizeof(o));
        o.verbose_update = isatty(2);
        o.update = 1;
index d99db356684fab9c1f2c53790a95bb2e2723c541..1238b6bab1f34d11121f9c55e0af955386e86b69 100644 (file)
@@ -67,7 +67,12 @@ static void delete_worktrees_dir_if_empty(void)
        rmdir(git_path("worktrees")); /* ignore failed removal */
 }
 
-static int prune_worktree(const char *id, struct strbuf *reason)
+/*
+ * Return true if worktree entry should be pruned, along with the reason for
+ * pruning. Otherwise, return false and the worktree's path, or NULL if it
+ * cannot be determined. Caller is responsible for freeing returned path.
+ */
+static int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath)
 {
        struct stat st;
        char *path;
@@ -75,20 +80,21 @@ static int prune_worktree(const char *id, struct strbuf *reason)
        size_t len;
        ssize_t read_result;
 
+       *wtpath = NULL;
        if (!is_directory(git_path("worktrees/%s", id))) {
-               strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
+               strbuf_addstr(reason, _("not a valid directory"));
                return 1;
        }
        if (file_exists(git_path("worktrees/%s/locked", id)))
                return 0;
        if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
-               strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id);
+               strbuf_addstr(reason, _("gitdir file does not exist"));
                return 1;
        }
        fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
        if (fd < 0) {
-               strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
-                           id, strerror(errno));
+               strbuf_addf(reason, _("unable to read gitdir file (%s)"),
+                           strerror(errno));
                return 1;
        }
        len = xsize_t(st.st_size);
@@ -96,8 +102,8 @@ static int prune_worktree(const char *id, struct strbuf *reason)
 
        read_result = read_in_full(fd, path, len);
        if (read_result < 0) {
-               strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
-                           id, strerror(errno));
+               strbuf_addf(reason, _("unable to read gitdir file (%s)"),
+                           strerror(errno));
                close(fd);
                free(path);
                return 1;
@@ -106,53 +112,103 @@ static int prune_worktree(const char *id, struct strbuf *reason)
 
        if (read_result != len) {
                strbuf_addf(reason,
-                           _("Removing worktrees/%s: short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"),
-                           id, (uintmax_t)len, (uintmax_t)read_result);
+                           _("short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"),
+                           (uintmax_t)len, (uintmax_t)read_result);
                free(path);
                return 1;
        }
        while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
                len--;
        if (!len) {
-               strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id);
+               strbuf_addstr(reason, _("invalid gitdir file"));
                free(path);
                return 1;
        }
        path[len] = '\0';
        if (!file_exists(path)) {
-               free(path);
                if (stat(git_path("worktrees/%s/index", id), &st) ||
                    st.st_mtime <= expire) {
-                       strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id);
+                       strbuf_addstr(reason, _("gitdir file points to non-existent location"));
+                       free(path);
                        return 1;
                } else {
+                       *wtpath = path;
                        return 0;
                }
        }
-       free(path);
+       *wtpath = path;
        return 0;
 }
 
+static void prune_worktree(const char *id, const char *reason)
+{
+       if (show_only || verbose)
+               printf_ln(_("Removing %s/%s: %s"), "worktrees", id, reason);
+       if (!show_only)
+               delete_git_dir(id);
+}
+
+static int prune_cmp(const void *a, const void *b)
+{
+       const struct string_list_item *x = a;
+       const struct string_list_item *y = b;
+       int c;
+
+       if ((c = fspathcmp(x->string, y->string)))
+           return c;
+       /*
+        * paths same; prune_dupes() removes all but the first worktree entry
+        * having the same path, so sort main worktree ('util' is NULL) above
+        * linked worktrees ('util' not NULL) since main worktree can't be
+        * removed
+        */
+       if (!x->util)
+               return -1;
+       if (!y->util)
+               return 1;
+       /* paths same; sort by .git/worktrees/<id> */
+       return strcmp(x->util, y->util);
+}
+
+static void prune_dups(struct string_list *l)
+{
+       int i;
+
+       QSORT(l->items, l->nr, prune_cmp);
+       for (i = 1; i < l->nr; i++) {
+               if (!fspathcmp(l->items[i].string, l->items[i - 1].string))
+                       prune_worktree(l->items[i].util, "duplicate entry");
+       }
+}
+
 static void prune_worktrees(void)
 {
        struct strbuf reason = STRBUF_INIT;
+       struct strbuf main_path = STRBUF_INIT;
+       struct string_list kept = STRING_LIST_INIT_NODUP;
        DIR *dir = opendir(git_path("worktrees"));
        struct dirent *d;
        if (!dir)
                return;
        while ((d = readdir(dir)) != NULL) {
+               char *path;
                if (is_dot_or_dotdot(d->d_name))
                        continue;
                strbuf_reset(&reason);
-               if (!prune_worktree(d->d_name, &reason))
-                       continue;
-               if (show_only || verbose)
-                       printf("%s\n", reason.buf);
-               if (show_only)
-                       continue;
-               delete_git_dir(d->d_name);
+               if (should_prune_worktree(d->d_name, &reason, &path))
+                       prune_worktree(d->d_name, reason.buf);
+               else if (path)
+                       string_list_append(&kept, path)->util = xstrdup(d->d_name);
        }
        closedir(dir);
+
+       strbuf_add_absolute_path(&main_path, get_git_common_dir());
+       /* massage main worktree absolute path to match 'gitdir' content */
+       strbuf_strip_suffix(&main_path, "/.");
+       string_list_append(&kept, strbuf_detach(&main_path, NULL));
+       prune_dups(&kept);
+       string_list_clear(&kept, 1);
+
        if (!show_only)
                delete_worktrees_dir_if_empty();
        strbuf_release(&reason);
@@ -224,34 +280,33 @@ static const char *worktree_basename(const char *path, int *olen)
        return name;
 }
 
-static void validate_worktree_add(const char *path, const struct add_opts *opts)
+/* check that path is viable location for worktree */
+static void check_candidate_path(const char *path,
+                                int force,
+                                struct worktree **worktrees,
+                                const char *cmd)
 {
-       struct worktree **worktrees;
        struct worktree *wt;
        int locked;
 
        if (file_exists(path) && !is_empty_dir(path))
                die(_("'%s' already exists"), path);
 
-       worktrees = get_worktrees(0);
        wt = find_worktree_by_path(worktrees, path);
        if (!wt)
-               goto done;
+               return;
 
        locked = !!worktree_lock_reason(wt);
-       if ((!locked && opts->force) || (locked && opts->force > 1)) {
+       if ((!locked && force) || (locked && force > 1)) {
                if (delete_git_dir(wt->id))
-                   die(_("unable to re-add worktree '%s'"), path);
-               goto done;
+                   die(_("unusable worktree destination '%s'"), path);
+               return;
        }
 
        if (locked)
-               die(_("'%s' is a missing but locked worktree;\nuse 'add -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"), path);
+               die(_("'%s' is a missing but locked worktree;\nuse '%s -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"), cmd, path);
        else
-               die(_("'%s' is a missing but already registered worktree;\nuse 'add -f' to override, or 'prune' or 'remove' to clear"), path);
-
-done:
-       free_worktrees(worktrees);
+               die(_("'%s' is a missing but already registered worktree;\nuse '%s -f' to override, or 'prune' or 'remove' to clear"), cmd, path);
 }
 
 static int add_worktree(const char *path, const char *refname,
@@ -268,8 +323,12 @@ static int add_worktree(const char *path, const char *refname,
        struct commit *commit = NULL;
        int is_branch = 0;
        struct strbuf sb_name = STRBUF_INIT;
+       struct worktree **worktrees;
 
-       validate_worktree_add(path, opts);
+       worktrees = get_worktrees(0);
+       check_candidate_path(path, opts->force, worktrees, "add");
+       free_worktrees(worktrees);
+       worktrees = NULL;
 
        /* is 'refname' a branch or commit? */
        if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
@@ -804,8 +863,7 @@ static int move_worktree(int ac, const char **av, const char *prefix)
                strbuf_trim_trailing_dir_sep(&dst);
                strbuf_addstr(&dst, sep);
        }
-       if (file_exists(dst.buf))
-               die(_("target '%s' already exists"), dst.buf);
+       check_candidate_path(dst.buf, force, worktrees, "move");
 
        validate_no_submodules(wt);
 
index e3420ddcbff5e829aa821d6c42c740e6eb819d37..2ff042fbf4f732f63b6317f9430e1e589df67ef2 100644 (file)
@@ -881,7 +881,6 @@ struct write_commit_graph_context {
        unsigned append:1,
                 report_progress:1,
                 split:1,
-                check_oids:1,
                 changed_paths:1,
                 order_by_pack:1;
 
@@ -1319,13 +1318,25 @@ static void compute_bloom_filters(struct write_commit_graph_context *ctx)
        stop_progress(&progress);
 }
 
+struct refs_cb_data {
+       struct oidset *commits;
+       struct progress *progress;
+};
+
 static int add_ref_to_set(const char *refname,
                          const struct object_id *oid,
                          int flags, void *cb_data)
 {
-       struct oidset *commits = (struct oidset *)cb_data;
+       struct object_id peeled;
+       struct refs_cb_data *data = (struct refs_cb_data *)cb_data;
+
+       if (!peel_ref(refname, &peeled))
+               oid = &peeled;
+       if (oid_object_info(the_repository, oid, NULL) == OBJ_COMMIT)
+               oidset_insert(data->commits, oid);
+
+       display_progress(data->progress, oidset_size(data->commits));
 
-       oidset_insert(commits, oid);
        return 0;
 }
 
@@ -1334,13 +1345,22 @@ int write_commit_graph_reachable(struct object_directory *odb,
                                 const struct split_commit_graph_opts *split_opts)
 {
        struct oidset commits = OIDSET_INIT;
+       struct refs_cb_data data;
        int result;
 
-       for_each_ref(add_ref_to_set, &commits);
+       memset(&data, 0, sizeof(data));
+       data.commits = &commits;
+       if (flags & COMMIT_GRAPH_WRITE_PROGRESS)
+               data.progress = start_delayed_progress(
+                       _("Collecting referenced commits"), 0);
+
+       for_each_ref(add_ref_to_set, &data);
        result = write_commit_graph(odb, NULL, &commits,
                                    flags, split_opts);
 
        oidset_clear(&commits);
+       if (data.progress)
+               stop_progress(&data.progress);
        return result;
 }
 
@@ -1392,46 +1412,19 @@ static int fill_oids_from_packs(struct write_commit_graph_context *ctx,
 static int fill_oids_from_commits(struct write_commit_graph_context *ctx,
                                  struct oidset *commits)
 {
-       uint32_t i = 0;
-       struct strbuf progress_title = STRBUF_INIT;
        struct oidset_iter iter;
        struct object_id *oid;
 
        if (!oidset_size(commits))
                return 0;
 
-       if (ctx->report_progress) {
-               strbuf_addf(&progress_title,
-                           Q_("Finding commits for commit graph from %d ref",
-                              "Finding commits for commit graph from %d refs",
-                              oidset_size(commits)),
-                           oidset_size(commits));
-               ctx->progress = start_delayed_progress(
-                                       progress_title.buf,
-                                       oidset_size(commits));
-       }
-
        oidset_iter_init(commits, &iter);
        while ((oid = oidset_iter_next(&iter))) {
-               struct commit *result;
-
-               display_progress(ctx->progress, ++i);
-
-               result = lookup_commit_reference_gently(ctx->r, oid, 1);
-               if (result) {
-                       ALLOC_GROW(ctx->oids.list, ctx->oids.nr + 1, ctx->oids.alloc);
-                       oidcpy(&ctx->oids.list[ctx->oids.nr], &(result->object.oid));
-                       ctx->oids.nr++;
-               } else if (ctx->check_oids) {
-                       error(_("invalid commit object id: %s"),
-                             oid_to_hex(oid));
-                       return -1;
-               }
+               ALLOC_GROW(ctx->oids.list, ctx->oids.nr + 1, ctx->oids.alloc);
+               oidcpy(&ctx->oids.list[ctx->oids.nr], oid);
+               ctx->oids.nr++;
        }
 
-       stop_progress(&ctx->progress);
-       strbuf_release(&progress_title);
-
        return 0;
 }
 
@@ -2017,7 +2010,6 @@ int write_commit_graph(struct object_directory *odb,
        ctx->append = flags & COMMIT_GRAPH_WRITE_APPEND ? 1 : 0;
        ctx->report_progress = flags & COMMIT_GRAPH_WRITE_PROGRESS ? 1 : 0;
        ctx->split = flags & COMMIT_GRAPH_WRITE_SPLIT ? 1 : 0;
-       ctx->check_oids = flags & COMMIT_GRAPH_WRITE_CHECK_OIDS ? 1 : 0;
        ctx->split_opts = split_opts;
        ctx->changed_paths = flags & COMMIT_GRAPH_WRITE_BLOOM_FILTERS ? 1 : 0;
        ctx->total_bloom_filter_data_size = 0;
index 4212766a4f0507f52fb5d4f42edbf1b977fc5cae..3ba0da1e5f4738823670ba560a34e156946b1b8a 100644 (file)
@@ -91,9 +91,7 @@ enum commit_graph_write_flags {
        COMMIT_GRAPH_WRITE_APPEND     = (1 << 0),
        COMMIT_GRAPH_WRITE_PROGRESS   = (1 << 1),
        COMMIT_GRAPH_WRITE_SPLIT      = (1 << 2),
-       /* Make sure that each OID in the input is a valid commit OID. */
-       COMMIT_GRAPH_WRITE_CHECK_OIDS = (1 << 3),
-       COMMIT_GRAPH_WRITE_BLOOM_FILTERS = (1 << 4),
+       COMMIT_GRAPH_WRITE_BLOOM_FILTERS = (1 << 3),
 };
 
 enum commit_graph_split_flags {
index d9f71b7cbb71676567930f327c1c0acf0b67641a..61ad084a7b710e3b667fe191bd06a63879e5177e 100755 (executable)
@@ -23,7 +23,9 @@ while (@ARGV) {
            # before any "-l*" flags.
            $is_debug = 1;
        }
-       if ("$arg" =~ /^-[DIMGOZ]/) {
+       if ("$arg" =~ /^-I\/mingw(32|64)/) {
+               # eat
+       } elsif ("$arg" =~ /^-[DIMGOZ]/) {
                push(@cflags, $arg);
        } elsif ("$arg" eq "-o") {
                my $file_out = shift @ARGV;
index 23013c634436adeeac65bd5e6a0d895a06fe6fe6..0df45a110888e53e4d6f3f86bafc76abcd096297 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -127,6 +127,7 @@ enum protocol_version discover_version(struct packet_reader *reader)
                die_initial_contact(0);
        case PACKET_READ_FLUSH:
        case PACKET_READ_DELIM:
+       case PACKET_READ_RESPONSE_END:
                version = protocol_v0;
                break;
        case PACKET_READ_NORMAL:
@@ -310,6 +311,7 @@ struct ref **get_remote_heads(struct packet_reader *reader,
                        state = EXPECTING_DONE;
                        break;
                case PACKET_READ_DELIM:
+               case PACKET_READ_RESPONSE_END:
                        die(_("invalid packet"));
                }
 
@@ -404,10 +406,21 @@ out:
        return ret;
 }
 
+void check_stateless_delimiter(int stateless_rpc,
+                             struct packet_reader *reader,
+                             const char *error)
+{
+       if (!stateless_rpc)
+               return; /* not in stateless mode, no delimiter expected */
+       if (packet_reader_read(reader) != PACKET_READ_RESPONSE_END)
+               die("%s", error);
+}
+
 struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
                             struct ref **list, int for_push,
                             const struct argv_array *ref_prefixes,
-                            const struct string_list *server_options)
+                            const struct string_list *server_options,
+                            int stateless_rpc)
 {
        int i;
        *list = NULL;
@@ -444,6 +457,9 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
        if (reader->status != PACKET_READ_FLUSH)
                die(_("expected flush after ref listing"));
 
+       check_stateless_delimiter(stateless_rpc, reader,
+                                 _("expected response end packet after ref listing"));
+
        return list;
 }
 
index 5f2382e01868042757901a419b5e5f34ad8bb279..235bc66254d4a0b8760cd828339fccee7fac1bd0 100644 (file)
--- a/connect.h
+++ b/connect.h
@@ -22,4 +22,8 @@ int server_supports_v2(const char *c, int die_on_error);
 int server_supports_feature(const char *c, const char *feature,
                            int die_on_error);
 
+void check_stateless_delimiter(int stateless_rpc,
+                              struct packet_reader *reader,
+                              const char *error);
+
 #endif
index 70ad04e1b2a8c65896c95c8e6bd34e963c74c687..de5d0fbbd1dccaf2db571ba6e442249d0b35995e 100644 (file)
@@ -301,6 +301,19 @@ __gitcomp_direct ()
        COMPREPLY=($1)
 }
 
+# Similar to __gitcomp_direct, but appends to COMPREPLY instead.
+# Callers must take care of providing only words that match the current word
+# to be completed and adding any prefix and/or suffix (trailing space!), if
+# necessary.
+# 1: List of newline-separated matching completion words, complete with
+#    prefix and suffix.
+__gitcomp_direct_append ()
+{
+       local IFS=$'\n'
+
+       COMPREPLY+=($1)
+}
+
 __gitcompappend ()
 {
        local x i=${#COMPREPLY[@]}
@@ -373,7 +386,7 @@ __gitcomp ()
 # Clear the variables caching builtins' options when (re-)sourcing
 # the completion script.
 if [[ -n ${ZSH_VERSION-} ]]; then
-       unset $(set |sed -ne 's/^\(__gitcomp_builtin_[a-zA-Z0-9_][a-zA-Z0-9_]*\)=.*/\1/p') 2>/dev/null
+       unset ${(M)${(k)parameters[@]}:#__gitcomp_builtin_*} 2>/dev/null
 else
        unset $(compgen -v __gitcomp_builtin_)
 fi
@@ -611,6 +624,19 @@ __git_heads ()
                        "refs/heads/$cur_*" "refs/heads/$cur_*/**"
 }
 
+# Lists branches from remote repositories.
+# 1: A prefix to be added to each listed branch (optional).
+# 2: List only branches matching this word (optional; list all branches if
+#    unset or empty).
+# 3: A suffix to be appended to each listed branch (optional).
+__git_remote_heads ()
+{
+       local pfx="${1-}" cur_="${2-}" sfx="${3-}"
+
+       __git for-each-ref --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \
+                       "refs/remotes/$cur_*" "refs/remotes/$cur_*/**"
+}
+
 # Lists tags from the local repository.
 # Accepts the same positional parameters as __git_heads() above.
 __git_tags ()
@@ -621,6 +647,26 @@ __git_tags ()
                        "refs/tags/$cur_*" "refs/tags/$cur_*/**"
 }
 
+# List unique branches from refs/remotes used for 'git checkout' and 'git
+# switch' tracking DWIMery.
+# 1: A prefix to be added to each listed branch (optional)
+# 2: List only branches matching this word (optional; list all branches if
+#    unset or empty).
+# 3: A suffix to be appended to each listed branch (optional).
+__git_dwim_remote_heads ()
+{
+       local pfx="${1-}" cur_="${2-}" sfx="${3-}"
+       local fer_pfx="${pfx//\%/%%}" # "escape" for-each-ref format specifiers
+
+       # employ the heuristic used by git checkout and git switch
+       # Try to find a remote branch that cur_es the completion word
+       # but only output if the branch name is unique
+       __git for-each-ref --format="$fer_pfx%(refname:strip=3)$sfx" \
+               --sort="refname:strip=3" \
+               "refs/remotes/*/$cur_*" "refs/remotes/*/$cur_*/**" | \
+       uniq -u
+}
+
 # Lists refs from the local (by default) or from a remote repository.
 # It accepts 0, 1 or 2 arguments:
 # 1: The remote to list refs from (optional; ignored, if set but empty).
@@ -696,13 +742,7 @@ __git_refs ()
                __git_dir="$dir" __git for-each-ref --format="$fer_pfx%($format)$sfx" \
                        "${refs[@]}"
                if [ -n "$track" ]; then
-                       # employ the heuristic used by git checkout
-                       # Try to find a remote branch that matches the completion word
-                       # but only output if the branch name is unique
-                       __git for-each-ref --format="$fer_pfx%(refname:strip=3)$sfx" \
-                               --sort="refname:strip=3" \
-                               "refs/remotes/*/$match*" "refs/remotes/*/$match*/**" | \
-                       uniq -u
+                       __git_dwim_remote_heads "$pfx" "$match" "$sfx"
                fi
                return
        fi
@@ -749,29 +789,51 @@ __git_refs ()
 # Usage: __git_complete_refs [<option>]...
 # --remote=<remote>: The remote to list refs from, can be the name of a
 #                    configured remote, a path, or a URL.
-# --track: List unique remote branches for 'git checkout's tracking DWIMery.
+# --dwim: List unique remote branches for 'git switch's tracking DWIMery.
 # --pfx=<prefix>: A prefix to be added to each ref.
 # --cur=<word>: The current ref to be completed.  Defaults to the current
 #               word to be completed.
 # --sfx=<suffix>: A suffix to be appended to each ref instead of the default
 #                 space.
+# --mode=<mode>: What set of refs to complete, one of 'refs' (the default) to
+#                complete all refs, 'heads' to complete only branches, or
+#                'remote-heads' to complete only remote branches. Note that
+#                --remote is only compatible with --mode=refs.
 __git_complete_refs ()
 {
-       local remote track pfx cur_="$cur" sfx=" "
+       local remote dwim pfx cur_="$cur" sfx=" " mode="refs"
 
        while test $# != 0; do
                case "$1" in
                --remote=*)     remote="${1##--remote=}" ;;
-               --track)        track="yes" ;;
+               --dwim)         dwim="yes" ;;
+               # --track is an old spelling of --dwim
+               --track)        dwim="yes" ;;
                --pfx=*)        pfx="${1##--pfx=}" ;;
                --cur=*)        cur_="${1##--cur=}" ;;
                --sfx=*)        sfx="${1##--sfx=}" ;;
+               --mode=*)       mode="${1##--mode=}" ;;
                *)              return 1 ;;
                esac
                shift
        done
 
-       __gitcomp_direct "$(__git_refs "$remote" "$track" "$pfx" "$cur_" "$sfx")"
+       # complete references based on the specified mode
+       case "$mode" in
+               refs)
+                       __gitcomp_direct "$(__git_refs "$remote" "" "$pfx" "$cur_" "$sfx")" ;;
+               heads)
+                       __gitcomp_direct "$(__git_heads "$pfx" "$cur_" "$sfx")" ;;
+               remote-heads)
+                       __gitcomp_direct "$(__git_remote_heads "$pfx" "$cur_" "$sfx")" ;;
+               *)
+                       return 1 ;;
+       esac
+
+       # Append DWIM remote branch names if requested
+       if [ "$dwim" = "yes" ]; then
+               __gitcomp_direct_append "$(__git_dwim_remote_heads "$pfx" "$cur_" "$sfx")"
+       fi
 }
 
 # __git_refs2 requires 1 argument (to pass to __git_refs)
@@ -1102,6 +1164,40 @@ __git_find_on_cmdline ()
        done
 }
 
+# Similar to __git_find_on_cmdline, except that it loops backwards and thus
+# prints the *last* word found. Useful for finding which of two options that
+# supersede each other came last, such as "--guess" and "--no-guess".
+#
+# Usage: __git_find_last_on_cmdline [<option>]... "<wordlist>"
+# --show-idx: Optionally show the index of the found word in the $words array.
+__git_find_last_on_cmdline ()
+{
+       local word c=$cword show_idx
+
+       while test $# -gt 1; do
+               case "$1" in
+               --show-idx)     show_idx=y ;;
+               *)              return 1 ;;
+               esac
+               shift
+       done
+       local wordlist="$1"
+
+       while [ $c -gt 1 ]; do
+               ((c--))
+               for word in $wordlist; do
+                       if [ "$word" = "${words[c]}" ]; then
+                               if [ -n "$show_idx" ]; then
+                                       echo "$c $word"
+                               else
+                                       echo "$word"
+                               fi
+                               return
+                       fi
+               done
+       done
+}
+
 # Echo the value of an option set on the command line or config
 #
 # $1: short option name
@@ -1356,6 +1452,46 @@ _git_bundle ()
        esac
 }
 
+# Helper function to decide whether or not we should enable DWIM logic for
+# git-switch and git-checkout.
+#
+# To decide between the following rules in priority order
+# 1) the last provided of "--guess" or "--no-guess" explicitly enable or
+#    disable completion of DWIM logic respectively.
+# 2) If the --no-track option is provided, take this as a hint to disable the
+#    DWIM completion logic
+# 3) If GIT_COMPLETION_CHECKOUT_NO_GUESS is set, disable the DWIM completion
+#    logic, as requested by the user.
+# 4) Enable DWIM logic otherwise.
+#
+__git_checkout_default_dwim_mode ()
+{
+       local last_option dwim_opt="--dwim"
+
+       if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ]; then
+               dwim_opt=""
+       fi
+
+       # --no-track disables DWIM, but with lower priority than
+       # --guess/--no-guess
+       if [ -n "$(__git_find_on_cmdline "--no-track")" ]; then
+               dwim_opt=""
+       fi
+
+       # Find the last provided --guess or --no-guess
+       last_option="$(__git_find_last_on_cmdline "--guess --no-guess")"
+       case "$last_option" in
+               --guess)
+                       dwim_opt="--dwim"
+                       ;;
+               --no-guess)
+                       dwim_opt=""
+                       ;;
+       esac
+
+       echo "$dwim_opt"
+}
+
 _git_checkout ()
 {
        __git_has_doubledash && return
@@ -1368,14 +1504,38 @@ _git_checkout ()
                __gitcomp_builtin checkout
                ;;
        *)
-               # check if --track, --no-track, or --no-guess was specified
-               # if so, disable DWIM mode
-               local flags="--track --no-track --no-guess" track_opt="--track"
-               if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ] ||
-                  [ -n "$(__git_find_on_cmdline "$flags")" ]; then
-                       track_opt=''
+               local dwim_opt="$(__git_checkout_default_dwim_mode)"
+               local prevword prevword="${words[cword-1]}"
+
+               case "$prevword" in
+                       -b|-B|--orphan)
+                               # Complete local branches (and DWIM branch
+                               # remote branch names) for an option argument
+                               # specifying a new branch name. This is for
+                               # convenience, assuming new branches are
+                               # possibly based on pre-existing branch names.
+                               __git_complete_refs $dwim_opt --mode="heads"
+                               return
+                               ;;
+                       *)
+                               ;;
+               esac
+
+               # At this point, we've already handled special completion for
+               # the arguments to -b/-B, and --orphan. There are 3 main
+               # things left we can possibly complete:
+               # 1) a start-point for -b/-B, -d/--detach, or --orphan
+               # 2) a remote head, for --track
+               # 3) an arbitrary reference, possibly including DWIM names
+               #
+
+               if [ -n "$(__git_find_on_cmdline "-b -B -d --detach --orphan")" ]; then
+                       __git_complete_refs --mode="refs"
+               elif [ -n "$(__git_find_on_cmdline "--track")" ]; then
+                       __git_complete_refs --mode="remote-heads"
+               else
+                       __git_complete_refs $dwim_opt --mode="refs"
                fi
-               __git_complete_refs $track_opt
                ;;
        esac
 }
@@ -2224,29 +2384,43 @@ _git_switch ()
                __gitcomp_builtin switch
                ;;
        *)
-               # check if --track, --no-track, or --no-guess was specified
-               # if so, disable DWIM mode
-               local track_opt="--track" only_local_ref=n
-               if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ] ||
-                  [ -n "$(__git_find_on_cmdline "--track --no-track --no-guess")" ]; then
-                       track_opt=''
-               fi
-               # explicit --guess enables DWIM mode regardless of
-               # $GIT_COMPLETION_CHECKOUT_NO_GUESS
-               if [ -n "$(__git_find_on_cmdline "--guess")" ]; then
-                       track_opt='--track'
-               fi
-               if [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then
-                       only_local_ref=y
-               else
-                       # --guess --detach is invalid combination, no
-                       # dwim will be done when --detach is specified
-                       track_opt=
+               local dwim_opt="$(__git_checkout_default_dwim_mode)"
+               local prevword prevword="${words[cword-1]}"
+
+               case "$prevword" in
+                       -c|-C|--orphan)
+                               # Complete local branches (and DWIM branch
+                               # remote branch names) for an option argument
+                               # specifying a new branch name. This is for
+                               # convenience, assuming new branches are
+                               # possibly based on pre-existing branch names.
+                               __git_complete_refs $dwim_opt --mode="heads"
+                               return
+                               ;;
+                       *)
+                               ;;
+               esac
+
+               # Unlike in git checkout, git switch --orphan does not take
+               # a start point. Thus we really have nothing to complete after
+               # the branch name.
+               if [ -n "$(__git_find_on_cmdline "--orphan")" ]; then
+                       return
                fi
-               if [ $only_local_ref = y -a -z "$track_opt" ]; then
-                       __gitcomp_direct "$(__git_heads "" "$cur" " ")"
+
+               # At this point, we've already handled special completion for
+               # -c/-C, and --orphan. There are 3 main things left to
+               # complete:
+               # 1) a start-point for -c/-C or -d/--detach
+               # 2) a remote head, for --track
+               # 3) a branch name, possibly including DWIM remote branches
+
+               if [ -n "$(__git_find_on_cmdline "-c -C -d --detach")" ]; then
+                       __git_complete_refs --mode="refs"
+               elif [ -n "$(__git_find_on_cmdline "--track")" ]; then
+                       __git_complete_refs --mode="remote-heads"
                else
-                       __git_complete_refs $track_opt
+                       __git_complete_refs $dwim_opt --mode="heads"
                fi
                ;;
        esac
@@ -2782,7 +2956,7 @@ _git_stash ()
        local save_opts='--all --keep-index --no-keep-index --quiet --patch --include-untracked'
        local subcommands='push list show apply clear drop pop create branch'
        local subcommand="$(__git_find_on_cmdline "$subcommands save")"
-       if [ -n "$(__git_find_on_cmdline "-p")" ]; then
+       if [ -z "$subcommand" -a -n "$(__git_find_on_cmdline "-p")" ]; then
                subcommand="push"
        fi
        if [ -z "$subcommand" ]; then
diff --git a/diff.c b/diff.c
index d1ad6a3c4ad0bc5049d1af26ca0ea33fc9f68a1e..d24aaa304780b7105618434d80a76627e588d280 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -48,6 +48,7 @@ static const char *diff_order_file_cfg;
 int diff_auto_refresh_index = 1;
 static int diff_mnemonic_prefix;
 static int diff_no_prefix;
+static int diff_relative;
 static int diff_stat_graph_width;
 static int diff_dirstat_permille_default = 30;
 static struct diff_options default_diff_options;
@@ -386,6 +387,10 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
                diff_no_prefix = git_config_bool(var, value);
                return 0;
        }
+       if (!strcmp(var, "diff.relative")) {
+               diff_relative = git_config_bool(var, value);
+               return 0;
+       }
        if (!strcmp(var, "diff.statgraphwidth")) {
                diff_stat_graph_width = git_config_int(var, value);
                return 0;
@@ -4538,6 +4543,7 @@ void repo_diff_setup(struct repository *r, struct diff_options *options)
        options->interhunkcontext = diff_interhunk_context_default;
        options->ws_error_highlight = ws_error_highlight_default;
        options->flags.rename_empty = 1;
+       options->flags.relative_name = diff_relative;
        options->objfind = NULL;
 
        /* pathchange left =NULL by default */
@@ -5195,8 +5201,7 @@ static int diff_opt_relative(const struct option *opt,
 {
        struct diff_options *options = opt->value;
 
-       BUG_ON_OPT_NEG(unset);
-       options->flags.relative_name = 1;
+       options->flags.relative_name = !unset;
        if (arg)
                options->prefix = arg;
        return 0;
@@ -5492,7 +5497,7 @@ static void prep_parse_options(struct diff_options *options)
                OPT_GROUP(N_("Other diff options")),
                OPT_CALLBACK_F(0, "relative", options, N_("<prefix>"),
                               N_("when run from subdir, exclude changes outside and show relative paths"),
-                              PARSE_OPT_NONEG | PARSE_OPT_OPTARG,
+                              PARSE_OPT_OPTARG,
                               diff_opt_relative),
                OPT_BOOL('a', "text", &options->flags.text,
                         N_("treat all files as text")),
@@ -6758,8 +6763,11 @@ void diff_change(struct diff_options *options,
                return;
 
        if (options->flags.quick && options->skip_stat_unmatch &&
-           !diff_filespec_check_stat_unmatch(options->repo, p))
+           !diff_filespec_check_stat_unmatch(options->repo, p)) {
+               diff_free_filespec_data(p->one);
+               diff_free_filespec_data(p->two);
                return;
+       }
 
        options->flags.has_changes = 1;
 }
diff --git a/dir.c b/dir.c
index a3ad9702d646649477a37ce94a1f3b6c22e27f21..1045cc9c6f58702aba6e9036a8bce91a431c108a 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -368,7 +368,8 @@ static int match_pathspec_item(const struct index_state *istate,
                return MATCHED_FNMATCH;
 
        /* Perform checks to see if "name" is a leading string of the pathspec */
-       if (flags & DO_MATCH_LEADING_PATHSPEC) {
+       if ( (flags & DO_MATCH_LEADING_PATHSPEC) &&
+           !(flags & DO_MATCH_EXCLUDE)) {
                /* name is a literal prefix of the pathspec */
                int offset = name[namelen-1] == '/' ? 1 : 0;
                if ((namelen < matchlen) &&
@@ -405,6 +406,10 @@ static int match_pathspec_item(const struct index_state *istate,
 }
 
 /*
+ * do_match_pathspec() is meant to ONLY be called by
+ * match_pathspec_with_flags(); calling it directly risks pathspecs
+ * like ':!unwanted_path' being ignored.
+ *
  * Given a name and a list of pathspecs, returns the nature of the
  * closest (i.e. most specific) match of the name to any of the
  * pathspecs.
@@ -490,13 +495,12 @@ static int do_match_pathspec(const struct index_state *istate,
        return retval;
 }
 
-int match_pathspec(const struct index_state *istate,
-                  const struct pathspec *ps,
-                  const char *name, int namelen,
-                  int prefix, char *seen, int is_dir)
+static int match_pathspec_with_flags(const struct index_state *istate,
+                                    const struct pathspec *ps,
+                                    const char *name, int namelen,
+                                    int prefix, char *seen, unsigned flags)
 {
        int positive, negative;
-       unsigned flags = is_dir ? DO_MATCH_DIRECTORY : 0;
        positive = do_match_pathspec(istate, ps, name, namelen,
                                     prefix, seen, flags);
        if (!(ps->magic & PATHSPEC_EXCLUDE) || !positive)
@@ -507,6 +511,16 @@ int match_pathspec(const struct index_state *istate,
        return negative ? 0 : positive;
 }
 
+int match_pathspec(const struct index_state *istate,
+                  const struct pathspec *ps,
+                  const char *name, int namelen,
+                  int prefix, char *seen, int is_dir)
+{
+       unsigned flags = is_dir ? DO_MATCH_DIRECTORY : 0;
+       return match_pathspec_with_flags(istate, ps, name, namelen,
+                                        prefix, seen, flags);
+}
+
 /**
  * Check if a submodule is a superset of the pathspec
  */
@@ -515,11 +529,11 @@ int submodule_path_match(const struct index_state *istate,
                         const char *submodule_name,
                         char *seen)
 {
-       int matched = do_match_pathspec(istate, ps, submodule_name,
-                                       strlen(submodule_name),
-                                       0, seen,
-                                       DO_MATCH_DIRECTORY |
-                                       DO_MATCH_LEADING_PATHSPEC);
+       int matched = match_pathspec_with_flags(istate, ps, submodule_name,
+                                               strlen(submodule_name),
+                                               0, seen,
+                                               DO_MATCH_DIRECTORY |
+                                               DO_MATCH_LEADING_PATHSPEC);
        return matched;
 }
 
@@ -1761,9 +1775,11 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
         * for matching patterns.
         */
        if (pathspec && !excluded) {
-               matches_how = do_match_pathspec(istate, pathspec, dirname, len,
-                                               0 /* prefix */, NULL /* seen */,
-                                               DO_MATCH_LEADING_PATHSPEC);
+               matches_how = match_pathspec_with_flags(istate, pathspec,
+                                                       dirname, len,
+                                                       0 /* prefix */,
+                                                       NULL /* seen */,
+                                                       DO_MATCH_LEADING_PATHSPEC);
                if (!matches_how)
                        return path_none;
        }
@@ -2196,9 +2212,9 @@ static enum path_treatment treat_path(struct dir_struct *dir,
                if (excluded)
                        return path_excluded;
                if (pathspec &&
-                   !do_match_pathspec(istate, pathspec, path->buf, path->len,
-                                      0 /* prefix */, NULL /* seen */,
-                                      0 /* flags */))
+                   !match_pathspec(istate, pathspec, path->buf, path->len,
+                                   0 /* prefix */, NULL /* seen */,
+                                   0 /* is_dir */))
                        return path_none;
                return path_untracked;
        }
index c98970274c4056178b0e9a37e3ba38b323707b50..0dfa14dc8c3c07bfb8f19275d2366a5f63576212 100644 (file)
@@ -139,6 +139,7 @@ struct hash_list {
 
 typedef enum {
        WHENSPEC_RAW = 1,
+       WHENSPEC_RAW_PERMISSIVE,
        WHENSPEC_RFC2822,
        WHENSPEC_NOW
 } whenspec_type;
@@ -1911,7 +1912,7 @@ static int parse_data(struct strbuf *sb, uintmax_t limit, uintmax_t *len_res)
        return 1;
 }
 
-static int validate_raw_date(const char *src, struct strbuf *result)
+static int validate_raw_date(const char *src, struct strbuf *result, int strict)
 {
        const char *orig_src = src;
        char *endp;
@@ -1920,7 +1921,11 @@ static int validate_raw_date(const char *src, struct strbuf *result)
        errno = 0;
 
        num = strtoul(src, &endp, 10);
-       /* NEEDSWORK: perhaps check for reasonable values? */
+       /*
+        * NEEDSWORK: perhaps check for reasonable values? For example, we
+        *            could error on values representing times more than a
+        *            day in the future.
+        */
        if (errno || endp == src || *endp != ' ')
                return -1;
 
@@ -1929,7 +1934,13 @@ static int validate_raw_date(const char *src, struct strbuf *result)
                return -1;
 
        num = strtoul(src + 1, &endp, 10);
-       if (errno || endp == src + 1 || *endp || 1400 < num)
+       /*
+        * NEEDSWORK: check for brokenness other than num > 1400, such as
+        *            (num % 100) >= 60, or ((num % 100) % 15) != 0 ?
+        */
+       if (errno || endp == src + 1 || *endp || /* did not parse */
+           (strict && (1400 < num))             /* parsed a broken timezone */
+          )
                return -1;
 
        strbuf_addstr(result, orig_src);
@@ -1963,7 +1974,11 @@ static char *parse_ident(const char *buf)
 
        switch (whenspec) {
        case WHENSPEC_RAW:
-               if (validate_raw_date(ltgt, &ident) < 0)
+               if (validate_raw_date(ltgt, &ident, 1) < 0)
+                       die("Invalid raw date \"%s\" in ident: %s", ltgt, buf);
+               break;
+       case WHENSPEC_RAW_PERMISSIVE:
+               if (validate_raw_date(ltgt, &ident, 0) < 0)
                        die("Invalid raw date \"%s\" in ident: %s", ltgt, buf);
                break;
        case WHENSPEC_RFC2822:
@@ -3258,6 +3273,8 @@ static void option_date_format(const char *fmt)
 {
        if (!strcmp(fmt, "raw"))
                whenspec = WHENSPEC_RAW;
+       else if (!strcmp(fmt, "raw-permissive"))
+               whenspec = WHENSPEC_RAW_PERMISSIVE;
        else if (!strcmp(fmt, "rfc2822"))
                whenspec = WHENSPEC_RFC2822;
        else if (!strcmp(fmt, "now"))
index 7eaa19d7c17abeb4d2975d112a79762507a81009..d8bbf45ee27a732469287f99768321640f9d992a 100644 (file)
@@ -1451,6 +1451,13 @@ enum fetch_state {
        FETCH_DONE,
 };
 
+static void do_check_stateless_delimiter(const struct fetch_pack_args *args,
+                                        struct packet_reader *reader)
+{
+       check_stateless_delimiter(args->stateless_rpc, reader,
+                                 _("git fetch-pack: expected response end packet"));
+}
+
 static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
                                    int fd[2],
                                    const struct ref *orig_ref,
@@ -1535,6 +1542,10 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
                        /* Process ACKs/NAKs */
                        switch (process_acks(negotiator, &reader, &common)) {
                        case READY:
+                               /*
+                                * Don't check for response delimiter; get_pack() will
+                                * read the rest of this response.
+                                */
                                state = FETCH_GET_PACK;
                                break;
                        case COMMON_FOUND:
@@ -1542,6 +1553,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
                                seen_ack = 1;
                                /* fallthrough */
                        case NO_COMMON_FOUND:
+                               do_check_stateless_delimiter(args, &reader);
                                state = FETCH_SEND_REQUEST;
                                break;
                        }
@@ -1561,6 +1573,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
                        process_section_header(&reader, "packfile", 0);
                        if (get_pack(args, fd, pack_lockfile, sought, nr_sought))
                                die(_("git fetch-pack: fetch failed."));
+                       do_check_stateless_delimiter(args, &reader);
 
                        state = FETCH_DONE;
                        break;
diff --git a/fsck.c b/fsck.c
index 8bb3ecf282563e3ae318f27b89bd9a707bef3850..f82e2fe9e302fed2e9ebf44c0323628cb94576f0 100644 (file)
--- a/fsck.c
+++ b/fsck.c
@@ -598,7 +598,7 @@ static int verify_ordered(unsigned mode1, const char *name1,
 
        /*
         * There can be non-consecutive duplicates due to the implicitly
-        * add slash, e.g.:
+        * added slash, e.g.:
         *
         *   foo
         *   foo.bar
@@ -620,7 +620,7 @@ static int verify_ordered(unsigned mode1, const char *name1,
                        if (!f_name)
                                break;
                        if (!skip_prefix(name2, f_name, &p))
-                               break;
+                               continue;
                        if (!*p)
                                return TREE_HAS_DUPS;
                        if (is_less_than_slash(*p)) {
index 9fd1c04edd3108917ebae6e83840e1a2383606a9..430817214dcbfaadb35a3505b84a9dafa45865c8 100644 (file)
@@ -12,7 +12,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
        initialize_the_repository();
        g = parse_commit_graph((void *)data, size);
        repo_clear(the_repository);
-       free(g);
+       free_commit_graph(g);
 
        return 0;
 }
index 10fd30ae16a3bdf943732d07bc33c0a723b5e344..f36c0078ac9a71758a7e1cab701d3a633b66c0d3 100755 (executable)
@@ -754,16 +754,18 @@ sub parse_diff_header {
        my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
        my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
        my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };
+       my $addition = { TEXT => [], DISPLAY => [], TYPE => 'addition' };
 
        for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
                my $dest =
                   $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
                   $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
+                  $src->{TEXT}->[$i] =~ /^new file/ ? $addition :
                   $head;
                push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
                push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
        }
-       return ($head, $mode, $deletion);
+       return ($head, $mode, $deletion, $addition);
 }
 
 sub hunk_splittable {
@@ -1427,46 +1429,55 @@ my %patch_update_prompt_modes = (
        stage => {
                mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
                deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
+               addition => N__("Stage addition [y,n,q,a,d%s,?]? "),
                hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
        },
        stash => {
                mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
                deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
+               addition => N__("Stash addition [y,n,q,a,d%s,?]? "),
                hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
        },
        reset_head => {
                mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
                deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
+               addition => N__("Unstage addition [y,n,q,a,d%s,?]? "),
                hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
        },
        reset_nothead => {
                mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "),
                deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "),
+               addition => N__("Apply addition to index [y,n,q,a,d%s,?]? "),
                hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
        },
        checkout_index => {
                mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
                deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
+               addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "),
                hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
        },
        checkout_head => {
                mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
                deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
+               addition => N__("Discard addition from index and worktree [y,n,q,a,d%s,?]? "),
                hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
        },
        checkout_nothead => {
                mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
                deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
+               addition => N__("Apply addition to index and worktree [y,n,q,a,d%s,?]? "),
                hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
        },
        worktree_head => {
                mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
                deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
+               addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "),
                hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
        },
        worktree_nothead => {
                mode => N__("Apply mode change to worktree [y,n,q,a,d%s,?]? "),
                deletion => N__("Apply deletion to worktree [y,n,q,a,d%s,?]? "),
+               addition => N__("Apply addition to worktree [y,n,q,a,d%s,?]? "),
                hunk => N__("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
        },
 );
@@ -1476,7 +1487,7 @@ sub patch_update_file {
        my ($ix, $num);
        my $path = shift;
        my ($head, @hunk) = parse_diff($path);
-       ($head, my $mode, my $deletion) = parse_diff_header($head);
+       ($head, my $mode, my $deletion, my $addition) = parse_diff_header($head);
        for (@{$head->{DISPLAY}}) {
                print;
        }
@@ -1490,6 +1501,12 @@ sub patch_update_file {
                        push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
                }
                @hunk = ($deletion);
+       } elsif (@{$addition->{TEXT}}) {
+               foreach my $hunk (@hunk) {
+                       push @{$addition->{TEXT}}, @{$hunk->{TEXT}};
+                       push @{$addition->{DISPLAY}}, @{$hunk->{DISPLAY}};
+               }
+               @hunk = ($addition);
        }
 
        $num = scalar @hunk;
index d551efb0dd11f9caccff277594e2cb628e719e77..ca79dc09004feddbaec9c7521e5013bc8de3d586 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -2537,11 +2537,12 @@ class P4Submit(Command, P4UserMap):
                 ok = self.applyCommit(commit)
             if ok:
                 applied.append(commit)
-            else:
-                if self.prepare_p4_only and i < last:
-                    print("Processing only the first commit due to option" \
-                          " --prepare-p4-only")
+                if self.prepare_p4_only:
+                    if i < last:
+                        print("Processing only the first commit due to option" \
+                                " --prepare-p4-only")
                     break
+            else:
                 if i < last:
                     # prompt for what to do, or use the option/variable
                     if self.conflict_behavior == "ask":
diff --git a/help.c b/help.c
index 1de9c0d589cd9b615b1b20d60e06b9601d08fe8f..44cee69c11c68381fbdc12a3fb6b7f5bae7f4dfe 100644 (file)
--- a/help.c
+++ b/help.c
@@ -641,6 +641,7 @@ void get_version_info(struct strbuf *buf, int show_build_options)
                        strbuf_addstr(buf, "no commit associated with this build\n");
                strbuf_addf(buf, "sizeof-long: %d\n", (int)sizeof(long));
                strbuf_addf(buf, "sizeof-size_t: %d\n", (int)sizeof(size_t));
+               strbuf_addf(buf, "shell-path: %s\n", SHELL_PATH);
                /* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */
        }
 }
diff --git a/http.c b/http.c
index 62aa995245324dc30fec699ed8c570af7b152b8f..0eb1931a151c9361067cd79ed72a528e549863f2 100644 (file)
--- a/http.c
+++ b/http.c
@@ -18,7 +18,7 @@
 
 static struct trace_key trace_curl = TRACE_KEY_INIT(CURL);
 static int trace_curl_data = 1;
-static struct string_list cookies_to_redact = STRING_LIST_INIT_DUP;
+static int trace_curl_redact = 1;
 #if LIBCURL_VERSION_NUM >= 0x070a08
 long int git_curl_ipresolve = CURL_IPRESOLVE_WHATEVER;
 #else
@@ -642,8 +642,9 @@ static void redact_sensitive_header(struct strbuf *header)
 {
        const char *sensitive_header;
 
-       if (skip_prefix(header->buf, "Authorization:", &sensitive_header) ||
-           skip_prefix(header->buf, "Proxy-Authorization:", &sensitive_header)) {
+       if (trace_curl_redact &&
+           (skip_prefix(header->buf, "Authorization:", &sensitive_header) ||
+            skip_prefix(header->buf, "Proxy-Authorization:", &sensitive_header))) {
                /* The first token is the type, which is OK to log */
                while (isspace(*sensitive_header))
                        sensitive_header++;
@@ -652,20 +653,15 @@ static void redact_sensitive_header(struct strbuf *header)
                /* Everything else is opaque and possibly sensitive */
                strbuf_setlen(header,  sensitive_header - header->buf);
                strbuf_addstr(header, " <redacted>");
-       } else if (cookies_to_redact.nr &&
+       } else if (trace_curl_redact &&
                   skip_prefix(header->buf, "Cookie:", &sensitive_header)) {
                struct strbuf redacted_header = STRBUF_INIT;
-               char *cookie;
+               const char *cookie;
 
                while (isspace(*sensitive_header))
                        sensitive_header++;
 
-               /*
-                * The contents of header starting from sensitive_header will
-                * subsequently be overridden, so it is fine to mutate this
-                * string (hence the assignment to "char *").
-                */
-               cookie = (char *) sensitive_header;
+               cookie = sensitive_header;
 
                while (cookie) {
                        char *equals;
@@ -678,14 +674,8 @@ static void redact_sensitive_header(struct strbuf *header)
                                strbuf_addstr(&redacted_header, cookie);
                                continue;
                        }
-                       *equals = 0; /* temporarily set to NUL for lookup */
-                       if (string_list_lookup(&cookies_to_redact, cookie)) {
-                               strbuf_addstr(&redacted_header, cookie);
-                               strbuf_addstr(&redacted_header, "=<redacted>");
-                       } else {
-                               *equals = '=';
-                               strbuf_addstr(&redacted_header, cookie);
-                       }
+                       strbuf_add(&redacted_header, cookie, equals - cookie);
+                       strbuf_addstr(&redacted_header, "=<redacted>");
                        if (semicolon) {
                                /*
                                 * There are more cookies. (Or, for some
@@ -804,6 +794,12 @@ static int curl_trace(CURL *handle, curl_infotype type, char *data, size_t size,
        return 0;
 }
 
+void http_trace_curl_no_data(void)
+{
+       trace_override_envvar(&trace_curl, "1");
+       trace_curl_data = 0;
+}
+
 void setup_curl_trace(CURL *handle)
 {
        if (!trace_want(&trace_curl))
@@ -993,15 +989,12 @@ static CURL *get_curl_handle(void)
        warning(_("Protocol restrictions not supported with cURL < 7.19.4"));
 #endif
        if (getenv("GIT_CURL_VERBOSE"))
-               curl_easy_setopt(result, CURLOPT_VERBOSE, 1L);
+               http_trace_curl_no_data();
        setup_curl_trace(result);
        if (getenv("GIT_TRACE_CURL_NO_DATA"))
                trace_curl_data = 0;
-       if (getenv("GIT_REDACT_COOKIES")) {
-               string_list_split(&cookies_to_redact,
-                                 getenv("GIT_REDACT_COOKIES"), ',', -1);
-               string_list_sort(&cookies_to_redact);
-       }
+       if (!git_env_bool("GIT_TRACE_REDACT", 1))
+               trace_curl_redact = 0;
 
        curl_easy_setopt(result, CURLOPT_USERAGENT,
                user_agent ? user_agent : git_user_agent());
diff --git a/http.h b/http.h
index 5e0ad724f92f3c708d2b330bb689f6eef6bb8de4..faf8cbb0d10e44e036542a15aa6684a5eb7416cc 100644 (file)
--- a/http.h
+++ b/http.h
@@ -252,6 +252,13 @@ int finish_http_object_request(struct http_object_request *freq);
 void abort_http_object_request(struct http_object_request *freq);
 void release_http_object_request(struct http_object_request *freq);
 
+/*
+ * Instead of using environment variables to determine if curl tracing happens,
+ * behave as if GIT_TRACE_CURL=1 and GIT_TRACE_CURL_NO_DATA=1 is set. Call this
+ * before calling setup_curl_trace().
+ */
+void http_trace_curl_no_data(void);
+
 /* setup routine for curl_easy_setopt CURLOPT_DEBUGFUNCTION */
 void setup_curl_trace(CURL *handle);
 #endif /* HTTP_H */
index 6c54d8c29d64c0ea89943494986513c1054065f4..52737546f38b65f1cba1043dbda349d6680b78b6 100644 (file)
@@ -1464,7 +1464,7 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
        curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
 
        if (0 < verbosity || getenv("GIT_CURL_VERBOSE"))
-               curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+               http_trace_curl_no_data();
        setup_curl_trace(curl);
 
        return curl;
index 40e1738dbb3817aaf59df68a7c967c43548f4f2f..c53692834d858cb602431f6a15c30203615e3b0a 100644 (file)
@@ -15,6 +15,7 @@
 #include "userdiff.h"
 #include "line-log.h"
 #include "argv-array.h"
+#include "bloom.h"
 
 static void range_set_grow(struct range_set *rs, size_t extra)
 {
@@ -1146,6 +1147,37 @@ int line_log_print(struct rev_info *rev, struct commit *commit)
        return 1;
 }
 
+static int bloom_filter_check(struct rev_info *rev,
+                             struct commit *commit,
+                             struct line_log_data *range)
+{
+       struct bloom_filter *filter;
+       struct bloom_key key;
+       int result = 0;
+
+       if (!commit->parents)
+               return 1;
+
+       if (!rev->bloom_filter_settings ||
+           !(filter = get_bloom_filter(rev->repo, commit, 0)))
+               return 1;
+
+       if (!range)
+               return 0;
+
+       while (!result && range) {
+               fill_bloom_key(range->path, strlen(range->path), &key, rev->bloom_filter_settings);
+
+               if (bloom_filter_contains(filter, &key, rev->bloom_filter_settings))
+                       result = 1;
+
+               clear_bloom_key(&key);
+               range = range->next;
+       }
+
+       return result;
+}
+
 static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *commit,
                                          struct line_log_data *range)
 {
@@ -1159,6 +1191,7 @@ static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *c
 
        queue_diffs(range, &rev->diffopt, &queue, commit, parent);
        changed = process_all_files(&parent_range, rev, &queue, range);
+
        if (parent)
                add_line_range(rev, parent, parent_range);
        free_line_log_data(parent_range);
@@ -1227,13 +1260,17 @@ static int process_ranges_merge_commit(struct rev_info *rev, struct commit *comm
        /* NEEDSWORK leaking like a sieve */
 }
 
-static int process_ranges_arbitrary_commit(struct rev_info *rev, struct commit *commit)
+int line_log_process_ranges_arbitrary_commit(struct rev_info *rev, struct commit *commit)
 {
        struct line_log_data *range = lookup_line_range(rev, commit);
        int changed = 0;
 
        if (range) {
-               if (!commit->parents || !commit->parents->next)
+               if (commit->parents && !bloom_filter_check(rev, commit, range)) {
+                       struct line_log_data *prange = line_log_data_copy(range);
+                       add_line_range(rev, commit->parents->item, prange);
+                       clear_commit_line_range(rev, commit);
+               } else if (!commit->parents || !commit->parents->next)
                        changed = process_ranges_ordinary_commit(rev, commit, range);
                else
                        changed = process_ranges_merge_commit(rev, commit, range);
@@ -1270,7 +1307,7 @@ int line_log_filter(struct rev_info *rev)
        while (list) {
                struct commit_list *to_free = NULL;
                commit = list->item;
-               if (process_ranges_arbitrary_commit(rev, commit)) {
+               if (line_log_process_ranges_arbitrary_commit(rev, commit)) {
                        *pp = list;
                        pp = &list->next;
                } else
index 8ee7a2bd4a1866be85d1cbe412be83fcbc5cc42a..82ae8d98a403bb644eede373a8def1a37a811fdd 100644 (file)
@@ -46,10 +46,7 @@ void sort_and_merge_range_set(struct range_set *);
 struct line_log_data {
        struct line_log_data *next;
        char *path;
-       char status;
        struct range_set ranges;
-       int arg_alloc, arg_nr;
-       const char **args;
        struct diff_filepair *pair;
        struct diff_ranges diff;
 };
@@ -57,6 +54,8 @@ struct line_log_data {
 void line_log_init(struct rev_info *rev, const char *prefix, struct string_list *args);
 
 int line_log_filter(struct rev_info *rev);
+int line_log_process_ranges_arbitrary_commit(struct rev_info *rev,
+                                                   struct commit *commit);
 
 int line_log_print(struct rev_info *rev, struct commit *commit);
 
index a0e87b1e81408e17b4bb8e99bc162b6e7bce4012..8f9bc68ee28ef46b2cd221a46a2049bf8a633511 100644 (file)
@@ -99,6 +99,13 @@ void packet_delim(int fd)
                die_errno(_("unable to write delim packet"));
 }
 
+void packet_response_end(int fd)
+{
+       packet_trace("0002", 4, 1);
+       if (write_in_full(fd, "0002", 4) < 0)
+               die_errno(_("unable to write stateless separator packet"));
+}
+
 int packet_flush_gently(int fd)
 {
        packet_trace("0000", 4, 1);
@@ -306,10 +313,10 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,
        return ret;
 }
 
-static int packet_length(const char *linelen)
+int packet_length(const char lenbuf_hex[4])
 {
-       int val = hex2chr(linelen);
-       return (val < 0) ? val : (val << 8) | hex2chr(linelen + 2);
+       int val = hex2chr(lenbuf_hex);
+       return (val < 0) ? val : (val << 8) | hex2chr(lenbuf_hex + 2);
 }
 
 enum packet_read_status packet_read_with_status(int fd, char **src_buffer,
@@ -337,6 +344,10 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,
                packet_trace("0001", 4, 0);
                *pktlen = 0;
                return PACKET_READ_DELIM;
+       } else if (len == 2) {
+               packet_trace("0002", 4, 0);
+               *pktlen = 0;
+               return PACKET_READ_RESPONSE_END;
        } else if (len < 4) {
                die(_("protocol error: bad line length %d"), len);
        }
index fef3a0d792d31bd04aa0145908db9dfb0c87c94d..5b373fe4cdaae2d1446fb86d4fb0762aaac764f5 100644 (file)
@@ -22,6 +22,7 @@
  */
 void packet_flush(int fd);
 void packet_delim(int fd);
+void packet_response_end(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_buf_delim(struct strbuf *buf);
@@ -74,6 +75,15 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);
 int packet_read(int fd, char **src_buffer, size_t *src_len, char
                *buffer, unsigned size, int options);
 
+/*
+ * Convert a four hex digit packet line length header into its numeric
+ * representation.
+ *
+ * If lenbuf_hex contains non-hex characters, return -1. Otherwise, return the
+ * numeric value of the length header.
+ */
+int packet_length(const char lenbuf_hex[4]);
+
 /*
  * Read a packetized line into a buffer like the 'packet_read()' function but
  * returns an 'enum packet_read_status' which indicates the status of the read.
@@ -85,6 +95,7 @@ enum packet_read_status {
        PACKET_READ_NORMAL,
        PACKET_READ_FLUSH,
        PACKET_READ_DELIM,
+       PACKET_READ_RESPONSE_END,
 };
 enum packet_read_status packet_read_with_status(int fd, char **src_buffer,
                                                size_t *src_len, char *buffer,
index d390391ebac80a7b9d506c592bbd32b24f9f250f..d1dd3424bbaa56e21950e6b95a56fcde87cd5450 100644 (file)
@@ -17,6 +17,7 @@ static enum protocol_version parse_protocol_version(const char *value)
 enum protocol_version get_protocol_version_config(void)
 {
        const char *value;
+       int val;
        const char *git_test_k = "GIT_TEST_PROTOCOL_VERSION";
        const char *git_test_v;
 
@@ -30,6 +31,9 @@ enum protocol_version get_protocol_version_config(void)
                return version;
        }
 
+       if (!git_config_get_bool("feature.experimental", &val) && val)
+               return protocol_v2;
+
        git_test_v = getenv(git_test_k);
        if (git_test_v && *git_test_v) {
                enum protocol_version env = parse_protocol_version(git_test_v);
diff --git a/refs.h b/refs.h
index a92d2c74c8306a25c0b6eab7624a06adae37a1b8..e010f8aec28aa47dddc5039178ac267d9aacfc5c 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -432,19 +432,35 @@ int delete_refs(const char *msg, struct string_list *refnames,
 int refs_delete_reflog(struct ref_store *refs, const char *refname);
 int delete_reflog(const char *refname);
 
-/* iterate over reflog entries */
+/*
+ * Callback to process a reflog entry found by the iteration functions (see
+ * below)
+ */
 typedef int each_reflog_ent_fn(
                struct object_id *old_oid, struct object_id *new_oid,
                const char *committer, timestamp_t timestamp,
                int tz, const char *msg, void *cb_data);
 
+/* Iterate over reflog entries in the log for `refname`. */
+
+/* oldest entry first */
 int refs_for_each_reflog_ent(struct ref_store *refs, const char *refname,
                             each_reflog_ent_fn fn, void *cb_data);
+
+/* youngest entry first */
 int refs_for_each_reflog_ent_reverse(struct ref_store *refs,
                                     const char *refname,
                                     each_reflog_ent_fn fn,
                                     void *cb_data);
+
+/*
+ * Iterate over reflog entries in the log for `refname` in the main ref store.
+ */
+
+/* oldest entry first */
 int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data);
+
+/* youngest entry first */
 int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data);
 
 /*
index ff2436c0fb706d8d34de926eca0f481b74b3170f..4271362d26458f8fffca016684d0b47f00307f75 100644 (file)
@@ -347,9 +347,13 @@ int is_empty_ref_iterator(struct ref_iterator *ref_iterator);
 /*
  * Return an iterator that goes over each reference in `refs` for
  * which the refname begins with prefix. If trim is non-zero, then
- * trim that many characters off the beginning of each refname. flags
- * can be DO_FOR_EACH_INCLUDE_BROKEN to include broken references in
- * the iteration. The output is ordered by refname.
+ * trim that many characters off the beginning of each refname.
+ * The output is ordered by refname. The following flags are supported:
+ *
+ * DO_FOR_EACH_INCLUDE_BROKEN: include broken references in
+ *         the iteration.
+ *
+ * DO_FOR_EACH_PER_WORKTREE_ONLY: only produce REF_TYPE_PER_WORKTREE refs.
  */
 struct ref_iterator *refs_ref_iterator_begin(
                struct ref_store *refs,
@@ -438,6 +442,14 @@ void base_ref_iterator_free(struct ref_iterator *iter);
 
 /* Virtual function declarations for ref_iterators: */
 
+/*
+ * backend-specific implementation of ref_iterator_advance. For symrefs, the
+ * function should set REF_ISSYMREF, and it should also dereference the symref
+ * to provide the OID referent. If DO_FOR_EACH_INCLUDE_BROKEN is set, symrefs
+ * with non-existent referents and refs pointing to non-existent object names
+ * should also be returned. If DO_FOR_EACH_PER_WORKTREE_ONLY, only
+ * REF_TYPE_PER_WORKTREE refs should be returned.
+ */
 typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator);
 
 typedef int ref_iterator_peel_fn(struct ref_iterator *ref_iterator,
index 1c9aa3d0ab978c1f98f1b4ff45143d765fcf6c27..75532a8baea8f20eb2c4aaa0f82c287a0dbaf617 100644 (file)
@@ -601,6 +601,8 @@ static int rpc_read_from_out(struct rpc_state *rpc, int options,
                case PACKET_READ_FLUSH:
                        memcpy(buf - 4, "0000", 4);
                        break;
+               case PACKET_READ_RESPONSE_END:
+                       die(_("remote server sent stateless separator"));
                }
        }
 
@@ -643,7 +645,7 @@ static size_t rpc_out(void *ptr, size_t eltsize,
                        return 0;
                }
                /*
-                * If avail is non-zerp, the line length for the flush still
+                * If avail is non-zero, the line length for the flush still
                 * hasn't been fully sent. Proceed with sending the line
                 * length.
                 */
@@ -679,9 +681,55 @@ static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp)
 }
 #endif
 
+struct check_pktline_state {
+       char len_buf[4];
+       int len_filled;
+       int remaining;
+};
+
+static void check_pktline(struct check_pktline_state *state, const char *ptr, size_t size)
+{
+       while (size) {
+               if (!state->remaining) {
+                       int digits_remaining = 4 - state->len_filled;
+                       if (digits_remaining > size)
+                               digits_remaining = size;
+                       memcpy(&state->len_buf[state->len_filled], ptr, digits_remaining);
+                       state->len_filled += digits_remaining;
+                       ptr += digits_remaining;
+                       size -= digits_remaining;
+
+                       if (state->len_filled == 4) {
+                               state->remaining = packet_length(state->len_buf);
+                               if (state->remaining < 0) {
+                                       die(_("remote-curl: bad line length character: %.4s"), state->len_buf);
+                               } else if (state->remaining == 2) {
+                                       die(_("remote-curl: unexpected response end packet"));
+                               } else if (state->remaining < 4) {
+                                       state->remaining = 0;
+                               } else {
+                                       state->remaining -= 4;
+                               }
+                               state->len_filled = 0;
+                       }
+               }
+
+               if (state->remaining) {
+                       int remaining = state->remaining;
+                       if (remaining > size)
+                               remaining = size;
+                       ptr += remaining;
+                       size -= remaining;
+                       state->remaining -= remaining;
+               }
+       }
+}
+
 struct rpc_in_data {
        struct rpc_state *rpc;
        struct active_request_slot *slot;
+       int check_pktline;
+       struct check_pktline_state pktline_state;
 };
 
 /*
@@ -702,6 +750,8 @@ static size_t rpc_in(char *ptr, size_t eltsize,
                return size;
        if (size)
                data->rpc->any_written = 1;
+       if (data->check_pktline)
+               check_pktline(&data->pktline_state, ptr, size);
        write_or_die(data->rpc->in, ptr, size);
        return size;
 }
@@ -778,7 +828,7 @@ static curl_off_t xcurl_off_t(size_t len)
  * If flush_received is true, do not attempt to read any more; just use what's
  * in rpc->buf.
  */
-static int post_rpc(struct rpc_state *rpc, int flush_received)
+static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_received)
 {
        struct active_request_slot *slot;
        struct curl_slist *headers = http_copy_default_headers();
@@ -920,6 +970,8 @@ retry:
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in);
        rpc_in_data.rpc = rpc;
        rpc_in_data.slot = slot;
+       rpc_in_data.check_pktline = stateless_connect;
+       memset(&rpc_in_data.pktline_state, 0, sizeof(rpc_in_data.pktline_state));
        curl_easy_setopt(slot->curl, CURLOPT_FILE, &rpc_in_data);
        curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0);
 
@@ -936,6 +988,14 @@ retry:
        if (!rpc->any_written)
                err = -1;
 
+       if (rpc_in_data.pktline_state.len_filled)
+               err = error(_("%d bytes of length header were received"), rpc_in_data.pktline_state.len_filled);
+       if (rpc_in_data.pktline_state.remaining)
+               err = error(_("%d bytes of body are still expected"), rpc_in_data.pktline_state.remaining);
+
+       if (stateless_connect)
+               packet_response_end(rpc->in);
+
        curl_slist_free_all(headers);
        free(gzip_body);
        return err;
@@ -985,7 +1045,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads,
                        break;
                rpc->pos = 0;
                rpc->len = n;
-               err |= post_rpc(rpc, 0);
+               err |= post_rpc(rpc, 0, 0);
        }
 
        close(client.in);
@@ -1276,7 +1336,7 @@ static void parse_push(struct strbuf *buf)
        if (ret)
                exit(128); /* error already reported */
 
- free_specs:
+free_specs:
        argv_array_clear(&specs);
 }
 
@@ -1342,7 +1402,7 @@ static int stateless_connect(const char *service_name)
                        BUG("The entire rpc->buf should be larger than LARGE_PACKET_MAX");
                if (status == PACKET_READ_EOF)
                        break;
-               if (post_rpc(&rpc, status == PACKET_READ_FLUSH))
+               if (post_rpc(&rpc, 1, status == PACKET_READ_FLUSH))
                        /* We would have an err here */
                        break;
                /* Reset the buffer for next request */
index 11d8719b587767bf4cdf0b5b1eccb9572da2658e..5cc26c1b3b3e1f1e2c97c24088f866de6840eab0 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -179,7 +179,8 @@ struct ref **get_remote_heads(struct packet_reader *reader,
 struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
                             struct ref **list, int for_push,
                             const struct argv_array *ref_prefixes,
-                            const struct string_list *server_options);
+                            const struct string_list *server_options,
+                            int stateless_rpc);
 
 int resolve_remote_symref(struct ref *ref, struct ref *list);
 
index 60cca8c0b9660e5c3e9927da99dee1fcda1c0069..ebb4d2a0f2e62e54d4680fc0968796db0ff92750 100644 (file)
@@ -39,6 +39,8 @@ static const char *term_good;
 
 implement_shared_commit_slab(revision_sources, char *);
 
+static inline int want_ancestry(const struct rev_info *revs);
+
 void show_object_with_name(FILE *out, struct object *obj, const char *name)
 {
        const char *p;
@@ -687,6 +689,9 @@ static void prepare_to_use_bloom_filter(struct rev_info *revs)
        if (!revs->bloom_filter_settings)
                return;
 
+       if (!revs->pruning.pathspec.nr)
+               return;
+
        pi = &revs->pruning.pathspec.items[0];
        last_index = pi->len - 1;
 
@@ -2810,6 +2815,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
        if (revs->diffopt.objfind)
                revs->simplify_history = 0;
 
+       if (revs->line_level_traverse) {
+               if (want_ancestry(revs))
+                       revs->limited = 1;
+               revs->topo_order = 1;
+       }
+
        if (revs->topo_order && !generation_numbers_enabled(the_repository))
                revs->limited = 1;
 
@@ -2829,11 +2840,6 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
 
        revs->diffopt.abbrev = revs->abbrev;
 
-       if (revs->line_level_traverse) {
-               revs->limited = 1;
-               revs->topo_order = 1;
-       }
-
        diff_setup_done(&revs->diffopt);
 
        grep_commit_pattern_type(GREP_PATTERN_TYPE_UNSPECIFIED,
@@ -3521,7 +3527,7 @@ int prepare_revision_walk(struct rev_info *revs)
                                       FOR_EACH_OBJECT_PROMISOR_ONLY);
        }
 
-       if (revs->pruning.pathspec.nr == 1 && !revs->reflog_info)
+       if (!revs->reflog_info)
                prepare_to_use_bloom_filter(revs);
        if (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED)
                commit_list_sort_by_date(&revs->commits);
@@ -3534,7 +3540,14 @@ int prepare_revision_walk(struct rev_info *revs)
                        sort_in_topological_order(&revs->commits, revs->sort_order);
        } else if (revs->topo_order)
                init_topo_walk(revs);
-       if (revs->line_level_traverse)
+       if (revs->line_level_traverse && want_ancestry(revs))
+               /*
+                * At the moment we can only do line-level log with parent
+                * rewriting by performing this expensive pre-filtering step.
+                * If parent rewriting is not requested, then we rather
+                * perform the line-level log filtering during the regular
+                * history traversal.
+                */
                line_log_filter(revs);
        if (revs->simplify_merges)
                simplify_merges(revs);
@@ -3745,6 +3758,22 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
                return commit_ignore;
        if (commit->object.flags & UNINTERESTING)
                return commit_ignore;
+       if (revs->line_level_traverse && !want_ancestry(revs)) {
+               /*
+                * In case of line-level log with parent rewriting
+                * prepare_revision_walk() already took care of all line-level
+                * log filtering, and there is nothing left to do here.
+                *
+                * If parent rewriting was not requested, then this is the
+                * place to perform the line-level log filtering.  Notably,
+                * this check, though expensive, must come before the other,
+                * cheaper filtering conditions, because the tracked line
+                * ranges must be adjusted even when the commit will end up
+                * being ignored based on other conditions.
+                */
+               if (!line_log_process_ranges_arbitrary_commit(revs, commit))
+                       return commit_ignore;
+       }
        if (revs->min_age != -1 &&
            comparison_date(revs, commit) > revs->min_age)
                        return commit_ignore;
diff --git a/serve.c b/serve.c
index 317256c1a493c4b2cc730a6212add4d1ef5696d4..c046926ba141c76fc2a6b6fb1db80e31c1c28e8c 100644 (file)
--- a/serve.c
+++ b/serve.c
@@ -217,6 +217,8 @@ static int process_request(void)
 
                        state = PROCESS_REQUEST_DONE;
                        break;
+               case PACKET_READ_RESPONSE_END:
+                       BUG("unexpected stateless separator packet");
                }
        }
 
index cf863837ab92992afb5ce3b562f9045bfb648c28..70ec61cf887fcacb3e7f9ffe09cee636caa37732 100644 (file)
--- a/t/README
+++ b/t/README
@@ -1,7 +1,7 @@
-Core GIT Tests
+Core Git Tests
 ==============
 
-This directory holds many test scripts for core GIT tools.  The
+This directory holds many test scripts for core Git tools.  The
 first part of this short document describes how to run the tests
 and read their output.
 
@@ -1117,21 +1117,21 @@ Tips for Writing Tests
 As with any programming projects, existing programs are the best
 source of the information.  However, do _not_ emulate
 t0000-basic.sh when writing your tests.  The test is special in
-that it tries to validate the very core of GIT.  For example, it
+that it tries to validate the very core of Git.  For example, it
 knows that there will be 256 subdirectories under .git/objects/,
 and it knows that the object ID of an empty tree is a certain
 40-byte string.  This is deliberately done so in t0000-basic.sh
 because the things the very basic core test tries to achieve is
-to serve as a basis for people who are changing the GIT internal
+to serve as a basis for people who are changing the Git internals
 drastically.  For these people, after making certain changes,
 not seeing failures from the basic test _is_ a failure.  And
-such drastic changes to the core GIT that even changes these
+such drastic changes to the core Git that even changes these
 otherwise supposedly stable object IDs should be accompanied by
 an update to t0000-basic.sh.
 
 However, other tests that simply rely on basic parts of the core
-GIT working properly should not have that level of intimate
-knowledge of the core GIT internals.  If all the test scripts
+Git working properly should not have that level of intimate
+knowledge of the core Git internals.  If all the test scripts
 hardcoded the object IDs like t0000-basic.sh does, that defeats
 the purpose of t0000-basic.sh, which is to isolate that level of
 validation in one place.  Your test also ends up needing
index 12ca698e17a1d556bf345355495849b452b79330..69152958e58eafea73264b1edb99e729613c6843 100644 (file)
@@ -46,6 +46,9 @@ static void unpack(void)
                case PACKET_READ_DELIM:
                        printf("0001\n");
                        break;
+               case PACKET_READ_RESPONSE_END:
+                       printf("0002\n");
+                       break;
                }
        }
 }
@@ -75,6 +78,7 @@ static void unpack_sideband(void)
                case PACKET_READ_FLUSH:
                        return;
                case PACKET_READ_DELIM:
+               case PACKET_READ_RESPONSE_END:
                        break;
                }
        }
index 10284cc56fa9f69703aa69f242edef113d6a40de..d6f28ca8d148d9c05fa2941b59054840da0b156f 100644 (file)
@@ -1,5 +1,4 @@
 #include "test-tool.h"
-#include "git-compat-util.h"
 #include "gettext.h"
 
 struct reg_flag {
@@ -8,12 +7,13 @@ struct reg_flag {
 };
 
 static struct reg_flag reg_flags[] = {
-       { "EXTENDED",    REG_EXTENDED   },
-       { "NEWLINE",     REG_NEWLINE    },
-       { "ICASE",       REG_ICASE      },
-       { "NOTBOL",      REG_NOTBOL     },
+       { "EXTENDED",   REG_EXTENDED    },
+       { "NEWLINE",    REG_NEWLINE     },
+       { "ICASE",      REG_ICASE       },
+       { "NOTBOL",     REG_NOTBOL      },
+       { "NOTEOL",     REG_NOTEOL      },
 #ifdef REG_STARTEND
-       { "STARTEND",    REG_STARTEND   },
+       { "STARTEND",   REG_STARTEND    },
 #endif
        { NULL, 0 }
 };
@@ -41,36 +41,74 @@ int cmd__regex(int argc, const char **argv)
 {
        const char *pat;
        const char *str;
-       int flags = 0;
+       int ret, silent = 0, flags = 0;
        regex_t r;
        regmatch_t m[1];
-
-       if (argc == 2 && !strcmp(argv[1], "--bug"))
-               return test_regex_bug();
-       else if (argc < 3)
-               usage("test-tool regex --bug\n"
-                     "test-tool regex <pattern> <string> [<options>]");
+       char errbuf[64];
 
        argv++;
-       pat = *argv++;
-       str = *argv++;
-       while (*argv) {
-               struct reg_flag *rf;
-               for (rf = reg_flags; rf->name; rf++)
-                       if (!strcmp(*argv, rf->name)) {
-                               flags |= rf->flag;
-                               break;
-                       }
-               if (!rf->name)
-                       die("do not recognize %s", *argv);
+       argc--;
+
+       if (!argc)
+               goto usage;
+
+       if (!strcmp(*argv, "--bug")) {
+               if (argc == 1)
+                       return test_regex_bug();
+               else
+                       goto usage;
+       }
+       if (!strcmp(*argv, "--silent")) {
+               silent = 1;
                argv++;
+               argc--;
+       }
+       if (!argc)
+               goto usage;
+
+       pat = *argv++;
+       if (argc == 1)
+               str = NULL;
+       else {
+               str = *argv++;
+               while (*argv) {
+                       struct reg_flag *rf;
+                       for (rf = reg_flags; rf->name; rf++)
+                               if (!strcmp(*argv, rf->name)) {
+                                       flags |= rf->flag;
+                                       break;
+                               }
+                       if (!rf->name)
+                               die("do not recognize flag %s", *argv);
+                       argv++;
+               }
        }
        git_setup_gettext();
 
-       if (regcomp(&r, pat, flags))
-               die("failed regcomp() for pattern '%s'", pat);
-       if (regexec(&r, str, 1, m, 0))
-               return 1;
+       ret = regcomp(&r, pat, flags);
+       if (ret) {
+               if (silent)
+                       return ret;
+
+               regerror(ret, &r, errbuf, sizeof(errbuf));
+               die("failed regcomp() for pattern '%s' (%s)", pat, errbuf);
+       }
+       if (!str)
+               return 0;
+
+       ret = regexec(&r, str, 1, m, 0);
+       if (ret) {
+               if (silent || ret == REG_NOMATCH)
+                       return ret;
+
+               regerror(ret, &r, errbuf, sizeof(errbuf));
+               die("failed regexec() for subject '%s' (%s)", str, errbuf);
+       }
 
        return 0;
+usage:
+       usage("\ttest-tool regex --bug\n"
+             "\ttest-tool regex [--silent] <pattern>\n"
+             "\ttest-tool regex [--silent] <pattern> <string> [<options>]");
+       return -1;
 }
index 1449ee95e9eaa06f47d54036454a35406f56d47f..d2edfa4c503af07f72a282dddb8cd4cf1d8e868e 100644 (file)
@@ -129,6 +129,8 @@ install_script () {
 prepare_httpd() {
        mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH"
        cp "$TEST_PATH"/passwd "$HTTPD_ROOT_PATH"
+       install_script incomplete-length-upload-pack-v2-http.sh
+       install_script incomplete-body-upload-pack-v2-http.sh
        install_script broken-smart-http.sh
        install_script error-smart-http.sh
        install_script error.sh
index 994e5290d63b0f96ba78dc75bcff5b4a370bdb02..afa91e38b0e2130cc27de6a40c3498b88eb8d122 100644 (file)
@@ -117,6 +117,8 @@ Alias /auth/dumb/ www/auth/dumb/
        SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
        SetEnv GIT_HTTP_EXPORT_ALL
 </LocationMatch>
+ScriptAlias /smart/incomplete_length/git-upload-pack incomplete-length-upload-pack-v2-http.sh/
+ScriptAlias /smart/incomplete_body/git-upload-pack incomplete-body-upload-pack-v2-http.sh/
 ScriptAliasMatch /error_git_upload_pack/(.*)/git-upload-pack error.sh/
 ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1
 ScriptAlias /broken_smart/ broken-smart-http.sh/
@@ -126,6 +128,12 @@ ScriptAliasMatch /one_time_perl/(.*) apply-one-time-perl.sh/$1
 <Directory ${GIT_EXEC_PATH}>
        Options FollowSymlinks
 </Directory>
+<Files incomplete-length-upload-pack-v2-http.sh>
+       Options ExecCGI
+</Files>
+<Files incomplete-body-upload-pack-v2-http.sh>
+       Options ExecCGI
+</Files>
 <Files broken-smart-http.sh>
        Options ExecCGI
 </Files>
diff --git a/t/lib-httpd/incomplete-body-upload-pack-v2-http.sh b/t/lib-httpd/incomplete-body-upload-pack-v2-http.sh
new file mode 100644 (file)
index 0000000..90e73ef
--- /dev/null
@@ -0,0 +1,3 @@
+printf "Content-Type: text/%s\n" "application/x-git-upload-pack-result"
+echo
+printf "%s%s" "0079" "45"
diff --git a/t/lib-httpd/incomplete-length-upload-pack-v2-http.sh b/t/lib-httpd/incomplete-length-upload-pack-v2-http.sh
new file mode 100644 (file)
index 0000000..dce552e
--- /dev/null
@@ -0,0 +1,3 @@
+printf "Content-Type: text/%s\n" "application/x-git-upload-pack-result"
+echo
+printf "%s" "00"
index 0aa9908ea12d7592841e10ac09afe8b36f37ce7f..960ed150cb59055fb3f597a0cc80542828ac4ebb 100755 (executable)
@@ -62,7 +62,7 @@ test_expect_success 'check commit-tree' '
 '
 
 test_expect_success 'check rev-list' '
-       echo $SHA >"$REAL/HEAD" &&
+       git update-ref "HEAD" "$SHA" &&
        test "$SHA" = "$(git rev-list HEAD)"
 '
 
index 88cdde255cdad7a0b138033b3ff9a997e4a2c0b2..7cd45fc13946b35af307c7735c7ecd8a1ae52563 100755 (executable)
@@ -100,6 +100,28 @@ test_expect_success 'clone --sparse' '
        check_files clone a
 '
 
+test_expect_success 'interaction with clone --no-checkout (unborn index)' '
+       git clone --no-checkout "file://$(pwd)/repo" clone_no_checkout &&
+       git -C clone_no_checkout sparse-checkout init --cone &&
+       git -C clone_no_checkout sparse-checkout set folder1 &&
+
+       git -C clone_no_checkout sparse-checkout list >actual &&
+       cat >expect <<-\EOF &&
+       folder1
+       EOF
+       test_cmp expect actual &&
+
+       # nothing checked out, expect "No such file or directory"
+       ! ls clone_no_checkout/* >actual &&
+       test_must_be_empty actual &&
+       test_path_is_missing clone_no_checkout/.git/index &&
+
+       # No branch is checked out until we manually switch to one
+       git -C clone_no_checkout switch master &&
+       test_path_is_file clone_no_checkout/.git/index &&
+       check_files clone_no_checkout a folder1
+'
+
 test_expect_success 'set enables config' '
        git init empty-config &&
        (
index e1197ac8189b942a3ab17511b30d97069c5c597e..27171f826129168aa1e52262e2b2a9b4ca327989 100755 (executable)
@@ -37,15 +37,15 @@ test_expect_success setup '
 
 test_expect_success "create $m" '
        git update-ref $m $A &&
-       test $A = $(cat .git/$m)
+       test $A = $(git show-ref -s --verify $m)
 '
 test_expect_success "create $m with oldvalue verification" '
        git update-ref $m $B $A &&
-       test $B = $(cat .git/$m)
+       test $B = $(git show-ref -s --verify $m)
 '
 test_expect_success "fail to delete $m with stale ref" '
        test_must_fail git update-ref -d $m $A &&
-       test $B = "$(cat .git/$m)"
+       test $B = "$(git show-ref -s --verify $m)"
 '
 test_expect_success "delete $m" '
        test_when_finished "rm -f .git/$m" &&
@@ -56,7 +56,7 @@ test_expect_success "delete $m" '
 test_expect_success "delete $m without oldvalue verification" '
        test_when_finished "rm -f .git/$m" &&
        git update-ref $m $A &&
-       test $A = $(cat .git/$m) &&
+       test $A = $(git show-ref -s --verify $m) &&
        git update-ref -d $m &&
        test_path_is_missing .git/$m
 '
@@ -69,15 +69,15 @@ test_expect_success "fail to create $n" '
 
 test_expect_success "create $m (by HEAD)" '
        git update-ref HEAD $A &&
-       test $A = $(cat .git/$m)
+       test $A = $(git show-ref -s --verify $m)
 '
 test_expect_success "create $m (by HEAD) with oldvalue verification" '
        git update-ref HEAD $B $A &&
-       test $B = $(cat .git/$m)
+       test $B = $(git show-ref -s --verify $m)
 '
 test_expect_success "fail to delete $m (by HEAD) with stale ref" '
        test_must_fail git update-ref -d HEAD $A &&
-       test $B = $(cat .git/$m)
+       test $B = $(git show-ref -s --verify $m)
 '
 test_expect_success "delete $m (by HEAD)" '
        test_when_finished "rm -f .git/$m" &&
@@ -178,14 +178,14 @@ test_expect_success '--no-create-reflog overrides core.logAllRefUpdates=always'
 
 test_expect_success "create $m (by HEAD)" '
        git update-ref HEAD $A &&
-       test $A = $(cat .git/$m)
+       test $A = $(git show-ref -s --verify $m)
 '
 test_expect_success 'pack refs' '
        git pack-refs --all
 '
 test_expect_success "move $m (by HEAD)" '
        git update-ref HEAD $B $A &&
-       test $B = $(cat .git/$m)
+       test $B = $(git show-ref -s --verify $m)
 '
 test_expect_success "delete $m (by HEAD) should remove both packed and loose $m" '
        test_when_finished "rm -f .git/$m" &&
@@ -255,7 +255,7 @@ test_expect_success '(not) change HEAD with wrong SHA1' '
 '
 test_expect_success "(not) changed .git/$m" '
        test_when_finished "rm -f .git/$m" &&
-       ! test $B = $(cat .git/$m)
+       ! test $B = $(git show-ref -s --verify $m)
 '
 
 rm -f .git/logs/refs/heads/master
@@ -263,19 +263,19 @@ test_expect_success "create $m (logged by touch)" '
        test_config core.logAllRefUpdates false &&
        GIT_COMMITTER_DATE="2005-05-26 23:30" \
        git update-ref --create-reflog HEAD $A -m "Initial Creation" &&
-       test $A = $(cat .git/$m)
+       test $A = $(git show-ref -s --verify $m)
 '
 test_expect_success "update $m (logged by touch)" '
        test_config core.logAllRefUpdates false &&
        GIT_COMMITTER_DATE="2005-05-26 23:31" \
        git update-ref HEAD $B $A -m "Switch" &&
-       test $B = $(cat .git/$m)
+       test $B = $(git show-ref -s --verify $m)
 '
 test_expect_success "set $m (logged by touch)" '
        test_config core.logAllRefUpdates false &&
        GIT_COMMITTER_DATE="2005-05-26 23:41" \
        git update-ref HEAD $A &&
-       test $A = $(cat .git/$m)
+       test $A = $(git show-ref -s --verify $m)
 '
 
 test_expect_success 'empty directory removal' '
@@ -319,19 +319,19 @@ test_expect_success "create $m (logged by config)" '
        test_config core.logAllRefUpdates true &&
        GIT_COMMITTER_DATE="2005-05-26 23:32" \
        git update-ref HEAD $A -m "Initial Creation" &&
-       test $A = $(cat .git/$m)
+       test $A = $(git show-ref -s --verify $m)
 '
 test_expect_success "update $m (logged by config)" '
        test_config core.logAllRefUpdates true &&
        GIT_COMMITTER_DATE="2005-05-26 23:33" \
        git update-ref HEAD'" $B $A "'-m "Switch" &&
-       test $B = $(cat .git/$m)
+       test $B = $(git show-ref -s --verify $m)
 '
 test_expect_success "set $m (logged by config)" '
        test_config core.logAllRefUpdates true &&
        GIT_COMMITTER_DATE="2005-05-26 23:43" \
        git update-ref HEAD $A &&
-       test $A = $(cat .git/$m)
+       test $A = $(git show-ref -s --verify $m)
 '
 
 cat >expect <<EOF
index 91a6e34f38173462d44623ce619b4ec744759e9f..344a2aad82f96d04aec38aa1a221d2649e97d491 100755 (executable)
@@ -257,21 +257,34 @@ test_expect_success 'tree object with duplicate entries' '
        test_i18ngrep "error in tree .*contains duplicate file entries" out
 '
 
-test_expect_success 'tree object with dublicate names' '
-       test_when_finished "remove_object \$blob" &&
-       test_when_finished "remove_object \$tree" &&
-       test_when_finished "remove_object \$badtree" &&
-       blob=$(echo blob | git hash-object -w --stdin) &&
-       printf "100644 blob %s\t%s\n" $blob x.2 >tree &&
-       tree=$(git mktree <tree) &&
-       printf "100644 blob %s\t%s\n" $blob x.1 >badtree &&
-       printf "100644 blob %s\t%s\n" $blob x >>badtree &&
-       printf "040000 tree %s\t%s\n" $tree x >>badtree &&
-       badtree=$(git mktree <badtree) &&
-       test_must_fail git fsck 2>out &&
-       test_i18ngrep "$badtree" out &&
-       test_i18ngrep "error in tree .*contains duplicate file entries" out
-'
+check_duplicate_names () {
+       expect=$1 &&
+       shift &&
+       names=$@ &&
+       test_expect_$expect "tree object with duplicate names: $names" '
+               test_when_finished "remove_object \$blob" &&
+               test_when_finished "remove_object \$tree" &&
+               test_when_finished "remove_object \$badtree" &&
+               blob=$(echo blob | git hash-object -w --stdin) &&
+               printf "100644 blob %s\t%s\n" $blob x.2 >tree &&
+               tree=$(git mktree <tree) &&
+               for name in $names
+               do
+                       case "$name" in
+                       */) printf "040000 tree %s\t%s\n" $tree "${name%/}" ;;
+                       *)  printf "100644 blob %s\t%s\n" $blob "$name" ;;
+                       esac
+               done >badtree &&
+               badtree=$(git mktree <badtree) &&
+               test_must_fail git fsck 2>out &&
+               test_i18ngrep "$badtree" out &&
+               test_i18ngrep "error in tree .*contains duplicate file entries" out
+       '
+}
+
+check_duplicate_names success x x.1 x/
+check_duplicate_names success x x.1.2 x.1/ x/
+check_duplicate_names success x x.1 x.1.2 x/
 
 test_expect_success 'unparseable tree object' '
        test_oid_cache <<-\EOF &&
index 52edcbdcc3272ed34854017680430e14796a04a1..dbf690b9c1ba492dff629ac75c9cadd5d4887095 100755 (executable)
@@ -207,7 +207,7 @@ test_expect_success 'arg before dashdash must be a revision (ambiguous)' '
        {
                # we do not want to use rev-parse here, because
                # we are testing it
-               cat .git/refs/heads/foobar &&
+               git show-ref -s refs/heads/foobar &&
                printf "%s\n" --
        } >expect &&
        git rev-parse foobar -- >actual &&
index 21583154d8e0d029168cfe87624cad89eeb765ef..5f761bc616ee5871055d6beed29e768b328f6827 100755 (executable)
@@ -260,4 +260,14 @@ test_expect_success 'checkout -b to a new branch preserves mergeable changes des
        test_cmp expect actual
 '
 
+test_expect_success 'checkout -b rejects an invalid start point' '
+       test_must_fail git checkout -b branch4 file1 2>err &&
+       test_i18ngrep "is not a commit" err
+'
+
+test_expect_success 'checkout -b rejects an extra path argument' '
+       test_must_fail git checkout -b branch5 branch1 file1 2>err &&
+       test_i18ngrep "Cannot update paths and switch to branch" err
+'
+
 test_done
diff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh
new file mode 100755 (executable)
index 0000000..bcba1bf
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+test_description='tests for git branch --track'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit one &&
+       test_commit two
+'
+
+test_expect_success 'checkout --track -b creates a new tracking branch' '
+       git checkout --track -b branch1 master &&
+       test $(git rev-parse --abbrev-ref HEAD) = branch1 &&
+       test $(git config --get branch.branch1.remote) = . &&
+       test $(git config --get branch.branch1.merge) = refs/heads/master
+'
+
+test_expect_success 'checkout --track -b rejects an extra path argument' '
+       test_must_fail git checkout --track -b branch2 master one.t 2>err &&
+       test_i18ngrep "cannot be used with updating paths" err
+'
+
+test_done
index f9efa29dfb8c7ad762fc502d883db2aaa3f114a2..2c1b8c0d6d22469fadd9eeb05a19c1b7a53d0f51 100755 (executable)
@@ -68,6 +68,14 @@ test_expect_success 'new orphan branch from empty' '
        test_cmp expected tracked-files
 '
 
+test_expect_success 'orphan branch works with --discard-changes' '
+       test_when_finished git switch master &&
+       echo foo >foo.txt &&
+       git switch --discard-changes --orphan new-orphan2 &&
+       git ls-files >tracked-files &&
+       test_must_be_empty tracked-files
+'
+
 test_expect_success 'switching ignores file of same branch name' '
        test_when_finished git switch master &&
        : >first-branch &&
index b7d6d5d45adf6067ab2f39801f658f778f9b2855..a6ce7f590b64ffcaaf41a7dc9c0b9de7a6aac46a 100755 (executable)
@@ -92,4 +92,28 @@ test_expect_success 'not prune proper checkouts' '
        test -d .git/worktrees/nop
 '
 
+test_expect_success 'prune duplicate (linked/linked)' '
+       test_when_finished rm -fr .git/worktrees w1 w2 &&
+       git worktree add --detach w1 &&
+       git worktree add --detach w2 &&
+       sed "s/w2/w1/" .git/worktrees/w2/gitdir >.git/worktrees/w2/gitdir.new &&
+       mv .git/worktrees/w2/gitdir.new .git/worktrees/w2/gitdir &&
+       git worktree prune --verbose >actual &&
+       test_i18ngrep "duplicate entry" actual &&
+       test -d .git/worktrees/w1 &&
+       ! test -d .git/worktrees/w2
+'
+
+test_expect_success 'prune duplicate (main/linked)' '
+       test_when_finished rm -fr repo wt &&
+       test_create_repo repo &&
+       test_commit -C repo x &&
+       git -C repo worktree add --detach ../wt &&
+       rm -fr wt &&
+       mv repo wt &&
+       git -C wt worktree prune --verbose >actual &&
+       test_i18ngrep "duplicate entry" actual &&
+       ! test -d .git/worktrees/wt
+'
+
 test_done
index 939d18d7286c1be1e58a698e9164fda8e24c654a..a4e1a178e0a00335affa95d566728e3085804b47 100755 (executable)
@@ -112,6 +112,27 @@ test_expect_success 'move locked worktree (force)' '
        git worktree move --force --force flump ploof
 '
 
+test_expect_success 'refuse to move worktree atop existing path' '
+       >bobble &&
+       git worktree add --detach beeble &&
+       test_must_fail git worktree move beeble bobble
+'
+
+test_expect_success 'move atop existing but missing worktree' '
+       git worktree add --detach gnoo &&
+       git worktree add --detach pneu &&
+       rm -fr pneu &&
+       test_must_fail git worktree move gnoo pneu &&
+       git worktree move --force gnoo pneu &&
+
+       git worktree add --detach nu &&
+       git worktree lock nu &&
+       rm -fr nu &&
+       test_must_fail git worktree move pneu nu &&
+       test_must_fail git worktree --force move pneu nu &&
+       git worktree move --force --force pneu nu
+'
+
 test_expect_success 'move a repo with uninitialized submodule' '
        git init withsub &&
        (
index b3d8bb7577661c0211bc349900cc321f9971ff05..49decbac711386b497b19f22d16324422c0609b5 100755 (executable)
@@ -412,6 +412,25 @@ test_expect_success 'deleting an empty file' '
        diff_cmp expected diff
 '
 
+test_expect_success 'adding an empty file' '
+       git init added &&
+       (
+               cd added &&
+               test_commit initial &&
+               >empty &&
+               git add empty &&
+               test_tick &&
+               git commit -m empty &&
+               git tag added-file &&
+               git reset --hard HEAD^ &&
+               test_path_is_missing empty &&
+
+               echo y | git checkout -p added-file -- >actual &&
+               test_path_is_file empty &&
+               test_i18ngrep "Apply addition to index and worktree" actual
+       )
+'
+
 test_expect_success 'split hunk setup' '
        git reset --hard &&
        test_write_lines 10 20 30 40 50 60 >test &&
index db7e733af9e5be1abe8991e1513f9d31949433f4..575e079cc261628ceca5013b8df3ddb74156f030 100755 (executable)
@@ -1602,6 +1602,19 @@ test_expect_success 'format patch ignores color.ui' '
        test_cmp expect actual
 '
 
+test_expect_success 'format patch respects diff.relative' '
+       rm -rf subdir &&
+       mkdir subdir &&
+       echo other content >subdir/file2 &&
+       git add subdir/file2 &&
+       git commit -F msg &&
+       test_unconfig diff.relative &&
+       git format-patch --relative=subdir --stdout -1 >expect &&
+       test_config diff.relative true &&
+       git -C subdir format-patch --stdout -1 >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'cover letter with invalid --cover-from-description and config' '
        test_config branch.rebuild-1.description "config subject
 
index 258808708e1093819a32e68dfc76b6faf506f388..7be1de736d86c90ac9c36ff68c0e8f4535e9bafd 100755 (executable)
@@ -8,7 +8,8 @@ test_expect_success 'setup' '
        echo content >file1 &&
        mkdir subdir &&
        echo other content >subdir/file2 &&
-       blob=$(git hash-object subdir/file2) &&
+       blob_file1=$(git hash-object file1) &&
+       blob_file2=$(git hash-object subdir/file2) &&
        git add . &&
        git commit -m one
 '
@@ -18,7 +19,7 @@ check_diff () {
        shift
        expect=$1
        shift
-       short_blob=$(git rev-parse --short $blob)
+       short_blob=$(git rev-parse --short $blob_file2)
        cat >expected <<-EOF
        diff --git a/$expect b/$expect
        new file mode 100644
@@ -70,7 +71,7 @@ check_raw () {
        expect=$1
        shift
        cat >expected <<-EOF
-       :000000 100644 $ZERO_OID $blob A        $expect
+       :000000 100644 $ZERO_OID $blob_file2 A  $expect
        EOF
        test_expect_success "--raw $*" "
                git -C '$dir' diff --no-abbrev --raw $* HEAD^ >actual &&
@@ -86,4 +87,79 @@ do
        check_$type . dir/file2 --relative=sub
 done
 
+check_diff_relative_option () {
+       dir=$1
+       shift
+       expect=$1
+       shift
+       relative_opt=$1
+       shift
+       test_expect_success "config diff.relative $relative_opt -p $*" "
+               short_blob=\$(git rev-parse --short $blob_file2) &&
+               cat >expected <<-EOF &&
+               diff --git a/$expect b/$expect
+               new file mode 100644
+               index 0000000..\$short_blob
+               --- /dev/null
+               +++ b/$expect
+               @@ -0,0 +1 @@
+               +other content
+               EOF
+               test_config -C $dir diff.relative $relative_opt &&
+               git -C '$dir' diff -p $* HEAD^ >actual &&
+               test_cmp expected actual
+       "
+}
+
+check_diff_no_relative_option () {
+       dir=$1
+       shift
+       expect=$1
+       shift
+       relative_opt=$1
+       shift
+       test_expect_success "config diff.relative $relative_opt -p $*" "
+               short_blob_file1=\$(git rev-parse --short $blob_file1) &&
+               short_blob_file2=\$(git rev-parse --short $blob_file2) &&
+               cat >expected <<-EOF &&
+               diff --git a/file1 b/file1
+               new file mode 100644
+               index 0000000..\$short_blob_file1
+               --- /dev/null
+               +++ b/file1
+               @@ -0,0 +1 @@
+               +content
+               diff --git a/$expect b/$expect
+               new file mode 100644
+               index 0000000..\$short_blob_file2
+               --- /dev/null
+               +++ b/$expect
+               @@ -0,0 +1 @@
+               +other content
+               EOF
+               test_config -C $dir diff.relative $relative_opt &&
+               git -C '$dir' diff -p $* HEAD^ >actual &&
+               test_cmp expected actual
+       "
+}
+
+check_diff_no_relative_option . subdir/file2 false
+check_diff_no_relative_option . subdir/file2 true --no-relative
+check_diff_no_relative_option . subdir/file2 false --no-relative
+check_diff_no_relative_option subdir subdir/file2 false
+check_diff_no_relative_option subdir subdir/file2 true --no-relative
+check_diff_no_relative_option subdir subdir/file2 false --no-relative
+
+check_diff_relative_option . file2 false --relative=subdir/
+check_diff_relative_option . file2 false --relative=subdir
+check_diff_relative_option . file2 true --relative=subdir/
+check_diff_relative_option . file2 true --relative=subdir
+check_diff_relative_option subdir file2 false --relative
+check_diff_relative_option subdir file2 true --relative
+check_diff_relative_option subdir file2 true
+check_diff_relative_option subdir file2 false --no-relative --relative
+check_diff_relative_option subdir file2 true --no-relative --relative
+check_diff_relative_option . file2 false --no-relative --relative=subdir
+check_diff_relative_option . file2 true --no-relative --relative=subdir
+
 test_done
index c3792081e627ffd10676b0a2a449bdc4df73a7f8..d2dfcf164e25b8771dd852d2aefc4aee4bd3f5ea 100755 (executable)
@@ -10,6 +10,13 @@ latin1_e=$(printf '\351')
 # invalid UTF-8
 invalid_e=$(printf '\303\50)') # ")" at end to close opening "("
 
+have_reg_illseq=
+if test_have_prereq GETTEXT_LOCALE &&
+       ! LC_ALL=$is_IS_locale test-tool regex --silent $latin1_e
+then
+       have_reg_illseq=1
+fi
+
 test_expect_success 'create commits in different encodings' '
        test_tick &&
        cat >msg <<-EOF &&
@@ -51,43 +58,77 @@ test_expect_success !MINGW 'log --grep does not find non-reencoded values (utf8)
        test_must_be_empty actual
 '
 
-test_expect_success !MINGW 'log --grep does not find non-reencoded values (latin1)' '
+test_expect_success 'log --grep does not find non-reencoded values (latin1)' '
        git log --encoding=ISO-8859-1 --format=%s --grep=$utf8_e >actual &&
        test_must_be_empty actual
 '
 
+triggers_undefined_behaviour () {
+       local engine=$1
+
+       case $engine in
+       fixed)
+               if test -n "$have_reg_illseq" &&
+                       ! test_have_prereq LIBPCRE2
+               then
+                       return 0
+               fi
+               ;;
+       basic|extended)
+               if test -n "$have_reg_illseq"
+               then
+                       return 0
+               fi
+               ;;
+       esac
+       return 1
+}
+
+mismatched_git_log () {
+       local pattern=$1
+
+       LC_ALL=$is_IS_locale git log --encoding=ISO-8859-1 --format=%s \
+               --grep=$pattern
+}
+
 for engine in fixed basic extended perl
 do
        prereq=
        if test $engine = "perl"
        then
-               prereq="PCRE"
-       else
-               prereq=""
+               prereq=PCRE
        fi
        force_regex=
        if test $engine != "fixed"
        then
-           force_regex=.*
+               force_regex='.*'
        fi
-       test_expect_success !MINGW,!REGEX_ILLSEQ,GETTEXT_LOCALE,$prereq "-c grep.patternType=$engine log --grep does not find non-reencoded values (latin1 + locale)" "
-               cat >expect <<-\EOF &&
-               latin1
-               utf8
-               EOF
-               LC_ALL=\"$is_IS_locale\" git -c grep.patternType=$engine log --encoding=ISO-8859-1 --format=%s --grep=\"$force_regex$latin1_e\" >actual &&
-               test_cmp expect actual
-       "
 
-       test_expect_success !MINGW,GETTEXT_LOCALE,$prereq "-c grep.patternType=$engine log --grep does not find non-reencoded values (latin1 + locale)" "
-               LC_ALL=\"$is_IS_locale\" git -c grep.patternType=$engine log --encoding=ISO-8859-1 --format=%s --grep=\"$force_regex$utf8_e\" >actual &&
-               test_must_be_empty actual
+       test_expect_success $prereq "config grep.patternType=$engine" "
+               git config grep.patternType $engine
        "
 
-       test_expect_success !MINGW,!REGEX_ILLSEQ,GETTEXT_LOCALE,$prereq "-c grep.patternType=$engine log --grep does not die on invalid UTF-8 value (latin1 + locale + invalid needle)" "
-               LC_ALL=\"$is_IS_locale\" git -c grep.patternType=$engine log --encoding=ISO-8859-1 --format=%s --grep=\"$force_regex$invalid_e\" >actual &&
+       test_expect_success GETTEXT_LOCALE,$prereq "log --grep does not find non-reencoded values (latin1 + locale)" "
+               mismatched_git_log '$force_regex$utf8_e' >actual &&
                test_must_be_empty actual
        "
+
+       if ! triggers_undefined_behaviour $engine
+       then
+               test_expect_success !MINGW,GETTEXT_LOCALE,$prereq "log --grep searches in log output encoding (latin1 + locale)" "
+                       cat >expect <<-\EOF &&
+                       latin1
+                       utf8
+                       EOF
+                       mismatched_git_log '$force_regex$latin1_e' >actual &&
+                       test_cmp expect actual
+               "
+
+               test_expect_success GETTEXT_LOCALE,$prereq "log --grep does not die on invalid UTF-8 value (latin1 + locale + invalid needle)" "
+                       mismatched_git_log '$force_regex$invalid_e' >actual &&
+                       test_must_be_empty actual
+               "
+       fi
 done
 
 test_done
index cda58186c2d2cc69cddb4921a5ab1fdc0474f433..1428eae26299d63262568bf93e3b46671b517660 100755 (executable)
@@ -215,4 +215,72 @@ test_expect_success 'fancy rename following #2' '
        test_cmp expect actual
 '
 
+# Create the following linear history, where each commit does what its
+# subject line promises:
+#
+#   * 66c6410 Modify func2() in file.c
+#   * 50834e5 Modify other-file
+#   * fe5851c Modify func1() in file.c
+#   * 8c7c7dd Add other-file
+#   * d5f4417 Add func1() and func2() in file.c
+test_expect_success 'setup for checking line-log and parent oids' '
+       git checkout --orphan parent-oids &&
+       git reset --hard &&
+
+       cat >file.c <<-\EOF &&
+       int func1()
+       {
+           return F1;
+       }
+
+       int func2()
+       {
+           return F2;
+       }
+       EOF
+       git add file.c &&
+       test_tick &&
+       git commit -m "Add func1() and func2() in file.c" &&
+
+       echo 1 >other-file &&
+       git add other-file &&
+       git commit -m "Add other-file" &&
+
+       sed -e "s/F1/F1 + 1/" file.c >tmp &&
+       mv tmp file.c &&
+       git commit -a -m "Modify func1() in file.c" &&
+
+       echo 2 >other-file &&
+       git commit -a -m "Modify other-file" &&
+
+       sed -e "s/F2/F2 + 2/" file.c >tmp &&
+       mv tmp file.c &&
+       git commit -a -m "Modify func2() in file.c" &&
+
+       head_oid=$(git rev-parse --short HEAD) &&
+       prev_oid=$(git rev-parse --short HEAD^) &&
+       root_oid=$(git rev-parse --short HEAD~4)
+'
+
+# Parent oid should be from immediate parent.
+test_expect_success 'parent oids without parent rewriting' '
+       cat >expect <<-EOF &&
+       $head_oid $prev_oid Modify func2() in file.c
+       $root_oid  Add func1() and func2() in file.c
+       EOF
+       git log --format="%h %p %s" --no-patch -L:func2:file.c >actual &&
+       test_cmp expect actual
+'
+
+# Parent oid should be from the most recent ancestor touching func2(),
+# i.e. in this case from the root commit.
+test_expect_success 'parent oids with parent rewriting' '
+       cat >expect <<-EOF &&
+       $head_oid $root_oid Modify func2() in file.c
+       $root_oid  Add func1() and func2() in file.c
+       EOF
+       git log --format="%h %p %s" --no-patch -L:func2:file.c --parents >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 424599959c9c31a1392ef584b924a787895906bb..26f332d6a31fcecfe8723a6c603ab224462c9ec8 100755 (executable)
@@ -46,15 +46,6 @@ test_expect_success 'create commits and repack' '
        git repack
 '
 
-test_expect_success 'exit with correct error on bad input to --stdin-commits' '
-       cd "$TRASH_DIRECTORY/full" &&
-       echo HEAD | test_expect_code 1 git commit-graph write --stdin-commits 2>stderr &&
-       test_i18ngrep "unexpected non-hex object ID: HEAD" stderr &&
-       # valid tree OID, but not a commit OID
-       git rev-parse HEAD^{tree} | test_expect_code 1 git commit-graph write --stdin-commits 2>stderr &&
-       test_i18ngrep "invalid commit object id" stderr
-'
-
 graph_git_two_modes() {
        git -c core.commitGraph=true $1 >output
        git -c core.commitGraph=false $1 >expect
@@ -95,6 +86,22 @@ graph_read_expect() {
        test_cmp expect output
 }
 
+test_expect_success 'exit with correct error on bad input to --stdin-commits' '
+       cd "$TRASH_DIRECTORY/full" &&
+       # invalid, non-hex OID
+       echo HEAD >in &&
+       test_expect_code 1 git commit-graph write --stdin-commits <in 2>stderr &&
+       test_i18ngrep "unexpected non-hex object ID: HEAD" stderr &&
+       # non-existent OID
+       echo $ZERO_OID >in &&
+       test_expect_code 1 git commit-graph write --stdin-commits <in 2>stderr &&
+       test_i18ngrep "invalid object" stderr &&
+       # valid commit and tree OID
+       git rev-parse HEAD HEAD^{tree} >in &&
+       git commit-graph write --stdin-commits <in &&
+       graph_read_expect 3
+'
+
 test_expect_success 'write graph' '
        cd "$TRASH_DIRECTORY/full" &&
        git commit-graph write &&
@@ -140,7 +147,7 @@ test_expect_success 'Add more commits' '
 test_expect_success 'commit-graph write progress off for redirected stderr' '
        cd "$TRASH_DIRECTORY/full" &&
        git commit-graph write 2>err &&
-       test_line_count = 0 err
+       test_must_be_empty err
 '
 
 test_expect_success 'commit-graph write force progress on for stderr' '
@@ -152,13 +159,34 @@ test_expect_success 'commit-graph write force progress on for stderr' '
 test_expect_success 'commit-graph write with the --no-progress option' '
        cd "$TRASH_DIRECTORY/full" &&
        git commit-graph write --no-progress 2>err &&
-       test_line_count = 0 err
+       test_must_be_empty err
+'
+
+test_expect_success 'commit-graph write --stdin-commits progress off for redirected stderr' '
+       cd "$TRASH_DIRECTORY/full" &&
+       git rev-parse commits/5 >in &&
+       git commit-graph write --stdin-commits <in 2>err &&
+       test_must_be_empty err
+'
+
+test_expect_success 'commit-graph write --stdin-commits force progress on for stderr' '
+       cd "$TRASH_DIRECTORY/full" &&
+       git rev-parse commits/5 >in &&
+       GIT_PROGRESS_DELAY=0 git commit-graph write --stdin-commits --progress <in 2>err &&
+       test_i18ngrep "Collecting commits from input" err
+'
+
+test_expect_success 'commit-graph write --stdin-commits with the --no-progress option' '
+       cd "$TRASH_DIRECTORY/full" &&
+       git rev-parse commits/5 >in &&
+       git commit-graph write --stdin-commits --no-progress <in 2>err &&
+       test_must_be_empty err
 '
 
 test_expect_success 'commit-graph verify progress off for redirected stderr' '
        cd "$TRASH_DIRECTORY/full" &&
        git commit-graph verify 2>err &&
-       test_line_count = 0 err
+       test_must_be_empty err
 '
 
 test_expect_success 'commit-graph verify force progress on for stderr' '
@@ -170,7 +198,7 @@ test_expect_success 'commit-graph verify force progress on for stderr' '
 test_expect_success 'commit-graph verify with the --no-progress option' '
        cd "$TRASH_DIRECTORY/full" &&
        git commit-graph verify --no-progress 2>err &&
-       test_line_count = 0 err
+       test_must_be_empty err
 '
 
 # Current graph structure:
index afc680d5e3d3940ee63a1564b9ef5aa1ca39c9a4..463d0f12e5bfcaf79dd5972ecc6fc410a706e148 100755 (executable)
@@ -464,6 +464,21 @@ test_expect_success 'push status output scrubs password' '
        grep "^To $HTTPD_URL/smart/test_repo.git" status
 '
 
+test_expect_success 'clone/fetch scrubs password from reflogs' '
+       cd "$ROOT_PATH" &&
+       git clone "$HTTPD_URL_USER_PASS/smart/test_repo.git" \
+               reflog-test &&
+       cd reflog-test &&
+       test_commit prepare-for-force-fetch &&
+       git switch -c away &&
+       git fetch "$HTTPD_URL_USER_PASS/smart/test_repo.git" \
+               +master:master &&
+       # should have been scrubbed down to vanilla URL
+       git log -g master >reflog &&
+       grep "$HTTPD_URL" reflog &&
+       ! grep "$HTTPD_URL_USER_PASS" reflog
+'
+
 test_expect_success 'colorize errors/hints' '
        cd "$ROOT_PATH"/test_repo_clone &&
        test_must_fail git -c color.transport=always -c color.advice=always \
index 6788aefaceb8b01d9f9914a3a096b2a6bddec1df..e40e9ed52f17ce3cdb83bbcf5f2cd3cf1a3b2d8c 100755 (executable)
@@ -185,6 +185,40 @@ test_expect_success 'redirects send auth to new location' '
        expect_askpass both user@host auth/smart/repo.git
 '
 
+test_expect_success 'GIT_TRACE_CURL redacts auth details' '
+       rm -rf redact-auth trace &&
+       set_askpass user@host pass@host &&
+       GIT_TRACE_CURL="$(pwd)/trace" git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth &&
+       expect_askpass both user@host &&
+
+       # Ensure that there is no "Basic" followed by a base64 string, but that
+       # the auth details are redacted
+       ! grep "Authorization: Basic [0-9a-zA-Z+/]" trace &&
+       grep "Authorization: Basic <redacted>" trace
+'
+
+test_expect_success 'GIT_CURL_VERBOSE redacts auth details' '
+       rm -rf redact-auth trace &&
+       set_askpass user@host pass@host &&
+       GIT_CURL_VERBOSE=1 git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth 2>trace &&
+       expect_askpass both user@host &&
+
+       # Ensure that there is no "Basic" followed by a base64 string, but that
+       # the auth details are redacted
+       ! grep "Authorization: Basic [0-9a-zA-Z+/]" trace &&
+       grep "Authorization: Basic <redacted>" trace
+'
+
+test_expect_success 'GIT_TRACE_CURL does not redact auth details if GIT_TRACE_REDACT=0' '
+       rm -rf redact-auth trace &&
+       set_askpass user@host pass@host &&
+       GIT_TRACE_REDACT=0 GIT_TRACE_CURL="$(pwd)/trace" \
+               git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth &&
+       expect_askpass both user@host &&
+
+       grep "Authorization: Basic [0-9a-zA-Z+/]" trace
+'
+
 test_expect_success 'disable dumb http on server' '
        git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \
                config http.getanyfile false
@@ -430,27 +464,39 @@ test_expect_success 'fetch by SHA-1 without tag following' '
                --no-tags origin $(cat bar_hash)
 '
 
-test_expect_success 'GIT_REDACT_COOKIES redacts cookies' '
+test_expect_success 'cookies are redacted by default' '
        rm -rf clone &&
        echo "Set-Cookie: Foo=1" >cookies &&
        echo "Set-Cookie: Bar=2" >>cookies &&
-       GIT_TRACE_CURL=true GIT_REDACT_COOKIES=Bar,Baz \
+       GIT_TRACE_CURL=true \
                git -c "http.cookieFile=$(pwd)/cookies" clone \
                $HTTPD_URL/smart/repo.git clone 2>err &&
-       grep "Cookie:.*Foo=1" err &&
+       grep "Cookie:.*Foo=<redacted>" err &&
        grep "Cookie:.*Bar=<redacted>" err &&
+       ! grep "Cookie:.*Foo=1" err &&
        ! grep "Cookie:.*Bar=2" err
 '
 
-test_expect_success 'GIT_REDACT_COOKIES handles empty values' '
+test_expect_success 'empty values of cookies are also redacted' '
        rm -rf clone &&
        echo "Set-Cookie: Foo=" >cookies &&
-       GIT_TRACE_CURL=true GIT_REDACT_COOKIES=Foo \
+       GIT_TRACE_CURL=true \
                git -c "http.cookieFile=$(pwd)/cookies" clone \
                $HTTPD_URL/smart/repo.git clone 2>err &&
        grep "Cookie:.*Foo=<redacted>" err
 '
 
+test_expect_success 'GIT_TRACE_REDACT=0 disables cookie redaction' '
+       rm -rf clone &&
+       echo "Set-Cookie: Foo=1" >cookies &&
+       echo "Set-Cookie: Bar=2" >>cookies &&
+       GIT_TRACE_REDACT=0 GIT_TRACE_CURL=true \
+               git -c "http.cookieFile=$(pwd)/cookies" clone \
+               $HTTPD_URL/smart/repo.git clone 2>err &&
+       grep "Cookie:.*Foo=1" err &&
+       grep "Cookie:.*Bar=2" err
+'
+
 test_expect_success 'GIT_TRACE_CURL_NO_DATA prevents data from being traced' '
        rm -rf clone &&
        GIT_TRACE_CURL=true \
index 5129b0724f703890c80c8dcbe7bd4347792963e3..927aad082098254d0ca31e96ba22310d044d8e3b 100755 (executable)
@@ -20,7 +20,7 @@ test_expect_success 'failure in git-upload-pack is shown' '
        test_might_fail env GIT_CURL_VERBOSE=1 \
                git clone "$HTTPD_URL/error_git_upload_pack/smart/repo.git" \
                2>curl_log &&
-       grep "< HTTP/1.1 500 Intentional Breakage" curl_log
+       grep "<= Recv header: HTTP/1.1 500 Intentional Breakage" curl_log
 '
 
 test_done
index eee0842888414bd06583feaf1aabf5ea71f1d684..4c476d2fa18a611621e027250fa7bc54b5446ca7 100755 (executable)
@@ -5,12 +5,11 @@ test_description='Test cloning a repository larger than 2 gigabyte'
 
 if ! test_bool_env GIT_TEST_CLONE_2GB false
 then
-       say 'Skipping expensive 2GB clone test; enable it with GIT_TEST_CLONE_2GB=t'
-else
-       test_set_prereq CLONE_2GB
+       skip_all='expensive 2GB clone test; enable with GIT_TEST_CLONE_2GB=true'
+       test_done
 fi
 
-test_expect_success CLONE_2GB 'setup' '
+test_expect_success 'setup' '
 
        git config pack.compression 0 &&
        git config pack.depth 0 &&
@@ -38,13 +37,13 @@ test_expect_success CLONE_2GB 'setup' '
 
 '
 
-test_expect_success CLONE_2GB 'clone - bare' '
+test_expect_success 'clone - bare' '
 
        git clone --bare --no-hardlinks . clone-bare
 
 '
 
-test_expect_success CLONE_2GB 'clone - with worktree, file:// protocol' '
+test_expect_success 'clone - with worktree, file:// protocol' '
 
        git clone "file://$(pwd)" clone-wt
 
index 5039e66dc47c0e28086454b5681ab102e1b4ecd4..8da65e60deea33a61c65829f4ae79f290e7d2c25 100755 (executable)
@@ -586,6 +586,53 @@ test_expect_success 'clone with http:// using protocol v2' '
        ! grep "Send header: Transfer-Encoding: chunked" log
 '
 
+test_expect_success 'clone repository with http:// using protocol v2 with incomplete pktline length' '
+       test_when_finished "rm -f log" &&
+
+       git init "$HTTPD_DOCUMENT_ROOT_PATH/incomplete_length" &&
+       test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/incomplete_length" file &&
+
+       test_must_fail env GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE_CURL="$(pwd)/log" git -c protocol.version=2 \
+               clone "$HTTPD_URL/smart/incomplete_length" incomplete_length_child 2>err &&
+
+       # Client requested to use protocol v2
+       grep "Git-Protocol: version=2" log &&
+       # Server responded using protocol v2
+       grep "git< version 2" log &&
+       # Client reported appropriate failure
+       test_i18ngrep "bytes of length header were received" err
+'
+
+test_expect_success 'clone repository with http:// using protocol v2 with incomplete pktline body' '
+       test_when_finished "rm -f log" &&
+
+       git init "$HTTPD_DOCUMENT_ROOT_PATH/incomplete_body" &&
+       test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/incomplete_body" file &&
+
+       test_must_fail env GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE_CURL="$(pwd)/log" git -c protocol.version=2 \
+               clone "$HTTPD_URL/smart/incomplete_body" incomplete_body_child 2>err &&
+
+       # Client requested to use protocol v2
+       grep "Git-Protocol: version=2" log &&
+       # Server responded using protocol v2
+       grep "git< version 2" log &&
+       # Client reported appropriate failure
+       test_i18ngrep "bytes of body are still expected" err
+'
+
+test_expect_success 'clone with http:// using protocol v2 and invalid parameters' '
+       test_when_finished "rm -f log" &&
+
+       test_must_fail env GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE_CURL="$(pwd)/log" \
+               git -c protocol.version=2 \
+               clone --shallow-since=20151012 "$HTTPD_URL/smart/http_parent" http_child_invalid &&
+
+       # Client requested to use protocol v2
+       grep "Git-Protocol: version=2" log &&
+       # Server responded using protocol v2
+       grep "git< version 2" log
+'
+
 test_expect_success 'clone big repository with http:// using protocol v2' '
        test_when_finished "rm -f log" &&
 
index ac31faefa15acd484f5c6c3042fa080d78a770bd..36d9b2b2e485ffa8a794e61229dedc8c7d47ea46 100755 (executable)
@@ -866,7 +866,9 @@ test_expect_success 'bisect cannot mix terms' '
 
 test_expect_success 'bisect terms rejects invalid terms' '
        git bisect reset &&
+       test_must_fail git bisect start --term-good &&
        test_must_fail git bisect start --term-good invalid..term &&
+       test_must_fail git bisect start --term-bad &&
        test_must_fail git bisect terms --term-bad invalid..term &&
        test_must_fail git bisect terms --term-good bad &&
        test_must_fail git bisect terms --term-good old &&
index e7e64e085ddcfe7d7cf4df844472a6bf456ab079..c80dc10b8f12581842fd3ad09d1dc459e5addbdf 100755 (executable)
@@ -135,7 +135,7 @@ test_expect_success 'tag replaced commit' '
 test_expect_success '"git fsck" works' '
      git fsck master >fsck_master.out &&
      test_i18ngrep "dangling commit $R" fsck_master.out &&
-     test_i18ngrep "dangling tag $(cat .git/refs/tags/mytag)" fsck_master.out &&
+     test_i18ngrep "dangling tag $(git show-ref -s refs/tags/mytag)" fsck_master.out &&
      test -z "$(git fsck)"
 '
 
index 2462b19ddd35eea986bbe0cdc62806bc455c06d8..30328b87f07657c899801304bf6e0331f569f1bb 100755 (executable)
@@ -211,4 +211,37 @@ test_expect_success 't_e_i() exclude case #8' '
        )
 '
 
+test_expect_success 'grep --untracked PATTERN' '
+       # This test is not an actual test of exclude patterns, rather it
+       # is here solely to ensure that if any tests are inserted, deleted, or
+       # changed above, that we still have untracked files with the expected
+       # contents for the NEXT two tests.
+       cat <<-\EOF >expect-grep &&
+       actual
+       expect
+       sub/actual
+       sub/expect
+       EOF
+       git grep -l --untracked file -- >actual-grep &&
+       test_cmp expect-grep actual-grep
+'
+
+test_expect_success 'grep --untracked PATTERN :(exclude)DIR' '
+       cat <<-\EOF >expect-grep &&
+       actual
+       expect
+       EOF
+       git grep -l --untracked file -- ":(exclude)sub" >actual-grep &&
+       test_cmp expect-grep actual-grep
+'
+
+test_expect_success 'grep --untracked PATTERN :(exclude)*FILE' '
+       cat <<-\EOF >expect-grep &&
+       actual
+       sub/actual
+       EOF
+       git grep -l --untracked file -- ":(exclude)*expect" >actual-grep &&
+       test_cmp expect-grep actual-grep
+'
+
 test_done
index 6fca08e5e35bd35cab5b97654b4a852ac28c9a34..9fcfa969a9b4604b1c9bdfff1dd3f58a613b95bb 100755 (executable)
@@ -48,8 +48,8 @@ test_expect_success REMOTE_SVN 'simple fetch' '
 '
 
 test_debug '
-       cat .git/refs/svn/svnsim/master
-       cat .git/refs/remotes/svnsim/master
+       git show-ref -s refs/svn/svnsim/master
+       git show-ref -s refs/remotes/svnsim/master
 '
 
 test_expect_success REMOTE_SVN 'repeated fetch, nothing shall change' '
index 768257b29e0cf0be6df93e01d529b50269d72465..e151df81c0672cdf2cd9d97942fe3fbce8a2c6bc 100755 (executable)
@@ -410,6 +410,34 @@ test_expect_success 'B: accept empty committer' '
        test -z "$out"
 '
 
+test_expect_success 'B: reject invalid timezone' '
+       cat >input <<-INPUT_END &&
+       commit refs/heads/invalid-timezone
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1234567890 +051800
+       data <<COMMIT
+       empty commit
+       COMMIT
+       INPUT_END
+
+       test_when_finished "git update-ref -d refs/heads/invalid-timezone" &&
+       test_must_fail git fast-import <input
+'
+
+test_expect_success 'B: accept invalid timezone with raw-permissive' '
+       cat >input <<-INPUT_END &&
+       commit refs/heads/invalid-timezone
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1234567890 +051800
+       data <<COMMIT
+       empty commit
+       COMMIT
+       INPUT_END
+
+       git init invalid-timezone &&
+       git -C invalid-timezone fast-import --date-format=raw-permissive <input &&
+       git -C invalid-timezone cat-file -p invalid-timezone >out &&
+       grep "1234567890 [+]051800" out
+'
+
 test_expect_success 'B: accept and fixup committer with no name' '
        cat >input <<-INPUT_END &&
        commit refs/heads/empty-committer-2
index 3c44af69401594545082c47b16d5a98262c5302f..8f434a0931e5b8146f57aaf8362badade9117820 100755 (executable)
@@ -1240,6 +1240,461 @@ test_expect_success '__git_complete_fetch_refspecs - fully qualified & prefix' '
        test_cmp expected out
 '
 
+test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '
+       test_completion "git switch " <<-\EOF
+       branch-in-other Z
+       master Z
+       master-in-other Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '
+       test_completion "git checkout " <<-\EOF
+       HEAD Z
+       branch-in-other Z
+       master Z
+       master-in-other Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git switch - with --no-guess, complete only local branches' '
+       test_completion "git switch --no-guess " <<-\EOF
+       master Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete only local branches' '
+       GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch " <<-\EOF
+       master Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '
+       GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch --guess " <<-\EOF
+       branch-in-other Z
+       master Z
+       master-in-other Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '
+       test_completion "git switch --no-guess --guess " <<-\EOF
+       branch-in-other Z
+       master Z
+       master-in-other Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git switch - a later --no-guess overrides previous --guess, complete only local branches' '
+       test_completion "git switch --guess --no-guess " <<-\EOF
+       master Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only completes refs' '
+       GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout " <<-\EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '
+       GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout --guess " <<-\EOF
+       HEAD Z
+       branch-in-other Z
+       master Z
+       master-in-other Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git checkout - with --no-guess, only completes refs' '
+       test_completion "git checkout --no-guess " <<-\EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '
+       test_completion "git checkout --no-guess --guess " <<-\EOF
+       HEAD Z
+       branch-in-other Z
+       master Z
+       master-in-other Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git checkout - a later --no-guess overrides previous --guess, complete only refs' '
+       test_completion "git checkout --guess --no-guess " <<-\EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git switch - with --detach, complete all references' '
+       test_completion "git switch --detach " <<-\EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git checkout - with --detach, complete only references' '
+       test_completion "git checkout --detach " <<-\EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git switch - with -d, complete all references' '
+       test_completion "git switch -d " <<-\EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git checkout - with -d, complete only references' '
+       test_completion "git checkout -d " <<-\EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git switch - with --track, complete only remote branches' '
+       test_completion "git switch --track " <<-\EOF
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git checkout - with --track, complete only remote branches' '
+       test_completion "git checkout --track " <<-\EOF
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git switch - with --no-track, complete only local branch names' '
+       test_completion "git switch --no-track " <<-\EOF
+       master Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git checkout - with --no-track, complete only local references' '
+       test_completion "git checkout --no-track " <<-\EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git switch - with -c, complete all references' '
+       test_completion "git switch -c new-branch " <<-\EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git switch - with -C, complete all references' '
+       test_completion "git switch -C new-branch " <<-\EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git switch - with -c and --track, complete all references' '
+       test_completion "git switch -c new-branch --track " <<-EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git switch - with -C and --track, complete all references' '
+       test_completion "git switch -C new-branch --track " <<-EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git switch - with -c and --no-track, complete all references' '
+       test_completion "git switch -c new-branch --no-track " <<-\EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git switch - with -C and --no-track, complete all references' '
+       test_completion "git switch -C new-branch --no-track " <<-\EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git checkout - with -b, complete all references' '
+       test_completion "git checkout -b new-branch " <<-\EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git checkout - with -B, complete all references' '
+       test_completion "git checkout -B new-branch " <<-\EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git checkout - with -b and --track, complete all references' '
+       test_completion "git checkout -b new-branch --track " <<-EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git checkout - with -B and --track, complete all references' '
+       test_completion "git checkout -B new-branch --track " <<-EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git checkout - with -b and --no-track, complete all references' '
+       test_completion "git checkout -b new-branch --no-track " <<-\EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git checkout - with -B and --no-track, complete all references' '
+       test_completion "git checkout -B new-branch --no-track " <<-\EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
+test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '
+       test_completion "git switch -c " <<-\EOF
+       branch-in-other Z
+       master Z
+       master-in-other Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '
+       test_completion "git switch -C " <<-\EOF
+       branch-in-other Z
+       master Z
+       master-in-other Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git switch - for -c with --no-guess, complete local branches only' '
+       test_completion "git switch --no-guess -c " <<-\EOF
+       master Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git switch - for -C with --no-guess, complete local branches only' '
+       test_completion "git switch --no-guess -C " <<-\EOF
+       master Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git switch - for -c with --no-track, complete local branches only' '
+       test_completion "git switch --no-track -c " <<-\EOF
+       master Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git switch - for -C with --no-track, complete local branches only' '
+       test_completion "git switch --no-track -C " <<-\EOF
+       master Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '
+       test_completion "git checkout -b " <<-\EOF
+       branch-in-other Z
+       master Z
+       master-in-other Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '
+       test_completion "git checkout -B " <<-\EOF
+       branch-in-other Z
+       master Z
+       master-in-other Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git checkout - for -b with --no-guess, complete local branches only' '
+       test_completion "git checkout --no-guess -b " <<-\EOF
+       master Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git checkout - for -B with --no-guess, complete local branches only' '
+       test_completion "git checkout --no-guess -B " <<-\EOF
+       master Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git checkout - for -b with --no-track, complete local branches only' '
+       test_completion "git checkout --no-track -b " <<-\EOF
+       master Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git checkout - for -B with --no-track, complete local branches only' '
+       test_completion "git checkout --no-track -B " <<-\EOF
+       master Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '
+       test_completion "git switch --orphan " <<-\EOF
+       branch-in-other Z
+       master Z
+       master-in-other Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git switch - --orphan with branch already provided completes nothing else' '
+       test_completion "git switch --orphan master " <<-\EOF
+
+       EOF
+'
+
+test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '
+       test_completion "git checkout --orphan " <<-\EOF
+       branch-in-other Z
+       master Z
+       master-in-other Z
+       matching-branch Z
+       EOF
+'
+
+test_expect_success 'git checkout - --orphan with branch already provided completes local refs for a start-point' '
+       test_completion "git checkout --orphan master " <<-\EOF
+       HEAD Z
+       master Z
+       matching-branch Z
+       matching-tag Z
+       other/branch-in-other Z
+       other/master-in-other Z
+       EOF
+'
+
 test_expect_success 'teardown after ref completion' '
        git branch -d matching-branch &&
        git tag -d matching-tag &&
index 88bb797141dbff99c12fb5db758584ac259de90e..dbc027ff267e0c3151965960608ef7cbc916950f 100644 (file)
@@ -1489,12 +1489,6 @@ case $uname_s in
        test_set_prereq SED_STRIPS_CR
        test_set_prereq GREP_STRIPS_CR
        ;;
-FreeBSD)
-       test_set_prereq REGEX_ILLSEQ
-       test_set_prereq POSIXPERM
-       test_set_prereq BSLASHPSPEC
-       test_set_prereq EXECKEEPSPID
-       ;;
 *)
        test_set_prereq POSIXPERM
        test_set_prereq BSLASHPSPEC
diff --git a/trace.c b/trace.c
index b3ef0e627f8cec18433c17ffb080575894882758..f726686fd92f0b9f388b7dddeca55edc2ea8d8a8 100644 (file)
--- a/trace.c
+++ b/trace.c
@@ -29,7 +29,7 @@ struct trace_key trace_perf_key = TRACE_KEY_INIT(PERFORMANCE);
 struct trace_key trace_setup_key = TRACE_KEY_INIT(SETUP);
 
 /* Get a trace file descriptor from "key" env variable. */
-static int get_trace_fd(struct trace_key *key)
+static int get_trace_fd(struct trace_key *key, const char *override_envvar)
 {
        const char *trace;
 
@@ -37,7 +37,7 @@ static int get_trace_fd(struct trace_key *key)
        if (key->initialized)
                return key->fd;
 
-       trace = getenv(key->key);
+       trace = override_envvar ? override_envvar : getenv(key->key);
 
        if (!trace || !strcmp(trace, "") ||
            !strcmp(trace, "0") || !strcasecmp(trace, "false"))
@@ -68,6 +68,18 @@ static int get_trace_fd(struct trace_key *key)
        return key->fd;
 }
 
+void trace_override_envvar(struct trace_key *key, const char *value)
+{
+       trace_disable(key);
+       key->initialized = 0;
+
+       /*
+        * Invoke get_trace_fd() to initialize key using the given value
+        * instead of the value of the environment variable.
+        */
+       get_trace_fd(key, value);
+}
+
 void trace_disable(struct trace_key *key)
 {
        if (key->need_close)
@@ -112,7 +124,7 @@ static int prepare_trace_line(const char *file, int line,
 
 static void trace_write(struct trace_key *key, const void *buf, unsigned len)
 {
-       if (write_in_full(get_trace_fd(key), buf, len) < 0) {
+       if (write_in_full(get_trace_fd(key, NULL), buf, len) < 0) {
                warning("unable to write trace for %s: %s",
                        key->key, strerror(errno));
                trace_disable(key);
@@ -383,7 +395,7 @@ void trace_repo_setup(const char *prefix)
 
 int trace_want(struct trace_key *key)
 {
-       return !!get_trace_fd(key);
+       return !!get_trace_fd(key, NULL);
 }
 
 #if defined(HAVE_CLOCK_GETTIME) && defined(HAVE_CLOCK_MONOTONIC)
diff --git a/trace.h b/trace.h
index 9826618b331af6574dd81387f6210eba803636e0..0dbbad0e41cb074e669f58fc3ed347e16faeb45c 100644 (file)
--- a/trace.h
+++ b/trace.h
@@ -101,6 +101,12 @@ void trace_repo_setup(const char *prefix);
  */
 int trace_want(struct trace_key *key);
 
+/**
+ * Enables or disables tracing for the specified key, as if the environment
+ * variable was set to the given value.
+ */
+void trace_override_envvar(struct trace_key *key, const char *value);
+
 /**
  * Disables tracing for the specified key, even if the environment variable
  * was set.
index 15f5ba4e8f22c69959357bc8a7fe073678fe8862..7d50c502adfb6c24be91c8b07260c61a0add5340 100644 (file)
@@ -297,7 +297,8 @@ static struct ref *handshake(struct transport *transport, int for_push,
                if (must_list_refs)
                        get_remote_refs(data->fd[1], &reader, &refs, for_push,
                                        ref_prefixes,
-                                       transport->server_options);
+                                       transport->server_options,
+                                       transport->stateless_rpc);
                break;
        case protocol_v1:
        case protocol_v0:
@@ -369,24 +370,15 @@ static int fetch_refs_via_pack(struct transport *transport,
                refs_tmp = handshake(transport, 0, NULL, must_list_refs);
        }
 
-       switch (data->version) {
-       case protocol_v2:
-               refs = fetch_pack(&args, data->fd,
-                                 refs_tmp ? refs_tmp : transport->remote_refs,
-                                 to_fetch, nr_heads, &data->shallow,
-                                 &transport->pack_lockfile, data->version);
-               break;
-       case protocol_v1:
-       case protocol_v0:
-               die_if_server_options(transport);
-               refs = fetch_pack(&args, data->fd,
-                                 refs_tmp ? refs_tmp : transport->remote_refs,
-                                 to_fetch, nr_heads, &data->shallow,
-                                 &transport->pack_lockfile, data->version);
-               break;
-       case protocol_unknown_version:
+       if (data->version == protocol_unknown_version)
                BUG("unknown protocol version");
-       }
+       else if (data->version <= protocol_v1)
+               die_if_server_options(transport);
+
+       refs = fetch_pack(&args, data->fd,
+                         refs_tmp ? refs_tmp : transport->remote_refs,
+                         to_fetch, nr_heads, &data->shallow,
+                         &transport->pack_lockfile, data->version);
 
        close(data->fd[0]);
        close(data->fd[1]);
index 0478bff3e7fd11fdf89463a1f23cd2371de1b220..bc7e3ca19dd4deea074b0680083f4fde1c5b1910 100644 (file)
 
 static timestamp_t oldest_have;
 
-static int multi_ack;
-static int no_done;
-static int use_thin_pack, use_ofs_delta, use_include_tag;
-static int no_progress, daemon_mode;
 /* Allow specifying sha1 if it is a ref tip. */
 #define ALLOW_TIP_SHA1 01
 /* Allow request of a sha1 if it is reachable from a ref (possibly hidden ref). */
@@ -57,27 +53,102 @@ static int no_progress, daemon_mode;
 static unsigned int allow_unadvertised_object_request;
 static int shallow_nr;
 static struct object_array extra_edge_obj;
-static unsigned int timeout;
-static int keepalive = 5;
-/* 0 for no sideband,
- * otherwise maximum packet size (up to 65520 bytes).
+
+/*
+ * Please annotate, and if possible group together, fields used only
+ * for protocol v0 or only for protocol v2.
  */
-static int use_sideband;
-static int stateless_rpc;
-static const char *pack_objects_hook;
+struct upload_pack_data {
+       struct string_list symref;                              /* v0 only */
+       struct object_array want_obj;
+       struct object_array have_obj;
+       struct oid_array haves;                                 /* v2 only */
+       struct string_list wanted_refs;                         /* v2 only */
+
+       struct object_array shallows;
+       struct string_list deepen_not;
+       int depth;
+       timestamp_t deepen_since;
+       int deepen_rev_list;
+       int deepen_relative;
+       int keepalive;
+
+       unsigned int timeout;                                   /* v0 only */
+       enum {
+               NO_MULTI_ACK = 0,
+               MULTI_ACK = 1,
+               MULTI_ACK_DETAILED = 2
+       } multi_ack;                                            /* v0 only */
+
+       /* 0 for no sideband, otherwise DEFAULT_PACKET_MAX or LARGE_PACKET_MAX */
+       int use_sideband;
+
+       struct list_objects_filter_options filter_options;
+
+       struct packet_writer writer;
+
+       const char *pack_objects_hook;
 
-static int filter_capability_requested;
-static int allow_filter;
-static int allow_ref_in_want;
+       unsigned stateless_rpc : 1;                             /* v0 only */
+       unsigned no_done : 1;                                   /* v0 only */
+       unsigned daemon_mode : 1;                               /* v0 only */
+       unsigned filter_capability_requested : 1;               /* v0 only */
+
+       unsigned use_thin_pack : 1;
+       unsigned use_ofs_delta : 1;
+       unsigned no_progress : 1;
+       unsigned use_include_tag : 1;
+       unsigned allow_filter : 1;
 
-static int allow_sideband_all;
+       unsigned done : 1;                                      /* v2 only */
+       unsigned allow_ref_in_want : 1;                         /* v2 only */
+       unsigned allow_sideband_all : 1;                        /* v2 only */
+};
 
-static void reset_timeout(void)
+static void upload_pack_data_init(struct upload_pack_data *data)
+{
+       struct string_list symref = STRING_LIST_INIT_DUP;
+       struct string_list wanted_refs = STRING_LIST_INIT_DUP;
+       struct object_array want_obj = OBJECT_ARRAY_INIT;
+       struct object_array have_obj = OBJECT_ARRAY_INIT;
+       struct oid_array haves = OID_ARRAY_INIT;
+       struct object_array shallows = OBJECT_ARRAY_INIT;
+       struct string_list deepen_not = STRING_LIST_INIT_DUP;
+
+       memset(data, 0, sizeof(*data));
+       data->symref = symref;
+       data->wanted_refs = wanted_refs;
+       data->want_obj = want_obj;
+       data->have_obj = have_obj;
+       data->haves = haves;
+       data->shallows = shallows;
+       data->deepen_not = deepen_not;
+       packet_writer_init(&data->writer, 1);
+
+       data->keepalive = 5;
+}
+
+static void upload_pack_data_clear(struct upload_pack_data *data)
+{
+       string_list_clear(&data->symref, 1);
+       string_list_clear(&data->wanted_refs, 1);
+       object_array_clear(&data->want_obj);
+       object_array_clear(&data->have_obj);
+       oid_array_clear(&data->haves);
+       object_array_clear(&data->shallows);
+       string_list_clear(&data->deepen_not, 0);
+       list_objects_filter_release(&data->filter_options);
+
+       free((char *)data->pack_objects_hook);
+}
+
+static void reset_timeout(unsigned int timeout)
 {
        alarm(timeout);
 }
 
-static void send_client_data(int fd, const char *data, ssize_t sz)
+static void send_client_data(int fd, const char *data, ssize_t sz,
+                            int use_sideband)
 {
        if (use_sideband) {
                send_sideband(1, fd, data, sz, use_sideband);
@@ -102,9 +173,7 @@ static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
        return 0;
 }
 
-static void create_pack_file(const struct object_array *have_obj,
-                            const struct object_array *want_obj,
-                            struct list_objects_filter_options *filter_options)
+static void create_pack_file(struct upload_pack_data *pack_data)
 {
        struct child_process pack_objects = CHILD_PROCESS_INIT;
        char data[8193], progress[128];
@@ -115,10 +184,10 @@ static void create_pack_file(const struct object_array *have_obj,
        int i;
        FILE *pipe_fd;
 
-       if (!pack_objects_hook)
+       if (!pack_data->pack_objects_hook)
                pack_objects.git_cmd = 1;
        else {
-               argv_array_push(&pack_objects.args, pack_objects_hook);
+               argv_array_push(&pack_objects.args, pack_data->pack_objects_hook);
                argv_array_push(&pack_objects.args, "git");
                pack_objects.use_shell = 1;
        }
@@ -129,21 +198,21 @@ static void create_pack_file(const struct object_array *have_obj,
        }
        argv_array_push(&pack_objects.args, "pack-objects");
        argv_array_push(&pack_objects.args, "--revs");
-       if (use_thin_pack)
+       if (pack_data->use_thin_pack)
                argv_array_push(&pack_objects.args, "--thin");
 
        argv_array_push(&pack_objects.args, "--stdout");
        if (shallow_nr)
                argv_array_push(&pack_objects.args, "--shallow");
-       if (!no_progress)
+       if (!pack_data->no_progress)
                argv_array_push(&pack_objects.args, "--progress");
-       if (use_ofs_delta)
+       if (pack_data->use_ofs_delta)
                argv_array_push(&pack_objects.args, "--delta-base-offset");
-       if (use_include_tag)
+       if (pack_data->use_include_tag)
                argv_array_push(&pack_objects.args, "--include-tag");
-       if (filter_options->choice) {
+       if (pack_data->filter_options.choice) {
                const char *spec =
-                       expand_list_objects_filter_spec(filter_options);
+                       expand_list_objects_filter_spec(&pack_data->filter_options);
                if (pack_objects.use_shell) {
                        struct strbuf buf = STRBUF_INIT;
                        sq_quote_buf(&buf, spec);
@@ -167,13 +236,13 @@ static void create_pack_file(const struct object_array *have_obj,
        if (shallow_nr)
                for_each_commit_graft(write_one_shallow, pipe_fd);
 
-       for (i = 0; i < want_obj->nr; i++)
+       for (i = 0; i < pack_data->want_obj.nr; i++)
                fprintf(pipe_fd, "%s\n",
-                       oid_to_hex(&want_obj->objects[i].item->oid));
+                       oid_to_hex(&pack_data->want_obj.objects[i].item->oid));
        fprintf(pipe_fd, "--not\n");
-       for (i = 0; i < have_obj->nr; i++)
+       for (i = 0; i < pack_data->have_obj.nr; i++)
                fprintf(pipe_fd, "%s\n",
-                       oid_to_hex(&have_obj->objects[i].item->oid));
+                       oid_to_hex(&pack_data->have_obj.objects[i].item->oid));
        for (i = 0; i < extra_edge_obj.nr; i++)
                fprintf(pipe_fd, "%s\n",
                        oid_to_hex(&extra_edge_obj.objects[i].item->oid));
@@ -187,10 +256,10 @@ static void create_pack_file(const struct object_array *have_obj,
 
        while (1) {
                struct pollfd pfd[2];
-               int pe, pu, pollsize;
+               int pe, pu, pollsize, polltimeout;
                int ret;
 
-               reset_timeout();
+               reset_timeout(pack_data->timeout);
 
                pollsize = 0;
                pe = pu = -1;
@@ -211,8 +280,11 @@ static void create_pack_file(const struct object_array *have_obj,
                if (!pollsize)
                        break;
 
-               ret = poll(pfd, pollsize,
-                       keepalive < 0 ? -1 : 1000 * keepalive);
+               polltimeout = pack_data->keepalive < 0
+                       ? -1
+                       : 1000 * pack_data->keepalive;
+
+               ret = poll(pfd, pollsize, polltimeout);
 
                if (ret < 0) {
                        if (errno != EINTR) {
@@ -228,7 +300,8 @@ static void create_pack_file(const struct object_array *have_obj,
                        sz = xread(pack_objects.err, progress,
                                  sizeof(progress));
                        if (0 < sz)
-                               send_client_data(2, progress, sz);
+                               send_client_data(2, progress, sz,
+                                                pack_data->use_sideband);
                        else if (sz == 0) {
                                close(pack_objects.err);
                                pack_objects.err = -1;
@@ -271,7 +344,8 @@ static void create_pack_file(const struct object_array *have_obj,
                        }
                        else
                                buffered = -1;
-                       send_client_data(1, data, sz);
+                       send_client_data(1, data, sz,
+                                        pack_data->use_sideband);
                }
 
                /*
@@ -284,7 +358,7 @@ static void create_pack_file(const struct object_array *have_obj,
                 * protocol to say anything, so those clients are just out of
                 * luck.
                 */
-               if (!ret && use_sideband) {
+               if (!ret && pack_data->use_sideband) {
                        static const char buf[] = "0005\1";
                        write_or_die(1, buf, 5);
                }
@@ -298,15 +372,17 @@ static void create_pack_file(const struct object_array *have_obj,
        /* flush the data */
        if (0 <= buffered) {
                data[0] = buffered;
-               send_client_data(1, data, 1);
+               send_client_data(1, data, 1,
+                                pack_data->use_sideband);
                fprintf(stderr, "flushed.\n");
        }
-       if (use_sideband)
+       if (pack_data->use_sideband)
                packet_flush(1);
        return;
 
  fail:
-       send_client_data(3, abort_msg, sizeof(abort_msg));
+       send_client_data(3, abort_msg, sizeof(abort_msg),
+                        pack_data->use_sideband);
        die("git upload-pack: %s", abort_msg);
 }
 
@@ -358,9 +434,8 @@ static int ok_to_give_up(const struct object_array *have_obj,
                                            min_generation);
 }
 
-static int get_common_commits(struct packet_reader *reader,
-                             struct object_array *have_obj,
-                             struct object_array *want_obj)
+static int get_common_commits(struct upload_pack_data *data,
+                             struct packet_reader *reader)
 {
        struct object_id oid;
        char last_hex[GIT_MAX_HEXSZ + 1];
@@ -373,34 +448,37 @@ static int get_common_commits(struct packet_reader *reader,
        for (;;) {
                const char *arg;
 
-               reset_timeout();
+               reset_timeout(data->timeout);
 
                if (packet_reader_read(reader) != PACKET_READ_NORMAL) {
-                       if (multi_ack == 2 && got_common
-                           && !got_other && ok_to_give_up(have_obj, want_obj)) {
+                       if (data->multi_ack == MULTI_ACK_DETAILED
+                           && got_common
+                           && !got_other
+                           && ok_to_give_up(&data->have_obj, &data->want_obj)) {
                                sent_ready = 1;
                                packet_write_fmt(1, "ACK %s ready\n", last_hex);
                        }
-                       if (have_obj->nr == 0 || multi_ack)
+                       if (data->have_obj.nr == 0 || data->multi_ack)
                                packet_write_fmt(1, "NAK\n");
 
-                       if (no_done && sent_ready) {
+                       if (data->no_done && sent_ready) {
                                packet_write_fmt(1, "ACK %s\n", last_hex);
                                return 0;
                        }
-                       if (stateless_rpc)
+                       if (data->stateless_rpc)
                                exit(0);
                        got_common = 0;
                        got_other = 0;
                        continue;
                }
                if (skip_prefix(reader->line, "have ", &arg)) {
-                       switch (got_oid(arg, &oid, have_obj)) {
+                       switch (got_oid(arg, &oid, &data->have_obj)) {
                        case -1: /* they have what we do not */
                                got_other = 1;
-                               if (multi_ack && ok_to_give_up(have_obj, want_obj)) {
+                               if (data->multi_ack
+                                   && ok_to_give_up(&data->have_obj, &data->want_obj)) {
                                        const char *hex = oid_to_hex(&oid);
-                                       if (multi_ack == 2) {
+                                       if (data->multi_ack == MULTI_ACK_DETAILED) {
                                                sent_ready = 1;
                                                packet_write_fmt(1, "ACK %s ready\n", hex);
                                        } else
@@ -410,19 +488,19 @@ static int get_common_commits(struct packet_reader *reader,
                        default:
                                got_common = 1;
                                oid_to_hex_r(last_hex, &oid);
-                               if (multi_ack == 2)
+                               if (data->multi_ack == MULTI_ACK_DETAILED)
                                        packet_write_fmt(1, "ACK %s common\n", last_hex);
-                               else if (multi_ack)
+                               else if (data->multi_ack)
                                        packet_write_fmt(1, "ACK %s continue\n", last_hex);
-                               else if (have_obj->nr == 1)
+                               else if (data->have_obj.nr == 1)
                                        packet_write_fmt(1, "ACK %s\n", last_hex);
                                break;
                        }
                        continue;
                }
                if (!strcmp(reader->line, "done")) {
-                       if (have_obj->nr > 0) {
-                               if (multi_ack)
+                       if (data->have_obj.nr > 0) {
+                               if (data->multi_ack)
                                        packet_write_fmt(1, "ACK %s\n", last_hex);
                                return 0;
                        }
@@ -592,8 +670,7 @@ error:
        return 1;
 }
 
-static void check_non_tip(struct object_array *want_obj,
-                         struct packet_writer *writer)
+static void check_non_tip(struct upload_pack_data *data)
 {
        int i;
 
@@ -602,18 +679,19 @@ static void check_non_tip(struct object_array *want_obj,
         * uploadpack.allowReachableSHA1InWant,
         * non-tip requests can never happen.
         */
-       if (!stateless_rpc && !(allow_unadvertised_object_request & ALLOW_REACHABLE_SHA1))
+       if (!data->stateless_rpc
+           && !(allow_unadvertised_object_request & ALLOW_REACHABLE_SHA1))
                goto error;
-       if (!has_unreachable(want_obj))
+       if (!has_unreachable(&data->want_obj))
                /* All the non-tip ones are ancestors of what we advertised */
                return;
 
 error:
        /* Pick one of them (we know there at least is one) */
-       for (i = 0; i < want_obj->nr; i++) {
-               struct object *o = want_obj->objects[i].item;
+       for (i = 0; i < data->want_obj.nr; i++) {
+               struct object *o = data->want_obj.objects[i].item;
                if (!is_our_ref(o)) {
-                       packet_writer_error(writer,
+                       packet_writer_error(&data->writer,
                                            "upload-pack: not our ref %s",
                                            oid_to_hex(&o->oid));
                        die("git upload-pack: not our ref %s",
@@ -849,45 +927,36 @@ static int process_deepen_not(const char *line, struct string_list *deepen_not,
        return 0;
 }
 
-static void receive_needs(struct packet_reader *reader,
-                         struct object_array *want_obj,
-                         struct list_objects_filter_options *filter_options)
+static void receive_needs(struct upload_pack_data *data,
+                         struct packet_reader *reader)
 {
-       struct object_array shallows = OBJECT_ARRAY_INIT;
-       struct string_list deepen_not = STRING_LIST_INIT_DUP;
-       int depth = 0;
        int has_non_tip = 0;
-       timestamp_t deepen_since = 0;
-       int deepen_rev_list = 0;
-       int deepen_relative = 0;
-       struct packet_writer writer;
 
        shallow_nr = 0;
-       packet_writer_init(&writer, 1);
        for (;;) {
                struct object *o;
                const char *features;
                struct object_id oid_buf;
                const char *arg;
 
-               reset_timeout();
+               reset_timeout(data->timeout);
                if (packet_reader_read(reader) != PACKET_READ_NORMAL)
                        break;
 
-               if (process_shallow(reader->line, &shallows))
+               if (process_shallow(reader->line, &data->shallows))
                        continue;
-               if (process_deepen(reader->line, &depth))
+               if (process_deepen(reader->line, &data->depth))
                        continue;
-               if (process_deepen_since(reader->line, &deepen_since, &deepen_rev_list))
+               if (process_deepen_since(reader->line, &data->deepen_since, &data->deepen_rev_list))
                        continue;
-               if (process_deepen_not(reader->line, &deepen_not, &deepen_rev_list))
+               if (process_deepen_not(reader->line, &data->deepen_not, &data->deepen_rev_list))
                        continue;
 
                if (skip_prefix(reader->line, "filter ", &arg)) {
-                       if (!filter_capability_requested)
+                       if (!data->filter_capability_requested)
                                die("git upload-pack: filtering capability not negotiated");
-                       list_objects_filter_die_if_populated(filter_options);
-                       parse_list_objects_filter(filter_options, arg);
+                       list_objects_filter_die_if_populated(&data->filter_options);
+                       parse_list_objects_filter(&data->filter_options, arg);
                        continue;
                }
 
@@ -897,31 +966,32 @@ static void receive_needs(struct packet_reader *reader,
                            "expected to get object ID, not '%s'", reader->line);
 
                if (parse_feature_request(features, "deepen-relative"))
-                       deepen_relative = 1;
+                       data->deepen_relative = 1;
                if (parse_feature_request(features, "multi_ack_detailed"))
-                       multi_ack = 2;
+                       data->multi_ack = MULTI_ACK_DETAILED;
                else if (parse_feature_request(features, "multi_ack"))
-                       multi_ack = 1;
+                       data->multi_ack = MULTI_ACK;
                if (parse_feature_request(features, "no-done"))
-                       no_done = 1;
+                       data->no_done = 1;
                if (parse_feature_request(features, "thin-pack"))
-                       use_thin_pack = 1;
+                       data->use_thin_pack = 1;
                if (parse_feature_request(features, "ofs-delta"))
-                       use_ofs_delta = 1;
+                       data->use_ofs_delta = 1;
                if (parse_feature_request(features, "side-band-64k"))
-                       use_sideband = LARGE_PACKET_MAX;
+                       data->use_sideband = LARGE_PACKET_MAX;
                else if (parse_feature_request(features, "side-band"))
-                       use_sideband = DEFAULT_PACKET_MAX;
+                       data->use_sideband = DEFAULT_PACKET_MAX;
                if (parse_feature_request(features, "no-progress"))
-                       no_progress = 1;
+                       data->no_progress = 1;
                if (parse_feature_request(features, "include-tag"))
-                       use_include_tag = 1;
-               if (allow_filter && parse_feature_request(features, "filter"))
-                       filter_capability_requested = 1;
+                       data->use_include_tag = 1;
+               if (data->allow_filter &&
+                   parse_feature_request(features, "filter"))
+                       data->filter_capability_requested = 1;
 
                o = parse_object(the_repository, &oid_buf);
                if (!o) {
-                       packet_writer_error(&writer,
+                       packet_writer_error(&data->writer,
                                            "upload-pack: not our ref %s",
                                            oid_to_hex(&oid_buf));
                        die("git upload-pack: not our ref %s",
@@ -932,7 +1002,7 @@ static void receive_needs(struct packet_reader *reader,
                        if (!((allow_unadvertised_object_request & ALLOW_ANY_SHA1) == ALLOW_ANY_SHA1
                              || is_our_ref(o)))
                                has_non_tip = 1;
-                       add_object_array(o, NULL, want_obj);
+                       add_object_array(o, NULL, &data->want_obj);
                }
        }
 
@@ -944,19 +1014,23 @@ static void receive_needs(struct packet_reader *reader,
         * by another process that handled the initial request.
         */
        if (has_non_tip)
-               check_non_tip(want_obj, &writer);
+               check_non_tip(data);
 
-       if (!use_sideband && daemon_mode)
-               no_progress = 1;
+       if (!data->use_sideband && data->daemon_mode)
+               data->no_progress = 1;
 
-       if (depth == 0 && !deepen_rev_list && shallows.nr == 0)
+       if (data->depth == 0 && !data->deepen_rev_list && data->shallows.nr == 0)
                return;
 
-       if (send_shallow_list(&writer, depth, deepen_rev_list, deepen_since,
-                             &deepen_not, deepen_relative, &shallows,
-                             want_obj))
+       if (send_shallow_list(&data->writer,
+                             data->depth,
+                             data->deepen_rev_list,
+                             data->deepen_since,
+                             &data->deepen_not,
+                             data->deepen_relative,
+                             &data->shallows,
+                             &data->want_obj))
                packet_flush(1);
-       object_array_clear(&shallows);
 }
 
 /* return non-zero if the ref is hidden, otherwise 0 */
@@ -1000,6 +1074,7 @@ static int send_ref(const char *refname, const struct object_id *oid,
                " deepen-relative no-progress include-tag multi_ack_detailed";
        const char *refname_nons = strip_namespace(refname);
        struct object_id peeled;
+       struct upload_pack_data *data = cb_data;
 
        if (mark_our_ref(refname_nons, refname, oid))
                return 0;
@@ -1007,7 +1082,7 @@ static int send_ref(const char *refname, const struct object_id *oid,
        if (capabilities) {
                struct strbuf symref_info = STRBUF_INIT;
 
-               format_symref_info(&symref_info, cb_data);
+               format_symref_info(&symref_info, &data->symref);
                packet_write_fmt(1, "%s %s%c%s%s%s%s%s%s agent=%s\n",
                             oid_to_hex(oid), refname_nons,
                             0, capabilities,
@@ -1015,9 +1090,9 @@ static int send_ref(const char *refname, const struct object_id *oid,
                                     " allow-tip-sha1-in-want" : "",
                             (allow_unadvertised_object_request & ALLOW_REACHABLE_SHA1) ?
                                     " allow-reachable-sha1-in-want" : "",
-                            stateless_rpc ? " no-done" : "",
+                            data->stateless_rpc ? " no-done" : "",
                             symref_info.buf,
-                            allow_filter ? " filter" : "",
+                            data->allow_filter ? " filter" : "",
                             git_user_agent_sanitized());
                strbuf_release(&symref_info);
        } else {
@@ -1045,8 +1120,10 @@ static int find_symref(const char *refname, const struct object_id *oid,
        return 0;
 }
 
-static int upload_pack_config(const char *var, const char *value, void *unused)
+static int upload_pack_config(const char *var, const char *value, void *cb_data)
 {
+       struct upload_pack_data *data = cb_data;
+
        if (!strcmp("uploadpack.allowtipsha1inwant", var)) {
                if (git_config_bool(var, value))
                        allow_unadvertised_object_request |= ALLOW_TIP_SHA1;
@@ -1063,15 +1140,15 @@ static int upload_pack_config(const char *var, const char *value, void *unused)
                else
                        allow_unadvertised_object_request &= ~ALLOW_ANY_SHA1;
        } else if (!strcmp("uploadpack.keepalive", var)) {
-               keepalive = git_config_int(var, value);
-               if (!keepalive)
-                       keepalive = -1;
+               data->keepalive = git_config_int(var, value);
+               if (!data->keepalive)
+                       data->keepalive = -1;
        } else if (!strcmp("uploadpack.allowfilter", var)) {
-               allow_filter = git_config_bool(var, value);
+               data->allow_filter = git_config_bool(var, value);
        } else if (!strcmp("uploadpack.allowrefinwant", var)) {
-               allow_ref_in_want = git_config_bool(var, value);
+               data->allow_ref_in_want = git_config_bool(var, value);
        } else if (!strcmp("uploadpack.allowsidebandall", var)) {
-               allow_sideband_all = git_config_bool(var, value);
+               data->allow_sideband_all = git_config_bool(var, value);
        } else if (!strcmp("core.precomposeunicode", var)) {
                precomposed_unicode = git_config_bool(var, value);
        }
@@ -1079,7 +1156,7 @@ static int upload_pack_config(const char *var, const char *value, void *unused)
        if (current_config_scope() != CONFIG_SCOPE_LOCAL &&
        current_config_scope() != CONFIG_SCOPE_WORKTREE) {
                if (!strcmp("uploadpack.packobjectshook", var))
-                       return git_config_string(&pack_objects_hook, var, value);
+                       return git_config_string(&data->pack_objects_hook, var, value);
        }
 
        return parse_hide_refs_config(var, value, "uploadpack");
@@ -1087,99 +1164,43 @@ static int upload_pack_config(const char *var, const char *value, void *unused)
 
 void upload_pack(struct upload_pack_options *options)
 {
-       struct string_list symref = STRING_LIST_INIT_DUP;
-       struct object_array want_obj = OBJECT_ARRAY_INIT;
        struct packet_reader reader;
-       struct list_objects_filter_options filter_options;
+       struct upload_pack_data data;
 
-       stateless_rpc = options->stateless_rpc;
-       timeout = options->timeout;
-       daemon_mode = options->daemon_mode;
+       upload_pack_data_init(&data);
 
-       memset(&filter_options, 0, sizeof(filter_options));
+       git_config(upload_pack_config, &data);
 
-       git_config(upload_pack_config, NULL);
+       data.stateless_rpc = options->stateless_rpc;
+       data.daemon_mode = options->daemon_mode;
+       data.timeout = options->timeout;
 
-       head_ref_namespaced(find_symref, &symref);
+       head_ref_namespaced(find_symref, &data.symref);
 
-       if (options->advertise_refs || !stateless_rpc) {
-               reset_timeout();
-               head_ref_namespaced(send_ref, &symref);
-               for_each_namespaced_ref(send_ref, &symref);
+       if (options->advertise_refs || !data.stateless_rpc) {
+               reset_timeout(data.timeout);
+               head_ref_namespaced(send_ref, &data);
+               for_each_namespaced_ref(send_ref, &data);
                advertise_shallow_grafts(1);
                packet_flush(1);
        } else {
                head_ref_namespaced(check_ref, NULL);
                for_each_namespaced_ref(check_ref, NULL);
        }
-       string_list_clear(&symref, 1);
-       if (options->advertise_refs)
-               return;
 
-       packet_reader_init(&reader, 0, NULL, 0,
-                          PACKET_READ_CHOMP_NEWLINE |
-                          PACKET_READ_DIE_ON_ERR_PACKET);
+       if (!options->advertise_refs) {
+               packet_reader_init(&reader, 0, NULL, 0,
+                                  PACKET_READ_CHOMP_NEWLINE |
+                                  PACKET_READ_DIE_ON_ERR_PACKET);
 
-       receive_needs(&reader, &want_obj, &filter_options);
-       if (want_obj.nr) {
-               struct object_array have_obj = OBJECT_ARRAY_INIT;
-               get_common_commits(&reader, &have_obj, &want_obj);
-               create_pack_file(&have_obj, &want_obj, &filter_options);
+               receive_needs(&data, &reader);
+               if (data.want_obj.nr) {
+                       get_common_commits(&data, &reader);
+                       create_pack_file(&data);
+               }
        }
 
-       list_objects_filter_release(&filter_options);
-}
-
-struct upload_pack_data {
-       struct object_array wants;
-       struct string_list wanted_refs;
-       struct oid_array haves;
-
-       struct object_array shallows;
-       struct string_list deepen_not;
-       int depth;
-       timestamp_t deepen_since;
-       int deepen_rev_list;
-       int deepen_relative;
-
-       struct list_objects_filter_options filter_options;
-
-       struct packet_writer writer;
-
-       unsigned stateless_rpc : 1;
-
-       unsigned use_thin_pack : 1;
-       unsigned use_ofs_delta : 1;
-       unsigned no_progress : 1;
-       unsigned use_include_tag : 1;
-       unsigned done : 1;
-};
-
-static void upload_pack_data_init(struct upload_pack_data *data)
-{
-       struct object_array wants = OBJECT_ARRAY_INIT;
-       struct string_list wanted_refs = STRING_LIST_INIT_DUP;
-       struct oid_array haves = OID_ARRAY_INIT;
-       struct object_array shallows = OBJECT_ARRAY_INIT;
-       struct string_list deepen_not = STRING_LIST_INIT_DUP;
-
-       memset(data, 0, sizeof(*data));
-       data->wants = wants;
-       data->wanted_refs = wanted_refs;
-       data->haves = haves;
-       data->shallows = shallows;
-       data->deepen_not = deepen_not;
-       packet_writer_init(&data->writer, 1);
-}
-
-static void upload_pack_data_clear(struct upload_pack_data *data)
-{
-       object_array_clear(&data->wants);
-       string_list_clear(&data->wanted_refs, 1);
-       oid_array_clear(&data->haves);
-       object_array_clear(&data->shallows);
-       string_list_clear(&data->deepen_not, 0);
-       list_objects_filter_release(&data->filter_options);
+       upload_pack_data_clear(&data);
 }
 
 static int parse_want(struct packet_writer *writer, const char *line,
@@ -1260,19 +1281,18 @@ static int parse_have(const char *line, struct oid_array *haves)
 }
 
 static void process_args(struct packet_reader *request,
-                        struct upload_pack_data *data,
-                        struct object_array *want_obj)
+                        struct upload_pack_data *data)
 {
        while (packet_reader_read(request) == PACKET_READ_NORMAL) {
                const char *arg = request->line;
                const char *p;
 
                /* process want */
-               if (parse_want(&data->writer, arg, want_obj))
+               if (parse_want(&data->writer, arg, &data->want_obj))
                        continue;
-               if (allow_ref_in_want &&
+               if (data->allow_ref_in_want &&
                    parse_want_ref(&data->writer, arg, &data->wanted_refs,
-                                  want_obj))
+                                  &data->want_obj))
                        continue;
                /* process have line */
                if (parse_have(arg, &data->haves))
@@ -1280,19 +1300,19 @@ static void process_args(struct packet_reader *request,
 
                /* process args like thin-pack */
                if (!strcmp(arg, "thin-pack")) {
-                       use_thin_pack = 1;
+                       data->use_thin_pack = 1;
                        continue;
                }
                if (!strcmp(arg, "ofs-delta")) {
-                       use_ofs_delta = 1;
+                       data->use_ofs_delta = 1;
                        continue;
                }
                if (!strcmp(arg, "no-progress")) {
-                       no_progress = 1;
+                       data->no_progress = 1;
                        continue;
                }
                if (!strcmp(arg, "include-tag")) {
-                       use_include_tag = 1;
+                       data->use_include_tag = 1;
                        continue;
                }
                if (!strcmp(arg, "done")) {
@@ -1316,14 +1336,14 @@ static void process_args(struct packet_reader *request,
                        continue;
                }
 
-               if (allow_filter && skip_prefix(arg, "filter ", &p)) {
+               if (data->allow_filter && skip_prefix(arg, "filter ", &p)) {
                        list_objects_filter_die_if_populated(&data->filter_options);
                        parse_list_objects_filter(&data->filter_options, p);
                        continue;
                }
 
                if ((git_env_bool("GIT_TEST_SIDEBAND_ALL", 0) ||
-                    allow_sideband_all) &&
+                    data->allow_sideband_all) &&
                    !strcmp(arg, "sideband-all")) {
                        data->writer.use_sideband = 1;
                        continue;
@@ -1403,17 +1423,16 @@ static int send_acks(struct packet_writer *writer, struct oid_array *acks,
        return 0;
 }
 
-static int process_haves_and_send_acks(struct upload_pack_data *data,
-                                      struct object_array *have_obj,
-                                      struct object_array *want_obj)
+static int process_haves_and_send_acks(struct upload_pack_data *data)
 {
        struct oid_array common = OID_ARRAY_INIT;
        int ret = 0;
 
-       process_haves(&data->haves, &common, have_obj);
+       process_haves(&data->haves, &common, &data->have_obj);
        if (data->done) {
                ret = 1;
-       } else if (send_acks(&data->writer, &common, have_obj, want_obj)) {
+       } else if (send_acks(&data->writer, &common,
+                            &data->have_obj, &data->want_obj)) {
                packet_writer_delim(&data->writer);
                ret = 1;
        } else {
@@ -1445,8 +1464,7 @@ static void send_wanted_ref_info(struct upload_pack_data *data)
        packet_writer_delim(&data->writer);
 }
 
-static void send_shallow_info(struct upload_pack_data *data,
-                             struct object_array *want_obj)
+static void send_shallow_info(struct upload_pack_data *data)
 {
        /* No shallow info needs to be sent */
        if (!data->depth && !data->deepen_rev_list && !data->shallows.nr &&
@@ -1459,10 +1477,10 @@ static void send_shallow_info(struct upload_pack_data *data,
                               data->deepen_rev_list,
                               data->deepen_since, &data->deepen_not,
                               data->deepen_relative,
-                              &data->shallows, want_obj) &&
+                              &data->shallows, &data->want_obj) &&
            is_repository_shallow(the_repository))
                deepen(&data->writer, INFINITE_DEPTH, data->deepen_relative,
-                      &data->shallows, want_obj);
+                      &data->shallows, &data->want_obj);
 
        packet_delim(1);
 }
@@ -1479,22 +1497,20 @@ int upload_pack_v2(struct repository *r, struct argv_array *keys,
 {
        enum fetch_state state = FETCH_PROCESS_ARGS;
        struct upload_pack_data data;
-       struct object_array have_obj = OBJECT_ARRAY_INIT;
-       struct object_array want_obj = OBJECT_ARRAY_INIT;
 
        clear_object_flags(ALL_FLAGS);
 
-       git_config(upload_pack_config, NULL);
-
        upload_pack_data_init(&data);
-       use_sideband = LARGE_PACKET_MAX;
+       data.use_sideband = LARGE_PACKET_MAX;
+
+       git_config(upload_pack_config, &data);
 
        while (state != FETCH_DONE) {
                switch (state) {
                case FETCH_PROCESS_ARGS:
-                       process_args(request, &data, &want_obj);
+                       process_args(request, &data);
 
-                       if (!want_obj.nr) {
+                       if (!data.want_obj.nr) {
                                /*
                                 * Request didn't contain any 'want' lines,
                                 * guess they didn't want anything.
@@ -1514,18 +1530,17 @@ int upload_pack_v2(struct repository *r, struct argv_array *keys,
                        }
                        break;
                case FETCH_SEND_ACKS:
-                       if (process_haves_and_send_acks(&data, &have_obj,
-                                                       &want_obj))
+                       if (process_haves_and_send_acks(&data))
                                state = FETCH_SEND_PACK;
                        else
                                state = FETCH_DONE;
                        break;
                case FETCH_SEND_PACK:
                        send_wanted_ref_info(&data);
-                       send_shallow_info(&data, &want_obj);
+                       send_shallow_info(&data);
 
                        packet_writer_write(&data.writer, "packfile\n");
-                       create_pack_file(&have_obj, &want_obj, &data.filter_options);
+                       create_pack_file(&data);
                        state = FETCH_DONE;
                        break;
                case FETCH_DONE:
@@ -1534,8 +1549,6 @@ int upload_pack_v2(struct repository *r, struct argv_array *keys,
        }
 
        upload_pack_data_clear(&data);
-       object_array_clear(&have_obj);
-       object_array_clear(&want_obj);
        return 0;
 }