]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'vd/scalar-generalize-diagnose'
authorJunio C Hamano <gitster@pobox.com>
Thu, 25 Aug 2022 21:42:32 +0000 (14:42 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 25 Aug 2022 21:42:32 +0000 (14:42 -0700)
The "diagnose" feature to create a zip archive for diagnostic
material has been lifted from "scalar" and made into a feature of
"git bugreport".

* vd/scalar-generalize-diagnose:
  scalar: update technical doc roadmap
  scalar-diagnose: use 'git diagnose --mode=all'
  builtin/bugreport.c: create '--diagnose' option
  builtin/diagnose.c: add '--mode' option
  builtin/diagnose.c: create 'git diagnose' builtin
  diagnose.c: add option to configure archive contents
  scalar-diagnose: move functionality to common location
  scalar-diagnose: move 'get_disk_info()' to 'compat/'
  scalar-diagnose: add directory to archiver more gently
  scalar-diagnose: avoid 32-bit overflow of size_t
  scalar-diagnose: use "$GIT_UNZIP" in test

215 files changed:
Documentation/Makefile
Documentation/RelNotes/2.37.2.txt
Documentation/RelNotes/2.38.0.txt
Documentation/config/lsrefs.txt
Documentation/config/pack.txt
Documentation/config/protocol.txt
Documentation/git-add.txt
Documentation/git-am.txt
Documentation/git-bundle.txt
Documentation/git-cat-file.txt
Documentation/git-cherry-pick.txt
Documentation/git-commit-graph.txt
Documentation/git-help.txt
Documentation/git-ls-files.txt
Documentation/git-merge.txt
Documentation/git-multi-pack-index.txt
Documentation/git-rebase.txt
Documentation/git-reflog.txt
Documentation/git-revert.txt
Documentation/git-upload-pack.txt
Documentation/git.txt
Documentation/gitformat-bundle.txt [moved from Documentation/technical/bundle-format.txt with 79% similarity]
Documentation/gitformat-chunk.txt [moved from Documentation/technical/chunk-format.txt with 89% similarity]
Documentation/gitformat-commit-graph.txt [moved from Documentation/technical/commit-graph-format.txt with 87% similarity]
Documentation/gitformat-index.txt [moved from Documentation/technical/index-format.txt with 98% similarity]
Documentation/gitformat-pack.txt [moved from Documentation/technical/pack-format.txt with 72% similarity]
Documentation/gitformat-signature.txt [moved from Documentation/technical/signature-format.txt with 96% similarity]
Documentation/gitprotocol-capabilities.txt [moved from Documentation/technical/protocol-capabilities.txt with 96% similarity]
Documentation/gitprotocol-common.txt [moved from Documentation/technical/protocol-common.txt with 89% similarity]
Documentation/gitprotocol-http.txt [moved from Documentation/technical/http-protocol.txt with 97% similarity]
Documentation/gitprotocol-pack.txt [moved from Documentation/technical/pack-protocol.txt with 98% similarity]
Documentation/gitprotocol-v2.txt [moved from Documentation/technical/protocol-v2.txt with 97% similarity]
Documentation/howto/recover-corrupted-object-harder.txt
Documentation/lint-man-section-order.perl
Documentation/rerere-options.txt [new file with mode: 0644]
Documentation/rev-list-options.txt
Documentation/technical/api-simple-ipc.txt
Documentation/technical/bundle-uri.txt [new file with mode: 0644]
Documentation/technical/cruft-packs.txt [deleted file]
Documentation/technical/hash-function-transition.txt
Documentation/technical/long-running-process-protocol.txt
Documentation/technical/packfile-uri.txt
Documentation/technical/partial-clone.txt
Documentation/user-manual.txt
Makefile
bisect.c
blame.c
bloom.c
builtin/cat-file.c
builtin/checkout.c
builtin/help.c
builtin/log.c
builtin/ls-files.c
builtin/merge.c
builtin/remote.c
builtin/reset.c
builtin/rev-list.c
builtin/rm.c
builtin/submodule--helper.c
builtin/symbolic-ref.c
bulk-checkin.c
cache.h
ci/lib.sh
command-list.txt
commit-graph.c
commit-graph.h
commit.c
compat/mingw.c
compat/mingw.h
compat/nonblock.c [new file with mode: 0644]
compat/nonblock.h [new file with mode: 0644]
config.c
config.mak.dev
config.mak.uname
contrib/buildsystems/CMakeLists.txt
contrib/credential/netrc/t-git-credential-netrc.sh
contrib/scalar/t/Makefile
contrib/subtree/t/Makefile
diff-lib.c
fetch-pack.c
fsck.c
fsck.h
git-compat-util.h
git-merge-resolve.sh
gitweb/gitweb.perl
help.c
help.h
hook.c
ident.c
merge-ort-wrappers.c
merge-ort.c
mergesort.c [deleted file]
mergesort.h
mergetools/vimdiff
midx.c
pack-bitmap-write.c
pack-bitmap.h
pack-revindex.h
packfile.c
pathspec.c
pathspec.h
read-cache.c
refspec.h
remote.c
remote.h
revision.c
revision.h
run-command.c
setup.c
t/Makefile
t/README
t/helper/test-fast-rebase.c
t/helper/test-mergesort.c
t/lib-perl.sh [new file with mode: 0644]
t/perf/p0071-sort.sh
t/perf/p2000-sparse-operations.sh
t/t0000-basic.sh
t/t0002-gitfile.sh
t/t0004-unwritable.sh
t/t0012-help.sh
t/t0027-auto-crlf.sh
t/t0032-reftable-unittest.sh
t/t0033-safe-directory.sh
t/t0050-filesystem.sh
t/t0071-sort.sh
t/t0095-bloom.sh
t/t0202-gettext-perl.sh
t/t0203-gettext-setlocale-sanity.sh
t/t1006-cat-file.sh
t/t1020-subdirectory.sh
t/t1092-sparse-checkout-compatibility.sh
t/t1401-symbolic-ref.sh
t/t1405-main-ref-store.sh
t/t1407-worktree-ref-store.sh
t/t1418-reflog-exists.sh
t/t1450-fsck.sh
t/t1503-rev-parse-verify.sh
t/t1701-racy-split-index.sh
t/t1800-hook.sh
t/t2006-checkout-index-basic.sh
t/t2020-checkout-detach.sh
t/t2023-checkout-m.sh
t/t2205-add-worktree-config.sh
t/t3001-ls-files-others-exclude.sh
t/t3012-ls-files-dedup.sh
t/t3013-ls-files-format.sh [new file with mode: 0755]
t/t3305-notes-fanout.sh
t/t3307-notes-man.sh
t/t3701-add-interactive.sh
t/t3920-crlf-messages.sh
t/t4017-diff-retval.sh
t/t4020-diff-external.sh
t/t4051-diff-function-context.sh
t/t4057-diff-combined-paths.sh
t/t4069-remerge-diff.sh
t/t4114-apply-typechange.sh
t/t4202-log.sh
t/t4203-mailmap.sh
t/t4301-merge-tree-write-tree.sh
t/t5315-pack-objects-compression.sh
t/t5318-commit-graph.sh
t/t5351-unpack-large-objects.sh
t/t5402-post-merge-hook.sh
t/t5500-fetch-pack.sh
t/t5503-tagfollow.sh
t/t5504-fetch-receive-strict.sh
t/t5516-fetch-push.sh
t/t5551-http-fetch-smart.sh
t/t5601-clone.sh
t/t5616-partial-clone.sh
t/t5703-upload-pack-ref-in-want.sh
t/t6102-rev-list-unexpected-objects.sh
t/t6115-rev-list-du.sh
t/t6402-merge-rename.sh
t/t6404-recursive-merge.sh
t/t6405-merge-symlinks.sh
t/t6407-merge-binary.sh
t/t6408-merge-up-to-date.sh
t/t6411-merge-filemode.sh
t/t6413-merge-crlf.sh
t/t6424-merge-unrelated-index-changes.sh
t/t6425-merge-rename-delete.sh
t/t6431-merge-criscross.sh
t/t6437-submodule-merge.sh
t/t6439-merge-co-error-msgs.sh
t/t7007-show.sh
t/t7060-wtstatus.sh
t/t7062-wtstatus-ignorecase.sh
t/t7063-status-untracked-cache.sh
t/t7110-reset-merge.sh
t/t7111-reset-table.sh
t/t7402-submodule-rebase.sh
t/t7503-pre-commit-and-pre-merge-commit-hooks.sh
t/t7607-merge-state.sh [new file with mode: 0755]
t/t7609-mergetool--lib.sh
t/t9100-git-svn-basic.sh
t/t9122-git-svn-author.sh
t/t9162-git-svn-dcommit-interactive.sh
t/t9700-perl-git.sh
t/t9901-git-web--browse.sh
t/test-lib-functions.sh
t/test-lib.sh
tree-walk.c
tree-walk.h
unpack-trees.c
upload-pack.c
wrapper.c
xdiff/xdiff.h
xdiff/xdiffi.c
xdiff/xhistogram.c
xdiff/xmacros.h
xdiff/xpatience.c
xdiff/xprepare.c
xdiff/xutils.c
xdiff/xutils.h

index 4f801f4e4c9470c42ae7933a68970a4984bc54b5..bd6b6fcb93085d550846defa9802b9d15a487034 100644 (file)
@@ -24,10 +24,21 @@ MAN1_TXT += gitweb.txt
 
 # man5 / man7 guides (note: new guides should also be added to command-list.txt)
 MAN5_TXT += gitattributes.txt
+MAN5_TXT += gitformat-bundle.txt
+MAN5_TXT += gitformat-chunk.txt
+MAN5_TXT += gitformat-commit-graph.txt
+MAN5_TXT += gitformat-index.txt
+MAN5_TXT += gitformat-pack.txt
+MAN5_TXT += gitformat-signature.txt
 MAN5_TXT += githooks.txt
 MAN5_TXT += gitignore.txt
 MAN5_TXT += gitmailmap.txt
 MAN5_TXT += gitmodules.txt
+MAN5_TXT += gitprotocol-capabilities.txt
+MAN5_TXT += gitprotocol-common.txt
+MAN5_TXT += gitprotocol-http.txt
+MAN5_TXT += gitprotocol-pack.txt
+MAN5_TXT += gitprotocol-v2.txt
 MAN5_TXT += gitrepository-layout.txt
 MAN5_TXT += gitweb.conf.txt
 
@@ -95,26 +106,17 @@ TECH_DOCS += MyFirstObjectWalk
 TECH_DOCS += SubmittingPatches
 TECH_DOCS += ToolsForGit
 TECH_DOCS += technical/bitmap-format
-TECH_DOCS += technical/bundle-format
-TECH_DOCS += technical/cruft-packs
+TECH_DOCS += technical/bundle-uri
 TECH_DOCS += technical/hash-function-transition
-TECH_DOCS += technical/http-protocol
-TECH_DOCS += technical/index-format
 TECH_DOCS += technical/long-running-process-protocol
 TECH_DOCS += technical/multi-pack-index
-TECH_DOCS += technical/pack-format
 TECH_DOCS += technical/pack-heuristics
-TECH_DOCS += technical/pack-protocol
 TECH_DOCS += technical/parallel-checkout
 TECH_DOCS += technical/partial-clone
-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
 TECH_DOCS += technical/trivial-merge
 SP_ARTICLES += $(TECH_DOCS)
 SP_ARTICLES += technical/api-index
@@ -290,6 +292,8 @@ cmds_txt = cmds-ancillaryinterrogators.txt \
        cmds-synchingrepositories.txt \
        cmds-synchelpers.txt \
        cmds-guide.txt \
+       cmds-developerinterfaces.txt \
+       cmds-userinterfaces.txt \
        cmds-purehelpers.txt \
        cmds-foreignscminterface.txt
 
index d4acf9e5de6b2426f038ccf7a7fc003e87ac4174..d82b29e01404409790214bf881502f17ff18f88a 100644 (file)
@@ -44,4 +44,45 @@ Fixes since v2.37.1
  * Certain diff options are currently ignored when combined-diff is
    shown; mark them as incompatible with the feature.
 
+ * "git clone" from a repository with some ref whose HEAD is unborn
+   did not set the HEAD in the resulting repository correctly, which
+   has been corrected.
+
+ * mkstemp() emulation on Windows has been improved.
+
+ * Add missing documentation for "include" and "includeIf" features in
+   "git config" file format, which incidentally teaches the command
+   line completion to include them in its offerings.
+
+ * Avoid "white/black-list" in documentation and code comments.
+
+ * Workaround for a compiler warning against use of die() in
+   osx-keychain (in contrib/).
+
+ * Workaround for a false positive compiler warning.
+
+ * The resolve-undo information in the index was not protected against
+   GC, which has been corrected.
+
+ * A corner case bug where lazily fetching objects from a promisor
+   remote resulted in infinite recursion has been corrected.
+
+ * "git p4" working on UTF-16 files on Windows did not implement
+   CRLF-to-LF conversion correctly, which has been corrected.
+
+ * "git p4" did not handle non-ASCII client name well, which has been
+   corrected.
+
+ * "rerere-train" script (in contrib/) used to honor commit.gpgSign
+   while recreating the throw-away merges.
+
+ * "git checkout" miscounted the paths it updated, which has been
+   corrected.
+
+ * Fix for a bug that makes write-tree to fail to write out a
+   non-existent index as a tree, introduced in 2.37.
+
+ * There was a bug in the codepath to upgrade generation information
+   in commit-graph from v1 to v2 format, which has been corrected.
+
 Also contains minor documentation updates and code clean-ups.
index 66e278b4fee1fd7d63933eaa197edca1a242c1ac..4a08602e0d42c671b2b5228878ea6a25bb10e928 100644 (file)
@@ -36,6 +36,24 @@ UI, Workflows & Features
  * "git rebase -i" learns to update branches whose tip appear in the
    rebased range with "--update-refs" option.
 
+ * "git ls-files" learns the "--format" option to tweak its output.
+
+ * "git cat-file" learned an option to use the mailmap when showing
+   commit and tag objects.
+
+ * When "git merge" finds that it cannot perform a merge, it should
+   restore the working tree to the state before the command was
+   initiated, but in some corner cases it didn't.
+
+ * Operating modes like "--batch" of "git cat-file" command learned to
+   take NUL-terminated input, instead of one-item-per-line.
+
+ * "git rm" has become more aware of the sparse-index feature.
+
+ * "git rev-list --disk-usage" learned to take an optional value
+   "human" to show the reported value in human-readable format, like
+   "3.40MiB".
+
 
 Performance, Internal Implementation, Development Support etc.
 
@@ -85,6 +103,18 @@ Performance, Internal Implementation, Development Support etc.
 
  * Omit fsync-related trace2 entries when their values are all zero.
 
+ * The codepath to write multi-pack index has been taught to release a
+   large chunk of memory that holds an array of objects in the packs,
+   as soon as it is done with the array, to reduce memory consumption.
+
+ * Add a level of redirection to array allocation API in xdiff part,
+   to make it easier to share with the libgit2 project.
+
+ * "git fetch" client logs the partial clone filter used in the trace2
+   output.
+
+ * The "bundle URI" design gets documented.
+
 
 Fixes since v2.37
 -----------------
@@ -129,16 +159,13 @@ Fixes since v2.37
 
  * The resolve-undo information in the index was not protected against
    GC, which has been corrected.
-   (merge e0ad13977a jc/resolve-undo later to maint).
 
  * A corner case bug where lazily fetching objects from a promisor
    remote resulted in infinite recursion has been corrected.
-   (merge cb88b37cb9 hx/lookup-commit-in-graph-fix later to maint).
 
  * "git clone" from a repository with some ref whose HEAD is unborn
    did not set the HEAD in the resulting repository correctly, which
    has been corrected.
-   (merge daf7898abb jk/clone-unborn-confusion later to maint).
 
  * An earlier attempt to plug leaks placed a clean-up label to jump to
    at a bogus place, which as been corrected.
@@ -151,41 +178,91 @@ Fixes since v2.37
  * A fix for a regression in test framework.
 
  * mkstemp() emulation on Windows has been improved.
-   (merge ae25974de3 rs/mingw-tighten-mkstemp later to maint).
 
  * Add missing documentation for "include" and "includeIf" features in
    "git config" file format, which incidentally teaches the command
    line completion to include them in its offerings.
-   (merge 07aed58017 mb/config-document-include later to maint).
 
  * Avoid "white/black-list" in documentation and code comments.
-   (merge f5adaa5cc3 ds/doc-wo-whitelist later to maint).
 
  * Workaround for a compiler warning against use of die() in
    osx-keychain (in contrib/).
-   (merge f2fc531585 ld/osx-keychain-usage-fix later to maint).
 
  * Workaround for a false positive compiler warning.
-   (merge b4f52f09ae ds/win-syslog-compiler-fix later to maint).
 
  * "git p4" working on UTF-16 files on Windows did not implement
    CRLF-to-LF conversion correctly, which has been corrected.
-   (merge 4d35f74421 mb/p4-utf16-crlf later to maint).
 
  * "git p4" did not handle non-ASCII client name well, which has been
    corrected.
-   (merge d205483695 kk/p4-client-name-encoding-fix later to maint).
 
  * "rerere-train" script (in contrib/) used to honor commit.gpgSign
    while recreating the throw-away merges.
-   (merge cc391fc886 cl/rerere-train-with-no-sign later to maint).
 
  * "git checkout" miscounted the paths it updated, which has been
    corrected.
-   (merge 611c7785e8 mt/checkout-count-fix later to maint).
+
+ * Fix for a bug that makes write-tree to fail to write out a
+   non-existent index as a tree, introduced in 2.37.
+
+ * There was a bug in the codepath to upgrade generation information
+   in commit-graph from v1 to v2 format, which has been corrected.
+
+ * Gitweb had legacy URL shortener that is specific to the way
+   projects hosted on kernel.org used to (but no longer) work, which
+   has been removed.
+   (merge 75707da4fa jr/gitweb-title-shortening later to maint).
+
+ * Fix build procedure for Windows that uses CMake so that it can pick
+   up the shell interpreter from local installation location.
+   (merge 476e54b1c6 ca/unignore-local-installation-on-windows later to maint).
+
+ * Conditionally allow building Python interpreter on Windows
+   (merge 2f0623aaa7 js/mingw-with-python later to maint).
+
+ * Fix to lstat() emulation on Windows.
+   (merge 82ba1191ff js/lstat-mingw-enotdir-fix later to maint).
+
+ * Older gcc with -Wall complains about the universal zero initializer
+   "struct s = { 0 };" idiom, which makes developers' lives
+   inconvenient (as -Werror is enabled by DEVELOPER=YesPlease).  The
+   build procedure has been tweaked to help these compilers.
+   (merge b53a5f2416 jk/struct-zero-init-with-older-gcc later to maint).
+
+ * Plug memory leaks in the failure code path in the "merge-ort" merge
+   strategy backend.
+   (merge 1250dff32b js/ort-clean-up-after-failed-merge later to maint).
+
+ * "git symbolic-ref symref non..sen..se" is now diagnosed as an error.
+   (merge 04ede97211 lt/symbolic-ref-sanity later to maint).
+
+ * A follow-up fix to a fix for a regression in 2.36.
+   (merge 99ddc24672 ab/hooks-regression-fix later to maint).
+
+ * Avoid repeatedly running getconf to ask libc version in the test
+   suite, and instead just as it once per script.
+   (merge a6a58f7801 pw/use-glibc-tunable-for-malloc-optim later to maint).
+
+ * Platform-specific code that determines if a directory is OK to use
+   as a repository has been taught to report more details, especially
+   on Windows.
+   (merge 3f7207e2ea js/safe-directory-plus later to maint).
+
+ * "vimdiff3" regression fix.
+   (merge 34133d9658 fc/vimdiff-layout-vimdiff3-fix later to maint).
+
+ * "git fsck" reads mode from tree objects but canonicalizes the mode
+   before passing it to the logic to check object sanity, which has
+   hid broken tree objects from the checking logic.  This has been
+   corrected, but to help exiting projects with broken tree objects
+   that they cannot fix retroactively, the severity of anomalies this
+   code detects has been demoted to "info" for now.
+   (merge 4dd3b045f5 jk/fsck-tree-mode-bits-fix later to maint).
+
+ * Fixes to sparse index compatibility work for "reset" and "checkout"
+   commands.
+   (merge b15207b8cf vd/sparse-reset-checkout-fixes later to maint).
 
  * Other code cleanup, docfix, build fix, etc.
-   (merge a700395eaf ma/t4200-update later to maint).
-   (merge ae436f283c ma/sparse-checkout-cone-doc-fix later to maint).
-   (merge a10f6e2bda sg/index-format-doc-update later to maint).
-   (merge ce5f07983d mt/pkt-line-comment-tweak later to maint).
+   (merge 94955d576b gc/git-reflog-doc-markup later to maint).
+   (merge efae7ce692 po/doc-add-renormalize later to maint).
index adeda0f24d350aa084cecec0c57570420893223c..3d88fb0badba456ddabac242ee6657b44cbe5768 100644 (file)
@@ -1,7 +1,7 @@
 lsrefs.unborn::
        May be "advertise" (the default), "allow", or "ignore". If "advertise",
        the server will respond to the client sending "unborn" (as described in
-       protocol-v2.txt) and will advertise support for this feature during the
+       linkgit:gitprotocol-v2[5]) and will advertise support for this feature during the
        protocol v2 capability advertisement. "allow" is the same as
        "advertise" except that the server will not advertise support for this
        feature; this is useful for load-balanced servers that cannot be
index ad7f73a1eade701492ae7b773cc5bb5dc37e5053..3e581eab84a3b5af3b8583710f14b499171a6d49 100644 (file)
@@ -166,7 +166,7 @@ permuted into their appropriate location when writing a new bitmap.
 
 pack.writeReverseIndex::
        When true, git will write a corresponding .rev file (see:
-       link:../technical/pack-format.html[Documentation/technical/pack-format.txt])
+       linkgit:gitformat-pack[5])
        for each new packfile that it writes in all places except for
        linkgit:git-fast-import[1] and in the bulk checkin mechanism.
        Defaults to false.
index 756591d77b080ccfe5be5a26c899b65273cf4866..576038185148d8cce30170b3fe4888b6cf3aa2b6 100644 (file)
@@ -58,6 +58,6 @@ protocol.version::
 * `1` - the original wire protocol with the addition of a version string
   in the initial response from the server.
 
-* `2` - link:technical/protocol-v2.html[wire protocol version 2].
+* `2` - Wire protocol version 2, see linkgit:gitprotocol-v2[5].
 
 --
index 11eb70f16c7287d53b567368754a19f33d4c7fb2..9b37f356542d1d8787f0344abf3243996fb89476 100644 (file)
@@ -188,7 +188,9 @@ for "git add --no-all <pathspec>...", i.e. ignored removed files.
        forcibly add them again to the index.  This is useful after
        changing `core.autocrlf` configuration or the `text` attribute
        in order to correct files added with wrong CRLF/LF line endings.
-       This option implies `-u`.
+       This option implies `-u`. Lone CR characters are untouched, thus
+       while a CRLF cleans to LF, a CRCRLF sequence is only partially
+       cleaned to CRLF.
 
 --chmod=(+|-)x::
        Override the executable bit of the added files.  The executable
index 09107fb106703d14c9e695685e93f59e15362fc3..320da6c4f7624bceed05011033e177b234e28564 100644 (file)
@@ -112,10 +112,7 @@ default.   You can use `--no-utf8` to override this.
        am.threeWay configuration variable. For more information,
        see am.threeWay in linkgit:git-config[1].
 
---rerere-autoupdate::
---no-rerere-autoupdate::
-       Allow the rerere mechanism to update the index with the
-       result of auto-conflict resolution if possible.
+include::rerere-options.txt[]
 
 --ignore-space-change::
 --ignore-whitespace::
index 7685b570455cadc243929deecc197776326dfeba..6da617224312315fa85efd912d91e521397f604a 100644 (file)
@@ -56,10 +56,8 @@ using "thin packs", bundles created using exclusions are smaller in
 size. That they're "thin" under the hood is merely noted here as a
 curiosity, and as a reference to other documentation.
 
-See link:technical/bundle-format.html[the `bundle-format`
-documentation] for more details and the discussion of "thin pack" in
-link:technical/pack-format.html[the pack format documentation] for
-further details.
+See linkgit:gitformat-bundle[5] for more details and the discussion of
+"thin pack" in linkgit:gitformat-pack[5] for further details.
 
 OPTIONS
 -------
@@ -77,7 +75,7 @@ verify <file>::
        commits exist and are fully linked in the current repository.
        Then, 'git bundle' prints a list of missing commits, if any.
        Finally, information about additional capabilities, such as "object
-       filter", is printed. See "Capabilities" in link:technical/bundle-format.html
+       filter", is printed. See "Capabilities" in linkgit:gitformat-bundle[5]
        for more information. The exit code is zero for success, but will
        be nonzero if the bundle file is invalid.
 
@@ -337,6 +335,11 @@ You can also see what references it offers:
 $ git ls-remote mybundle
 ----------------
 
+FILE FORMAT
+-----------
+
+See linkgit:gitformat-bundle[5].
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index 24a811f0ef64b0f411c23a3598e7bc73e6a2e7ee..ec30b5c5743fd64ea9d42f4f451dc6ce0627d80a 100644 (file)
@@ -14,7 +14,7 @@ SYNOPSIS
 'git cat-file' (-t | -s) [--allow-unknown-type] <object>
 'git cat-file' (--batch | --batch-check | --batch-command) [--batch-all-objects]
             [--buffer] [--follow-symlinks] [--unordered]
-            [--textconv | --filters]
+            [--textconv | --filters] [-z]
 'git cat-file' (--textconv | --filters)
             [<rev>:<path|tree-ish> | --path=<path|tree-ish> <rev>]
 
@@ -63,6 +63,12 @@ OPTIONS
        or to ask for a "blob" with `<object>` being a tag object that
        points at it.
 
+--[no-]mailmap::
+--[no-]use-mailmap::
+       Use mailmap file to map author, committer and tagger names
+       and email addresses to canonical real names and email addresses.
+       See linkgit:git-shortlog[1].
+
 --textconv::
        Show the content as transformed by a textconv filter. In this case,
        `<object>` has to be of the form `<tree-ish>:<path>`, or `:<path>` in
@@ -207,6 +213,11 @@ respectively print:
        /etc/passwd
 --
 
+-z::
+       Only meaningful with `--batch`, `--batch-check`, or
+       `--batch-command`; input is NUL-delimited instead of
+       newline-delimited.
+
 
 OUTPUT
 ------
index 78dcc9171fb04da07a5ec0308e3be83d417fefeb..1e8ac9df60274067dcf57d3f9a82ba6f270ea7d9 100644 (file)
@@ -156,10 +156,7 @@ effect to your index in a row.
        Pass the merge strategy-specific option through to the
        merge strategy.  See linkgit:git-merge[1] for details.
 
---rerere-autoupdate::
---no-rerere-autoupdate::
-       Allow the rerere mechanism to update the index with the
-       result of auto-conflict resolution if possible.
+include::rerere-options.txt[]
 
 SEQUENCER SUBCOMMANDS
 ---------------------
index e1f48c95b3ca37e67af88e634a44db88dd1ca6a9..047decdb65b029f1974e7ca2c07406a5ca868bdd 100644 (file)
@@ -143,6 +143,11 @@ $ git rev-parse HEAD | git commit-graph write --stdin-commits --append
 ------------------------------------------------
 
 
+FILE FORMAT
+-----------
+
+see linkgit:gitformat-commit-graph[5].
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index 239c68db457098cae526e4691061c7373b61266a..2b0b5e390dcb94a651ca92ded8c1542a175f7f09 100644 (file)
@@ -9,14 +9,16 @@ SYNOPSIS
 --------
 [verse]
 'git help' [-a|--all] [--[no-]verbose] [--[no-]external-commands] [--[no-]aliases]
-'git help' [[-i|--info] [-m|--man] [-w|--web]] [<command>|<guide>]
+'git help' [[-i|--info] [-m|--man] [-w|--web]] [<command>|<doc>]
 'git help' [-g|--guides]
 'git help' [-c|--config]
+'git help' [--user-interfaces]
+'git help' [--developer-interfaces]
 
 DESCRIPTION
 -----------
 
-With no options and no '<command>' or '<guide>' given, the synopsis of the 'git'
+With no options and no '<command>' or '<doc>' given, the synopsis of the 'git'
 command and a list of the most commonly used Git commands are printed
 on the standard output.
 
@@ -26,8 +28,8 @@ printed on the standard output.
 If the option `--guides` or `-g` is given, a list of the
 Git concept guides is also printed on the standard output.
 
-If a command, or a guide, is given, a manual page for that command or
-guide is brought up. The 'man' program is used by default for this
+If a command or other documentation is given, the relevant manual page
+will be brought up. The 'man' program is used by default for this
 purpose, but this can be overridden by other options or configuration
 variables.
 
@@ -69,6 +71,23 @@ OPTIONS
 --guides::
        Prints a list of the Git concept guides on the standard output.
 
+--user-interfaces::
+       Prints a list of the repository, command and file interfaces
+       documentation on the standard output.
++
+In-repository file interfaces such as `.git/info/exclude` are
+documented here (see linkgit:gitrepository-layout[5]), as well as
+in-tree configuration such as `.mailmap` (see linkgit:gitmailmap[5]).
++
+This section of the documentation also covers general or widespread
+user-interface conventions (e.g. linkgit:gitcli[7]), and
+pseudo-configuration such as the file-based `.git/hooks/*` interface
+described in linkgit:githooks[5].
+
+--developer-interfaces::
+       Print list of file formats, protocols and other developer
+       interfaces documentation on the standard output.
+
 -i::
 --info::
        Display manual page for the command in the 'info' format. The
index 0dabf3f0ddc8d893818b3ce09436c1b5b1c72a33..d7986419c2507a7032961ba5b37ce6c73e5822f9 100644 (file)
@@ -20,7 +20,7 @@ SYNOPSIS
                [--exclude-standard]
                [--error-unmatch] [--with-tree=<tree-ish>]
                [--full-name] [--recurse-submodules]
-               [--abbrev[=<n>]] [--] [<file>...]
+               [--abbrev[=<n>]] [--format=<format>] [--] [<file>...]
 
 DESCRIPTION
 -----------
@@ -192,6 +192,13 @@ followed by the  ("attr/<eolattr>").
        to the contained files. Sparse directories will be shown with a
        trailing slash, such as "x/" for a sparse directory "x".
 
+--format=<format>::
+       A string that interpolates `%(fieldname)` from the result being shown.
+       It also interpolates `%%` to `%`, and `%xx` where `xx` are hex digits
+       interpolates to character with hex code `xx`; for example `%00`
+       interpolates to `\0` (NUL), `%09` to `\t` (TAB) and %0a to `\n` (LF).
+       --format cannot be combined with `-s`, `-o`, `-k`, `-t`, `--resolve-undo`
+       and `--eol`.
 \--::
        Do not interpret any more arguments as options.
 
@@ -223,6 +230,36 @@ quoted as explained for the configuration variable `core.quotePath`
 (see linkgit:git-config[1]).  Using `-z` the filename is output
 verbatim and the line is terminated by a NUL byte.
 
+It is possible to print in a custom format by using the `--format`
+option, which is able to interpolate different fields using
+a `%(fieldname)` notation. For example, if you only care about the
+"objectname" and "path" fields, you can execute with a specific
+"--format" like
+
+       git ls-files --format='%(objectname) %(path)'
+
+FIELD NAMES
+-----------
+The way each path is shown can be customized by using the
+`--format=<format>` option, where the %(fieldname) in the
+<format> string for various aspects of the index entry are
+interpolated.  The following "fieldname" are understood:
+
+objectmode::
+       The mode of the file which is recorded in the index.
+objectname::
+       The name of the file which is recorded in the index.
+stage::
+       The stage of the file which is recorded in the index.
+eolinfo:index::
+eolinfo:worktree::
+       The <eolinfo> (see the description of the `--eol` option) of
+       the contents in the index or in the worktree for the path.
+eolattr::
+       The <eolattr> (see the description of the `--eol` option)
+       that applies to the path.
+path::
+       The pathname of the file which is recorded in the index.
 
 EXCLUDE PATTERNS
 ----------------
index 3125473cc1d19140cf54f5286106b86823a9b91f..fee1dc2df28fe01230225578a4831103fa60916f 100644 (file)
@@ -90,10 +90,7 @@ invocations. The automated message can include the branch description.
 If `--log` is specified, a shortlog of the commits being merged
 will be appended to the specified message.
 
---rerere-autoupdate::
---no-rerere-autoupdate::
-       Allow the rerere mechanism to update the index with the
-       result of auto-conflict resolution if possible.
+include::rerere-options.txt[]
 
 --overwrite-ignore::
 --no-overwrite-ignore::
index c588fb91af1995c844108cee346512815e39cb3d..a48c3d5ea6301abcdda5df021ac8442ed149f23c 100644 (file)
@@ -128,8 +128,8 @@ $ git multi-pack-index verify
 SEE ALSO
 --------
 See link:technical/multi-pack-index.html[The Multi-Pack-Index Design
-Document] and link:technical/pack-format.html[The Multi-Pack-Index
-Format] for more information on the multi-pack-index feature.
+Document] and linkgit:gitformat-pack[5] for more information on the
+multi-pack-index feature and its file format.
 
 
 GIT
index 080658c87108699edd1bca1202d3b3354d4e2aa0..1877942180e3c956542373d8d22cd57e54e0dc41 100644 (file)
@@ -376,10 +376,7 @@ See also INCOMPATIBLE OPTIONS below.
 +
 See also INCOMPATIBLE OPTIONS below.
 
---rerere-autoupdate::
---no-rerere-autoupdate::
-       Allow the rerere mechanism to update the index with the
-       result of auto-conflict resolution if possible.
+include::rerere-options.txt[]
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
index 5ced7ad4f8bd7e0259db6439e4c3c7568ed8e874..db9d46edfa950e8107f54b4f61dda5115d5c07e4 100644 (file)
@@ -22,7 +22,7 @@ depending on the subcommand:
        [--rewrite] [--updateref] [--stale-fix]
        [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]
 'git reflog delete' [--rewrite] [--updateref]
-       [--dry-run | -n] [--verbose] <ref>@\{<specifier>\}...
+       [--dry-run | -n] [--verbose] <ref>@{<specifier>}...
 'git reflog exists' <ref>
 
 Reference logs, or "reflogs", record when the tips of branches and
index 8463fe9cf75a3e9aa9cd2dced9907c6c00a87693..0105a54c1a5f2cbf0955a5e3b2c50faccff8abe9 100644 (file)
@@ -112,10 +112,7 @@ effect to your index in a row.
        Pass the merge strategy-specific option through to the
        merge strategy.  See linkgit:git-merge[1] for details.
 
---rerere-autoupdate::
---no-rerere-autoupdate::
-       Allow the rerere mechanism to update the index with the
-       result of auto-conflict resolution if possible.
+include::rerere-options.txt[]
 
 --reference::
        Instead of starting the body of the log message with "This
index 8f87b23ea86a3dfecb2a736d943a6728b6c9f767..3f89d64077231014a1fd418d00a97c4710db2c3b 100644 (file)
@@ -39,10 +39,9 @@ OPTIONS
 --http-backend-info-refs::
        Used by linkgit:git-http-backend[1] to serve up
        `$GIT_URL/info/refs?service=git-upload-pack` requests. See
-       "Smart Clients" in link:technical/http-protocol.html[the HTTP
-       transfer protocols] documentation and "HTTP Transport" in
-       link:technical/protocol-v2.html[the Git Wire Protocol, Version
-       2] documentation. Also understood by
+       "Smart Clients" in linkgit:gitprotocol-http[5] and "HTTP
+       Transport" in in the linkgit:gitprotocol-v2[5]
+       documentation. Also understood by
        linkgit:git-receive-pack[1].
 
 <directory>::
index 47a6095ff40f2c3f2f1d8f5684c92dd19b616b83..0ef7f5e4ecebf5480e7878d10f73b6d20d98c7ed 100644 (file)
@@ -339,6 +339,23 @@ The following documentation pages are guides about Git concepts.
 
 include::cmds-guide.txt[]
 
+Repository, command and file interfaces
+---------------------------------------
+
+This documentation discusses repository and command interfaces which
+users are expected to interact with directly. See `--user-formats` in
+linkgit:git-help[1] for more details on the critera.
+
+include::cmds-userinterfaces.txt[]
+
+File formats, protocols and other developer interfaces
+------------------------------------------------------
+
+This documentation discusses file formats, over-the-wire protocols and
+other git developer interfaces. See `--developer-interfaces` in
+linkgit:git-help[1].
+
+include::cmds-developerinterfaces.txt[]
 
 Configuration Mechanism
 -----------------------
similarity index 79%
rename from Documentation/technical/bundle-format.txt
rename to Documentation/gitformat-bundle.txt
index b9be8644cf5d53161b7190f580be935b28d402a3..00e0a20e6571969ebeabca8da5d9815be65d682b 100644 (file)
@@ -1,11 +1,33 @@
-= Git bundle v2 format
+gitformat-bundle(5)
+===================
 
-The Git bundle format is a format that represents both refs and Git objects.
+NAME
+----
+gitformat-bundle - The bundle file format
+
+
+SYNOPSIS
+--------
+[verse]
+*.bundle
+*.bdl
+
+DESCRIPTION
+-----------
+
+The Git bundle format is a format that represents both refs and Git
+objects. A bundle is a header in a format similar to
+linkgit:git-show-ref[1] followed by a pack in *.pack format.
 
-== Format
+The format is created and read by the linkgit:git-bundle[1] command,
+and supported by e.g. linkgit:git-fetch[1] and linkgit:git-clone[1].
+
+
+FORMAT
+------
 
 We will use ABNF notation to define the Git bundle format. See
-protocol-common.txt for the details.
+linkgit:gitprotocol-common[5] for the details.
 
 A v2 bundle looks like this:
 
@@ -36,7 +58,9 @@ value        = *(%01-09 / %0b-FF)
 pack         = ... ; packfile
 ----
 
-== Semantics
+
+SEMANTICS
+---------
 
 A Git bundle consists of several parts.
 
@@ -62,13 +86,15 @@ In the bundle format, there can be a comment following a prerequisite obj-id.
 This is a comment and it has no specific meaning. The writer of the bundle MAY
 put any string here. The reader of the bundle MUST ignore the comment.
 
-=== Note on the shallow clone and a Git bundle
+Note on the shallow clone and a Git bundle
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Note that the prerequisites does not represent a shallow-clone boundary. The
 semantics of the prerequisites and the shallow-clone boundaries are different,
 and the Git bundle v2 format cannot represent a shallow clone repository.
 
-== Capabilities
+CAPABILITIES
+------------
 
 Because there is no opportunity for negotiation, unknown capabilities cause 'git
 bundle' to abort.
@@ -79,3 +105,7 @@ bundle' to abort.
 * `filter` specifies an object filter as in the `--filter` option in
   linkgit:git-rev-list[1]. The resulting pack-file must be marked as a
   `.promisor` pack-file after it is unbundled.
+
+GIT
+---
+Part of the linkgit:git[1] suite
similarity index 89%
rename from Documentation/technical/chunk-format.txt
rename to Documentation/gitformat-chunk.txt
index 593614fcedab47336926c81b3c1708c7358edd01..57202ede273ad266be910045294fd74853dd193c 100644 (file)
@@ -1,12 +1,25 @@
-Chunk-based file formats
-========================
+gitformat-chunk(5)
+==================
+
+NAME
+----
+gitformat-chunk - Chunk-based file formats
+
+SYNOPSIS
+--------
+
+Used by linkgit:gitformat-commit-graph[5] and the "MIDX" format (see
+the pack format documentation in linkgit:gitformat-pack[5]).
+
+DESCRIPTION
+-----------
 
 Some file formats in Git use a common concept of "chunks" to describe
 sections of the file. This allows structured access to a large file by
 scanning a small "table of contents" for the remaining data. This common
 format is used by the `commit-graph` and `multi-pack-index` files. See
-link:technical/pack-format.html[the `multi-pack-index` format] and
-link:technical/commit-graph-format.html[the `commit-graph` format] for
+the `multi-pack-index` format in linkgit:gitformat-pack[5] and
+the `commit-graph` format in linkgit:gitformat-commit-graph[5] for
 how they use the chunks to describe structured data.
 
 A chunk-based file format begins with some header information custom to
@@ -108,9 +121,13 @@ for future formats:
 * *commit-graph:* see `write_commit_graph_file()` and `parse_commit_graph()`
   in `commit-graph.c` for how the chunk-format API is used to write and
   parse the commit-graph file format documented in
-  link:technical/commit-graph-format.html[the commit-graph file format].
+  the commit-graph file format in linkgit:gitformat-commit-graph[5].
 
 * *multi-pack-index:* see `write_midx_internal()` and `load_multi_pack_index()`
   in `midx.c` for how the chunk-format API is used to write and
   parse the multi-pack-index file format documented in
-  link:technical/pack-format.html[the multi-pack-index file format].
+  the multi-pack-index file format section of linkgit:gitformat-pack[5].
+
+GIT
+---
+Part of the linkgit:git[1] suite
similarity index 87%
rename from Documentation/technical/commit-graph-format.txt
rename to Documentation/gitformat-commit-graph.txt
index 484b185ba98bdc00efc147851abdcbe4958ee21f..7324665716d7db7f352354f80c61406629264e95 100644 (file)
@@ -1,5 +1,18 @@
-Git commit graph format
-=======================
+gitformat-commit-graph(5)
+=========================
+
+NAME
+----
+gitformat-commit-graph - Git commit graph format
+
+SYNOPSIS
+--------
+[verse]
+$GIT_DIR/objects/info/commit-graph
+$GIT_DIR/objects/info/commit-graphs/*
+
+DESCRIPTION
+-----------
 
 The Git commit graph stores a list of commit OIDs and some associated
 metadata, including:
@@ -30,7 +43,7 @@ and hash type.
 
 All multi-byte numbers are in network byte order.
 
-HEADER:
+=== HEADER:
 
   4-byte signature:
       The signature is: {'C', 'G', 'P', 'H'}
@@ -52,7 +65,7 @@ HEADER:
       We infer the length (H*B) of the Base Graphs chunk
       from this value.
 
-CHUNK LOOKUP:
+=== CHUNK LOOKUP:
 
   (C + 1) * 12 bytes listing the table of contents for the chunks:
       First 4 bytes describe the chunk id. Value 0 is a terminating label.
@@ -62,23 +75,23 @@ CHUNK LOOKUP:
       ID appears at most once.
 
   The CHUNK LOOKUP matches the table of contents from
-  link:technical/chunk-format.html[the chunk-based file format].
+  the chunk-based file format, see linkgit:gitformat-chunk[5]
 
   The remaining data in the body is described one chunk at a time, and
   these chunks may be given in any order. Chunks are required unless
   otherwise specified.
 
-CHUNK DATA:
+=== CHUNK DATA:
 
-  OID Fanout (ID: {'O', 'I', 'D', 'F'}) (256 * 4 bytes)
+==== OID Fanout (ID: {'O', 'I', 'D', 'F'}) (256 * 4 bytes)
       The ith entry, F[i], stores the number of OIDs with first
       byte at most i. Thus F[255] stores the total
       number of commits (N).
 
-  OID Lookup (ID: {'O', 'I', 'D', 'L'}) (N * H bytes)
+====  OID Lookup (ID: {'O', 'I', 'D', 'L'}) (N * H bytes)
       The OIDs for all commits in the graph, sorted in ascending order.
 
-  Commit Data (ID: {'C', 'D', 'A', 'T' }) (N * (H + 16) bytes)
+====  Commit Data (ID: {'C', 'D', 'A', 'T' }) (N * (H + 16) bytes)
     * The first H bytes are for the OID of the root tree.
     * The next 8 bytes are for the positions of the first two parents
       of the ith commit. Stores value 0x70000000 if no parent in that
@@ -93,7 +106,7 @@ CHUNK DATA:
       2 bits of the lowest byte, storing the 33rd and 34th bit of the
       commit time.
 
-  Generation Data (ID: {'G', 'D', 'A', '2' }) (N * 4 bytes) [Optional]
+==== Generation Data (ID: {'G', 'D', 'A', '2' }) (N * 4 bytes) [Optional]
     * This list of 4-byte values store corrected commit date offsets for the
       commits, arranged in the same order as commit data chunk.
     * If the corrected commit date offset cannot be stored within 31 bits,
@@ -104,7 +117,7 @@ CHUNK DATA:
       by compatible versions of Git and in case of split commit-graph chains,
       the topmost layer also has Generation Data chunk.
 
-  Generation Data Overflow (ID: {'G', 'D', 'O', '2' }) [Optional]
+==== Generation Data Overflow (ID: {'G', 'D', 'O', '2' }) [Optional]
     * This list of 8-byte values stores the corrected commit date offsets
       for commits with corrected commit date offsets that cannot be
       stored within 31 bits.
@@ -112,7 +125,7 @@ CHUNK DATA:
       chunk is present and atleast one corrected commit date offset cannot
       be stored within 31 bits.
 
-  Extra Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional]
+==== Extra Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional]
       This list of 4-byte values store the second through nth parents for
       all octopus merges. The second parent value in the commit data stores
       an array position within this list along with the most-significant bit
@@ -120,14 +133,14 @@ CHUNK DATA:
       positions for the parents until reaching a value with the most-significant
       bit on. The other bits correspond to the position of the last parent.
 
-  Bloom Filter Index (ID: {'B', 'I', 'D', 'X'}) (N * 4 bytes) [Optional]
+==== Bloom Filter Index (ID: {'B', 'I', 'D', 'X'}) (N * 4 bytes) [Optional]
     * The ith entry, BIDX[i], stores the number of bytes in all Bloom filters
       from commit 0 to commit i (inclusive) in lexicographic order. The Bloom
       filter for the i-th commit spans from BIDX[i-1] to BIDX[i] (plus header
       length), where BIDX[-1] is 0.
     * The BIDX chunk is ignored if the BDAT chunk is not present.
 
-  Bloom Filter Data (ID: {'B', 'D', 'A', 'T'}) [Optional]
+==== Bloom Filter Data (ID: {'B', 'D', 'A', 'T'}) [Optional]
     * It starts with header consisting of three unsigned 32-bit integers:
       - Version of the hash algorithm being used. We currently only support
        value 1 which corresponds to the 32-bit version of the murmur3 hash
@@ -147,13 +160,13 @@ CHUNK DATA:
       of length one, with either all bits set to zero or one respectively.
     * The BDAT chunk is present if and only if BIDX is present.
 
-  Base Graphs List (ID: {'B', 'A', 'S', 'E'}) [Optional]
+==== Base Graphs List (ID: {'B', 'A', 'S', 'E'}) [Optional]
       This list of H-byte hashes describe a set of B commit-graph files that
       form a commit-graph chain. The graph position for the ith commit in this
       file's OID Lookup chunk is equal to i plus the number of commits in all
       base graphs.  If B is non-zero, this chunk must exist.
 
-TRAILER:
+=== TRAILER:
 
        H-byte HASH-checksum of all of the above.
 
@@ -164,3 +177,7 @@ the number '2' in their chunk IDs because a previous version of Git wrote
 possibly erroneous data in these chunks with the IDs "GDAT" and "GDOV". By
 changing the IDs, newer versions of Git will silently ignore those older
 chunks and write the new information without trusting the incorrect data.
+
+GIT
+---
+Part of the linkgit:git[1] suite
similarity index 98%
rename from Documentation/technical/index-format.txt
rename to Documentation/gitformat-index.txt
index f691c20ab0a14dc8ecf18405d54c5572905d1606..015cb21bdc089a30e5403877ca1c61bea2b04e24 100644 (file)
@@ -1,5 +1,19 @@
+gitformat-index(5)
+==================
+
+NAME
+----
+gitformat-index - Git index format
+
+SYNOPSIS
+--------
+[verse]
+$GIT_DIR/index
+
+DESCRIPTION
+-----------
+
 Git index format
-================
 
 == The Git index file has the following format
 
@@ -125,7 +139,7 @@ Git index format
     entry is encoded as if the path name for the previous entry is an
     empty string).  At the beginning of an entry, an integer N in the
     variable width encoding (the same encoding as the offset is encoded
-    for OFS_DELTA pack entries; see pack-format.txt) is stored, followed
+    for OFS_DELTA pack entries; see linkgit:gitformat-pack[5]) is stored, followed
     by a NUL-terminated string S.  Removing N bytes from the end of the
     path name for the previous entry, and replacing it with the string S
     yields the path name for this entry.
@@ -402,3 +416,7 @@ The remaining data of each directory block is grouped by type:
   with signature { 's', 'd', 'i', 'r' }. Like the split-index extension,
   tools should avoid interacting with a sparse index unless they understand
   this extension.
+
+GIT
+---
+Part of the linkgit:git[1] suite
similarity index 72%
rename from Documentation/technical/pack-format.txt
rename to Documentation/gitformat-pack.txt
index b520aa9c45bf6cf9b12d6ef798658c298b1888a2..e06af02f211e7a7a8488d1470d7f76dd58e5989c 100644 (file)
@@ -1,5 +1,30 @@
-Git pack format
-===============
+gitformat-pack(5)
+=================
+
+NAME
+----
+gitformat-pack - Git pack format
+
+
+SYNOPSIS
+--------
+[verse]
+$GIT_DIR/objects/pack/pack-*.{pack,idx}
+$GIT_DIR/objects/pack/pack-*.rev
+$GIT_DIR/objects/pack/pack-*.mtimes
+$GIT_DIR/objects/pack/multi-pack-index
+
+DESCRIPTION
+-----------
+
+The Git pack format is now Git stores most of its primary repository
+data. Over the lietime af a repository loose objects (if any) and
+smaller packs are consolidated into larger pack(s). See
+linkgit:git-gc[1] and linkgit:git-pack-objects[1].
+
+The pack format is also used over-the-wire, see
+e.g. linkgit:gitprotocol-v2[5], as well as being a part of
+other container formats in the case of linkgit:gitformat-bundle[5].
 
 == Checksums and object IDs
 
@@ -356,7 +381,7 @@ CHUNK LOOKUP:
            using the next chunk position if necessary.)
 
        The CHUNK LOOKUP matches the table of contents from
-       link:technical/chunk-format.html[the chunk-based file format].
+       the chunk-based file format, see linkgit:gitformat-chunk[5].
 
        The remaining data in the body is described one chunk at a time, and
        these chunks may be given in any order. Chunks are required unless
@@ -482,3 +507,132 @@ packs arranged in MIDX order (with the preferred pack coming first).
 
 The MIDX's reverse index is stored in the optional 'RIDX' chunk within
 the MIDX itself.
+
+== cruft packs
+
+The cruft packs feature offer an alternative to Git's traditional mechanism of
+removing unreachable objects. This document provides an overview of Git's
+pruning mechanism, and how a cruft pack can be used instead to accomplish the
+same.
+
+=== Background
+
+To remove unreachable objects from your repository, Git offers `git repack -Ad`
+(see linkgit:git-repack[1]). Quoting from the documentation:
+
+----
+[...] unreachable objects in a previous pack become loose, unpacked objects,
+instead of being left in the old pack. [...] loose unreachable objects will be
+pruned according to normal expiry rules with the next 'git gc' invocation.
+----
+
+Unreachable objects aren't removed immediately, since doing so could race with
+an incoming push which may reference an object which is about to be deleted.
+Instead, those unreachable objects are stored as loose objects and stay that way
+until they are older than the expiration window, at which point they are removed
+by linkgit:git-prune[1].
+
+Git must store these unreachable objects loose in order to keep track of their
+per-object mtimes. If these unreachable objects were written into one big pack,
+then either freshening that pack (because an object contained within it was
+re-written) or creating a new pack of unreachable objects would cause the pack's
+mtime to get updated, and the objects within it would never leave the expiration
+window. Instead, objects are stored loose in order to keep track of the
+individual object mtimes and avoid a situation where all cruft objects are
+freshened at once.
+
+This can lead to undesirable situations when a repository contains many
+unreachable objects which have not yet left the grace period. Having large
+directories in the shards of `.git/objects` can lead to decreased performance in
+the repository. But given enough unreachable objects, this can lead to inode
+starvation and degrade the performance of the whole system. Since we
+can never pack those objects, these repositories often take up a large amount of
+disk space, since we can only zlib compress them, but not store them in delta
+chains.
+
+=== Cruft packs
+
+A cruft pack eliminates the need for storing unreachable objects in a loose
+state by including the per-object mtimes in a separate file alongside a single
+pack containing all loose objects.
+
+A cruft pack is written by `git repack --cruft` when generating a new pack.
+linkgit:git-pack-objects[1]'s `--cruft` option. Note that `git repack --cruft`
+is a classic all-into-one repack, meaning that everything in the resulting pack is
+reachable, and everything else is unreachable. Once written, the `--cruft`
+option instructs `git repack` to generate another pack containing only objects
+not packed in the previous step (which equates to packing all unreachable
+objects together). This progresses as follows:
+
+  1. Enumerate every object, marking any object which is (a) not contained in a
+     kept-pack, and (b) whose mtime is within the grace period as a traversal
+     tip.
+
+  2. Perform a reachability traversal based on the tips gathered in the previous
+     step, adding every object along the way to the pack.
+
+  3. Write the pack out, along with a `.mtimes` file that records the per-object
+     timestamps.
+
+This mode is invoked internally by linkgit:git-repack[1] when instructed to
+write a cruft pack. Crucially, the set of in-core kept packs is exactly the set
+of packs which will not be deleted by the repack; in other words, they contain
+all of the repository's reachable objects.
+
+When a repository already has a cruft pack, `git repack --cruft` typically only
+adds objects to it. An exception to this is when `git repack` is given the
+`--cruft-expiration` option, which allows the generated cruft pack to omit
+expired objects instead of waiting for linkgit:git-gc[1] to expire those objects
+later on.
+
+It is linkgit:git-gc[1] that is typically responsible for removing expired
+unreachable objects.
+
+=== Caution for mixed-version environments
+
+Repositories that have cruft packs in them will continue to work with any older
+version of Git. Note, however, that previous versions of Git which do not
+understand the `.mtimes` file will use the cruft pack's mtime as the mtime for
+all of the objects in it. In other words, do not expect older (pre-cruft pack)
+versions of Git to interpret or even read the contents of the `.mtimes` file.
+
+Note that having mixed versions of Git GC-ing the same repository can lead to
+unreachable objects never being completely pruned. This can happen under the
+following circumstances:
+
+  - An older version of Git running GC explodes the contents of an existing
+    cruft pack loose, using the cruft pack's mtime.
+  - A newer version running GC collects those loose objects into a cruft pack,
+    where the .mtime file reflects the loose object's actual mtimes, but the
+    cruft pack mtime is "now".
+
+Repeating this process will lead to unreachable objects not getting pruned as a
+result of repeatedly resetting the objects' mtimes to the present time.
+
+If you are GC-ing repositories in a mixed version environment, consider omitting
+the `--cruft` option when using linkgit:git-repack[1] and linkgit:git-gc[1], and
+leaving the `gc.cruftPacks` configuration unset until all writers understand
+cruft packs.
+
+=== Alternatives
+
+Notable alternatives to this design include:
+
+  - The location of the per-object mtime data, and
+  - Storing unreachable objects in multiple cruft packs.
+
+On the location of mtime data, a new auxiliary file tied to the pack was chosen
+to avoid complicating the `.idx` format. If the `.idx` format were ever to gain
+support for optional chunks of data, it may make sense to consolidate the
+`.mtimes` format into the `.idx` itself.
+
+Storing unreachable objects among multiple cruft packs (e.g., creating a new
+cruft pack during each repacking operation including only unreachable objects
+which aren't already stored in an earlier cruft pack) is significantly more
+complicated to construct, and so aren't pursued here. The obvious drawback to
+the current implementation is that the entire cruft pack must be re-written from
+scratch.
+
+GIT
+---
+Part of the linkgit:git[1] suite
similarity index 96%
rename from Documentation/technical/signature-format.txt
rename to Documentation/gitformat-signature.txt
index 166721be6f648cdac8d0fc960e121bafc02cd226..a249869fafaa6b10e52c55c7187efb8d2e212e8e 100644 (file)
@@ -1,7 +1,18 @@
-Git signature format
-====================
+gitformat-signature(5)
+======================
 
-== Overview
+NAME
+----
+gitformat-signature - Git cryptographic signature formats
+
+SYNOPSIS
+--------
+[verse]
+<[tag|commit] object header(s)>
+<over-the-wire protocol>
+
+DESCRIPTION
+-----------
 
 Git uses cryptographic signatures in various places, currently objects (tags,
 commits, mergetags) and transactions (pushes). In every case, the command which
@@ -200,3 +211,7 @@ Date:   Wed Jun 15 09:13:29 2016 +0000
     # gpg:          There is no indication that the signature belongs to the owner.
     # Primary key fingerprint: D4BE 2231 1AD3 131E 5EDA  29A4 6109 2E85 B722 7189
 ----
+
+GIT
+---
+Part of the linkgit:git[1] suite
similarity index 96%
rename from Documentation/technical/protocol-capabilities.txt
rename to Documentation/gitprotocol-capabilities.txt
index 9dfade930dafc471aa3f684b0b5c1dc6feba6e2c..c6dcc7d565d7f0687cbf0bb2226b0288db968996 100644 (file)
@@ -1,8 +1,20 @@
-Git Protocol Capabilities
-=========================
+gitprotocol-capabilities(5)
+===========================
+
+NAME
+----
+gitprotocol-capabilities - Protocol v0 and v1 capabilities
+
+SYNOPSIS
+--------
+[verse]
+<over-the-wire-protocol>
+
+DESCRIPTION
+-----------
 
 NOTE: this document describes capabilities for versions 0 and 1 of the pack
-protocol. For version 2, please refer to the link:protocol-v2.html[protocol-v2]
+protocol. For version 2, please refer to the linkgit:gitprotocol-v2[5]
 doc.
 
 Servers SHOULD support all capabilities defined in this document.
@@ -77,7 +89,7 @@ interleaved with S-R-Q.
 multi_ack_detailed
 ------------------
 This is an extension of multi_ack that permits client to better
-understand the server's in-memory state. See pack-protocol.txt,
+understand the server's in-memory state. See linkgit:gitprotocol-pack[5],
 section "Packfile Negotiation" for more information.
 
 no-done
@@ -281,7 +293,7 @@ a packfile upload and reference update.  If the pushing client requests
 this capability, after unpacking and updating references the server
 will respond with whether the packfile unpacked successfully and if
 each reference was updated successfully.  If any of those were not
-successful, it will send back an error message.  See pack-protocol.txt
+successful, it will send back an error message.  See linkgit:gitprotocol-pack[5]
 for example messages.
 
 report-status-v2
@@ -292,7 +304,7 @@ adding new "option" directives in order to support reference rewritten by
 the "proc-receive" hook.  The "proc-receive" hook may handle a command
 for a pseudo-reference which may create or update a reference with
 different name, new-oid, and old-oid.  While the capability
-'report-status' cannot report for such case.  See pack-protocol.txt
+'report-status' cannot report for such case.  See linkgit:gitprotocol-pack[5]
 for details.
 
 delete-refs
@@ -378,3 +390,7 @@ packet-line, and must not contain non-printable or whitespace characters. The
 current implementation uses trace2 session IDs (see
 link:api-trace2.html[api-trace2] for details), but this may change and users of
 the session ID should not rely on this fact.
+
+GIT
+---
+Part of the linkgit:git[1] suite
similarity index 89%
rename from Documentation/technical/protocol-common.txt
rename to Documentation/gitprotocol-common.txt
index ecedb34bba54ecf8105336d5a98ca349ff35c63c..1486651bd1002f3c121c703caae154caa822fedc 100644 (file)
@@ -1,5 +1,20 @@
-Documentation Common to Pack and Http Protocols
-===============================================
+gitprotocol-common(5)
+=====================
+
+NAME
+----
+gitprotocol-common - Things common to various protocols
+
+SYNOPSIS
+--------
+[verse]
+<over-the-wire-protocol>
+
+DESCRIPTION
+-----------
+
+This document sets defines things common to various over-the-wire
+protocols and file formats used in Git.
 
 ABNF Notation
 -------------
@@ -97,3 +112,7 @@ Examples (as C-style strings):
   "000bfoobar\n"    "foobar\n"
   "0004"            ""
 ----
+
+GIT
+---
+Part of the linkgit:git[1] suite
similarity index 97%
rename from Documentation/technical/http-protocol.txt
rename to Documentation/gitprotocol-http.txt
index cc5126cfedaac2e14e3de48d9cafeb3c58bbb03e..ccc13f0a40758ac5f8268472354998d63331bf92 100644 (file)
@@ -1,5 +1,19 @@
-HTTP transfer protocols
-=======================
+gitprotocol-http(5)
+===================
+
+NAME
+----
+gitprotocol-http - Git HTTP-based protocols
+
+
+SYNOPSIS
+--------
+[verse]
+<over-the-wire-protocol>
+
+
+DESCRIPTION
+-----------
 
 Git supports two HTTP based transfer protocols.  A "dumb" protocol
 which requires only a standard HTTP server on the server end of the
@@ -222,7 +236,7 @@ smart server reply:
    S: 0000
 
 The client may send Extra Parameters (see
-Documentation/technical/pack-protocol.txt) as a colon-separated string
+linkgit:gitprotocol-pack[5]) as a colon-separated string
 in the Git-Protocol HTTP header.
 
 Uses the `--http-backend-info-refs` option to
@@ -512,11 +526,18 @@ the id obtained through ref discovery as old_id.
 
 TODO: Document this further.
 
-
-References
+REFERENCES
 ----------
 
 http://www.ietf.org/rfc/rfc1738.txt[RFC 1738: Uniform Resource Locators (URL)]
 http://www.ietf.org/rfc/rfc2616.txt[RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1]
-link:technical/pack-protocol.html
-link:technical/protocol-capabilities.html
+
+SEE ALSO
+--------
+
+linkgit:gitprotocol-pack[5]
+linkgit:gitprotocol-capabilities[5]
+
+GIT
+---
+Part of the linkgit:git[1] suite
similarity index 98%
rename from Documentation/technical/pack-protocol.txt
rename to Documentation/gitprotocol-pack.txt
index e13a2c064d1244fc8926ee4831bc83186e0e514d..dd4108b7a3b95456e0887fc41aed4e381261bd41 100644 (file)
@@ -1,11 +1,23 @@
-Packfile transfer protocols
-===========================
+gitprotocol-pack(5)
+===================
+
+NAME
+----
+gitprotocol-pack - How packs are transferred over-the-wire
+
+SYNOPSIS
+--------
+[verse]
+<over-the-wire-protocol>
+
+DESCRIPTION
+-----------
 
 Git supports transferring data in packfiles over the ssh://, git://, http:// and
 file:// transports.  There exist two sets of protocols, one for pushing
 data from a client to a server and another for fetching data from a
 server to a client.  The three transports (ssh, git, file) use the same
-protocol to transfer data. http is documented in http-protocol.txt.
+protocol to transfer data. http is documented in linkgit:gitprotocol-http[5].
 
 The processes invoked in the canonical Git implementation are 'upload-pack'
 on the server side and 'fetch-pack' on the client side for fetching data;
@@ -18,7 +30,7 @@ pkt-line Format
 ---------------
 
 The descriptions below build on the pkt-line format described in
-protocol-common.txt. When the grammar indicate `PKT-LINE(...)`, unless
+linkgit:gitprotocol-common[5]. When the grammar indicate `PKT-LINE(...)`, unless
 otherwise noted the usual pkt-line LF rules apply: the sender SHOULD
 include a LF, but the receiver MUST NOT complain if it is not present.
 
@@ -60,7 +72,7 @@ Each Extra Parameter takes the form of `<key>=<value>` or `<key>`.
 
 Servers that receive any such Extra Parameters MUST ignore all
 unrecognized keys. Currently, the only Extra Parameter recognized is
-"version" with a value of '1' or '2'.  See protocol-v2.txt for more
+"version" with a value of '1' or '2'.  See linkgit:gitprotocol-v2[5] for more
 information on protocol version 2.
 
 Git Transport
@@ -455,7 +467,7 @@ Now that the client and server have finished negotiation about what
 the minimal amount of data that needs to be sent to the client is, the server
 will construct and send the required data in packfile format.
 
-See pack-format.txt for what the packfile itself actually looks like.
+See linkgit:gitformat-pack[5] for what the packfile itself actually looks like.
 
 If 'side-band' or 'side-band-64k' capabilities have been specified by
 the client, the server will send the packfile data multiplexed.
@@ -707,3 +719,7 @@ An example client/server communication might look like this:
    S: 0018ok refs/heads/debug\n
    S: 002ang refs/heads/master non-fast-forward\n
 ----
+
+GIT
+---
+Part of the linkgit:git[1] suite
similarity index 97%
rename from Documentation/technical/protocol-v2.txt
rename to Documentation/gitprotocol-v2.txt
index 8a877d27e23803686632e223cbc4ba7f4ac0ab79..c9c0f9160b22e4324e2414cebabe951ae175ad1c 100644 (file)
@@ -1,5 +1,17 @@
-Git Wire Protocol, Version 2
-============================
+gitprotocol-v2(5)
+=================
+
+NAME
+----
+gitprotocol-v2 - Git Wire Protocol, Version 2
+
+SYNOPSIS
+--------
+[verse]
+<over-the-wire-protocol>
+
+DESCRIPTION
+-----------
 
 This document presents a specification for a version 2 of Git's wire
 protocol.  Protocol v2 will improve upon v1 in the following ways:
@@ -26,8 +38,7 @@ Packet-Line Framing
 -------------------
 
 All communication is done using packet-line framing, just as in v1.  See
-`Documentation/technical/pack-protocol.txt` and
-`Documentation/technical/protocol-common.txt` for more information.
+linkgit:gitprotocol-pack[5] and linkgit:gitprotocol-common[5] for more information.
 
 In protocol v2 these special packets will have the following semantics:
 
@@ -42,7 +53,7 @@ Initial Client Request
 In general a client can request to speak protocol v2 by sending
 `version=2` through the respective side-channel for the transport being
 used which inevitably sets `GIT_PROTOCOL`.  More information can be
-found in `pack-protocol.txt` and `http-protocol.txt`, as well as the
+found in linkgit:gitprotocol-pack[5] and linkgit:gitprotocol-http[5], as well as the
 `GIT_PROTOCOL` definition in `git.txt`. In all cases the
 response from the server is the capability advertisement.
 
@@ -66,7 +77,7 @@ HTTP Transport
 ~~~~~~~~~~~~~~
 
 When using the http:// or https:// transport a client makes a "smart"
-info/refs request as described in `http-protocol.txt` and requests that
+info/refs request as described in linkgit:gitprotocol-http[5] and requests that
 v2 be used by supplying "version=2" in the `Git-Protocol` header.
 
    C: GET $GIT_URL/info/refs?service=git-upload-pack HTTP/1.0
@@ -566,3 +577,7 @@ and associated requested information, each separated by a single space.
        attr = "size"
 
        obj-info = obj-id SP obj-size
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 8994e2559eac0c5746ca898b0bad1946a7b83298..5efb4fe81ff120e50027b836e85c8ee205923821 100644 (file)
@@ -68,7 +68,7 @@ Note that the "object" file isn't fit for feeding straight to zlib; it
 has the git packed object header, which is variable-length. We want to
 strip that off so we can start playing with the zlib data directly. You
 can either work your way through it manually (the format is described in
-link:../technical/pack-format.html[Documentation/technical/pack-format.txt]),
+linkgit:gitformat-pack[5]),
 or you can walk through it in a debugger. I did the latter, creating a
 valid pack like:
 
index 425377dfeb7fc0856773fd5eaaac7dd3b7b1c999..02408a0062f0c8c380847b32592aae5671ca85de 100755 (executable)
@@ -32,6 +32,9 @@ my %SECTIONS;
                'SEE ALSO' => {
                        order => $order++,
                },
+               'FILE FORMAT' => {
+                       order => $order++,
+               },
                'GIT' => {
                        required => 1,
                        order => $order++,
diff --git a/Documentation/rerere-options.txt b/Documentation/rerere-options.txt
new file mode 100644 (file)
index 0000000..c3321dd
--- /dev/null
@@ -0,0 +1,9 @@
+--rerere-autoupdate::
+--no-rerere-autoupdate::
+       After the rerere mechanism reuses a recorded resolution on
+       the current conflict to update the files in the working
+       tree, allow it to also update the index with the result of
+       resolution.  `--no-rerere-autoupdate` is a good way to
+       double-check what `rerere` did and catch potential
+       mismerges, before committing the result to the index with a
+       separate `git add`.
index 195e74eec633ea913c0934d2b690b674360376d7..bd08d18576f1b91a4e62f40a728f705017245e47 100644 (file)
@@ -242,6 +242,7 @@ ifdef::git-rev-list[]
        to `/dev/null` as the output does not have to be formatted.
 
 --disk-usage::
+--disk-usage=human::
        Suppress normal output; instead, print the sum of the bytes used
        for on-disk storage by the selected commits or objects. This is
        equivalent to piping the output into `git cat-file
@@ -249,6 +250,8 @@ ifdef::git-rev-list[]
        faster (especially with `--use-bitmap-index`). See the `CAVEATS`
        section in linkgit:git-cat-file[1] for the limitations of what
        "on-disk storage" means.
+       With the optional value `human`, on-disk storage size is shown
+       in human-readable string(e.g. 12.24 Kib, 3.50 Mib).
 endif::git-rev-list[]
 
 --cherry-mark::
index d79ad323e675303ceedc6e608f045299b5ed2d8b..d44ada98e7db9ccdd7dd622221ee298af5de5000 100644 (file)
@@ -78,7 +78,7 @@ client and an optional response message from the server.  Both the
 client and server messages are unlimited in length and are terminated
 with a flush packet.
 
-The pkt-line routines (Documentation/technical/protocol-common.txt)
+The pkt-line routines (linkgit:gitprotocol-common[5])
 are used to simplify buffer management during message generation,
 transmission, and reception.  A flush packet is used to mark the end
 of the message.  This allows the sender to incrementally generate and
diff --git a/Documentation/technical/bundle-uri.txt b/Documentation/technical/bundle-uri.txt
new file mode 100644 (file)
index 0000000..c25c423
--- /dev/null
@@ -0,0 +1,573 @@
+Bundle URIs
+===========
+
+Git bundles are files that store a pack-file along with some extra metadata,
+including a set of refs and a (possibly empty) set of necessary commits. See
+linkgit:git-bundle[1] and link:bundle-format.txt[the bundle format] for more
+information.
+
+Bundle URIs are locations where Git can download one or more bundles in
+order to bootstrap the object database in advance of fetching the remaining
+objects from a remote.
+
+One goal is to speed up clones and fetches for users with poor network
+connectivity to the origin server. Another benefit is to allow heavy users,
+such as CI build farms, to use local resources for the majority of Git data
+and thereby reducing the load on the origin server.
+
+To enable the bundle URI feature, users can specify a bundle URI using
+command-line options or the origin server can advertise one or more URIs
+via a protocol v2 capability.
+
+Design Goals
+------------
+
+The bundle URI standard aims to be flexible enough to satisfy multiple
+workloads. The bundle provider and the Git client have several choices in
+how they create and consume bundle URIs.
+
+* Bundles can have whatever name the server desires. This name could refer
+  to immutable data by using a hash of the bundle contents. However, this
+  means that a new URI will be needed after every update of the content.
+  This might be acceptable if the server is advertising the URI (and the
+  server is aware of new bundles being generated) but would not be
+  ergonomic for users using the command line option.
+
+* The bundles could be organized specifically for bootstrapping full
+  clones, but could also be organized with the intention of bootstrapping
+  incremental fetches. The bundle provider must decide on one of several
+  organization schemes to minimize client downloads during incremental
+  fetches, but the Git client can also choose whether to use bundles for
+  either of these operations.
+
+* The bundle provider can choose to support full clones, partial clones,
+  or both. The client can detect which bundles are appropriate for the
+  repository's partial clone filter, if any.
+
+* The bundle provider can use a single bundle (for clones only), or a
+  list of bundles. When using a list of bundles, the provider can specify
+  whether or not the client needs _all_ of the bundle URIs for a full
+  clone, or if _any_ one of the bundle URIs is sufficient. This allows the
+  bundle provider to use different URIs for different geographies.
+
+* The bundle provider can organize the bundles using heuristics, such as
+  creation tokens, to help the client prevent downloading bundles it does
+  not need. When the bundle provider does not provide these heuristics,
+  the client can use optimizations to minimize how much of the data is
+  downloaded.
+
+* The bundle provider does not need to be associated with the Git server.
+  The client can choose to use the bundle provider without it being
+  advertised by the Git server.
+
+* The client can choose to discover bundle providers that are advertised
+  by the Git server. This could happen during `git clone`, during
+  `git fetch`, both, or neither. The user can choose which combination
+  works best for them.
+
+* The client can choose to configure a bundle provider manually at any
+  time. The client can also choose to specify a bundle provider manually
+  as a command-line option to `git clone`.
+
+Each repository is different and every Git server has different needs.
+Hopefully the bundle URI feature is flexible enough to satisfy all needs.
+If not, then the feature can be extended through its versioning mechanism.
+
+Server requirements
+-------------------
+
+To provide a server-side implementation of bundle servers, no other parts
+of the Git protocol are required. This allows server maintainers to use
+static content solutions such as CDNs in order to serve the bundle files.
+
+At the current scope of the bundle URI feature, all URIs are expected to
+be HTTP(S) URLs where content is downloaded to a local file using a `GET`
+request to that URL. The server could include authentication requirements
+to those requests with the aim of triggering the configured credential
+helper for secure access. (Future extensions could use "file://" URIs or
+SSH URIs.)
+
+Assuming a `200 OK` response from the server, the content at the URL is
+inspected. First, Git attempts to parse the file as a bundle file of
+version 2 or higher. If the file is not a bundle, then the file is parsed
+as a plain-text file using Git's config parser. The key-value pairs in
+that config file are expected to describe a list of bundle URIs. If
+neither of these parse attempts succeed, then Git will report an error to
+the user that the bundle URI provided erroneous data.
+
+Any other data provided by the server is considered erroneous.
+
+Bundle Lists
+------------
+
+The Git server can advertise bundle URIs using a set of `key=value` pairs.
+A bundle URI can also serve a plain-text file in the Git config format
+containing these same `key=value` pairs. In both cases, we consider this
+to be a _bundle list_. The pairs specify information about the bundles
+that the client can use to make decisions for which bundles to download
+and which to ignore.
+
+A few keys focus on properties of the list itself.
+
+bundle.version::
+       (Required) This value provides a version number for the bundle
+       list. If a future Git change enables a feature that needs the Git
+       client to react to a new key in the bundle list file, then this version
+       will increment. The only current version number is 1, and if any other
+       value is specified then Git will fail to use this file.
+
+bundle.mode::
+       (Required) This value has one of two values: `all` and `any`. When `all`
+       is specified, then the client should expect to need all of the listed
+       bundle URIs that match their repository's requirements. When `any` is
+       specified, then the client should expect that any one of the bundle URIs
+       that match their repository's requirements will suffice. Typically, the
+       `any` option is used to list a number of different bundle servers
+       located in different geographies.
+
+bundle.heuristic::
+       If this string-valued key exists, then the bundle list is designed to
+       work well with incremental `git fetch` commands. The heuristic signals
+       that there are additional keys available for each bundle that help
+       determine which subset of bundles the client should download. The only
+       heuristic currently planned is `creationToken`.
+
+The remaining keys include an `<id>` segment which is a server-designated
+name for each available bundle. The `<id>` must contain only alphanumeric
+and `-` characters.
+
+bundle.<id>.uri::
+       (Required) This string value is the URI for downloading bundle `<id>`.
+       If the URI begins with a protocol (`http://` or `https://`) then the URI
+       is absolute. Otherwise, the URI is interpreted as relative to the URI
+       used for the bundle list. If the URI begins with `/`, then that relative
+       path is relative to the domain name used for the bundle list. (This use
+       of relative paths is intended to make it easier to distribute a set of
+       bundles across a large number of servers or CDNs with different domain
+       names.)
+
+bundle.<id>.filter::
+       This string value represents an object filter that should also appear in
+       the header of this bundle. The server uses this value to differentiate
+       different kinds of bundles from which the client can choose those that
+       match their object filters.
+
+bundle.<id>.creationToken::
+       This value is a nonnegative 64-bit integer used for sorting the bundles
+       the list. This is used to download a subset of bundles during a fetch
+       when `bundle.heuristic=creationToken`.
+
+bundle.<id>.location::
+       This string value advertises a real-world location from where the bundle
+       URI is served. This can be used to present the user with an option for
+       which bundle URI to use or simply as an informative indicator of which
+       bundle URI was selected by Git. This is only valuable when
+       `bundle.mode` is `any`.
+
+Here is an example bundle list using the Git config format:
+
+       [bundle]
+               version = 1
+               mode = all
+               heuristic = creationToken
+
+       [bundle "2022-02-09-1644442601-daily"]
+               uri = https://bundles.example.com/git/git/2022-02-09-1644442601-daily.bundle
+               creationToken = 1644442601
+
+       [bundle "2022-02-02-1643842562"]
+               uri = https://bundles.example.com/git/git/2022-02-02-1643842562.bundle
+               creationToken = 1643842562
+
+       [bundle "2022-02-09-1644442631-daily-blobless"]
+               uri = 2022-02-09-1644442631-daily-blobless.bundle
+               creationToken = 1644442631
+               filter = blob:none
+
+       [bundle "2022-02-02-1643842568-blobless"]
+               uri = /git/git/2022-02-02-1643842568-blobless.bundle
+               creationToken = 1643842568
+               filter = blob:none
+
+This example uses `bundle.mode=all` as well as the
+`bundle.<id>.creationToken` heuristic. It also uses the `bundle.<id>.filter`
+options to present two parallel sets of bundles: one for full clones and
+another for blobless partial clones.
+
+Suppose that this bundle list was found at the URI
+`https://bundles.example.com/git/git/` and so the two blobless bundles have
+the following fully-expanded URIs:
+
+* `https://bundles.example.com/git/git/2022-02-09-1644442631-daily-blobless.bundle`
+* `https://bundles.example.com/git/git/2022-02-02-1643842568-blobless.bundle`
+
+Advertising Bundle URIs
+-----------------------
+
+If a user knows a bundle URI for the repository they are cloning, then
+they can specify that URI manually through a command-line option. However,
+a Git host may want to advertise bundle URIs during the clone operation,
+helping users unaware of the feature.
+
+The only thing required for this feature is that the server can advertise
+one or more bundle URIs. This advertisement takes the form of a new
+protocol v2 capability specifically for discovering bundle URIs.
+
+The client could choose an arbitrary bundle URI as an option _or_ select
+the URI with best performance by some exploratory checks. It is up to the
+bundle provider to decide if having multiple URIs is preferable to a
+single URI that is geodistributed through server-side infrastructure.
+
+Cloning with Bundle URIs
+------------------------
+
+The primary need for bundle URIs is to speed up clones. The Git client
+will interact with bundle URIs according to the following flow:
+
+1. The user specifies a bundle URI with the `--bundle-uri` command-line
+   option _or_ the client discovers a bundle list advertised by the
+   Git server.
+
+2. If the downloaded data from a bundle URI is a bundle, then the client
+   inspects the bundle headers to check that the prerequisite commit OIDs
+   are present in the client repository. If some are missing, then the
+   client delays unbundling until other bundles have been unbundled,
+   making those OIDs present. When all required OIDs are present, the
+   client unbundles that data using a refspec. The default refspec is
+   `+refs/heads/*:refs/bundles/*`, but this can be configured. These refs
+   are stored so that later `git fetch` negotiations can communicate the
+   bundled refs as `have`s, reducing the size of the fetch over the Git
+   protocol. To allow pruning refs from this ref namespace, Git may
+   introduce a numbered namespace (such as `refs/bundles/<i>/*`) such that
+   stale bundle refs can be deleted.
+
+3. If the file is instead a bundle list, then the client inspects the
+   `bundle.mode` to see if the list is of the `all` or `any` form.
+
+   a. If `bundle.mode=all`, then the client considers all bundle
+      URIs. The list is reduced based on the `bundle.<id>.filter` options
+      matching the client repository's partial clone filter. Then, all
+      bundle URIs are requested. If the `bundle.<id>.creationToken`
+      heuristic is provided, then the bundles are downloaded in decreasing
+      order by the creation token, stopping when a bundle has all required
+      OIDs. The bundles can then be unbundled in increasing creation token
+      order. The client stores the latest creation token as a heuristic
+      for avoiding future downloads if the bundle list does not advertise
+      bundles with larger creation tokens.
+
+   b. If `bundle.mode=any`, then the client can choose any one of the
+      bundle URIs to inspect. The client can use a variety of ways to
+      choose among these URIs. The client can also fallback to another URI
+      if the initial choice fails to return a result.
+
+Note that during a clone we expect that all bundles will be required, and
+heuristics such as `bundle.<uri>.creationToken` can be used to download
+bundles in chronological order or in parallel.
+
+If a given bundle URI is a bundle list with a `bundle.heuristic`
+value, then the client can choose to store that URI as its chosen bundle
+URI. The client can then navigate directly to that URI during later `git
+fetch` calls.
+
+When downloading bundle URIs, the client can choose to inspect the initial
+content before committing to downloading the entire content. This may
+provide enough information to determine if the URI is a bundle list or
+a bundle. In the case of a bundle, the client may inspect the bundle
+header to determine that all advertised tips are already in the client
+repository and cancel the remaining download.
+
+Fetching with Bundle URIs
+-------------------------
+
+When the client fetches new data, it can decide to fetch from bundle
+servers before fetching from the origin remote. This could be done via a
+command-line option, but it is more likely useful to use a config value
+such as the one specified during the clone.
+
+The fetch operation follows the same procedure to download bundles from a
+bundle list (although we do _not_ want to use parallel downloads here). We
+expect that the process will end when all prerequisite commit OIDs in a
+thin bundle are already in the object database.
+
+When using the `creationToken` heuristic, the client can avoid downloading
+any bundles if their creation tokenss are not larger than the stored
+creation token. After fetching new bundles, Git updates this local
+creation token.
+
+If the bundle provider does not provide a heuristic, then the client
+should attempt to inspect the bundle headers before downloading the full
+bundle data in case the bundle tips already exist in the client
+repository.
+
+Error Conditions
+----------------
+
+If the Git client discovers something unexpected while downloading
+information according to a bundle URI or the bundle list found at that
+location, then Git can ignore that data and continue as if it was not
+given a bundle URI. The remote Git server is the ultimate source of truth,
+not the bundle URI.
+
+Here are a few example error conditions:
+
+* The client fails to connect with a server at the given URI or a connection
+  is lost without any chance to recover.
+
+* The client receives a 400-level response (such as `404 Not Found` or
+  `401 Not Authorized`). The client should use the credential helper to
+  find and provide a credential for the URI, but match the semantics of
+  Git's other HTTP protocols in terms of handling specific 400-level
+  errors.
+
+* The server reports any other failure reponse.
+
+* The client receives data that is not parsable as a bundle or bundle list.
+
+* A bundle includes a filter that does not match expectations.
+
+* The client cannot unbundle the bundles because the prerequisite commit OIDs
+  are not in the object database and there are no more bundles to download.
+
+There are also situations that could be seen as wasteful, but are not
+error conditions:
+
+* The downloaded bundles contain more information than is requested by
+  the clone or fetch request. A primary example is if the user requests
+  a clone with `--single-branch` but downloads bundles that store every
+  reachable commit from all `refs/heads/*` references. This might be
+  initially wasteful, but perhaps these objects will become reachable by
+  a later ref update that the client cares about.
+
+* A bundle download during a `git fetch` contains objects already in the
+  object database. This is probably unavoidable if we are using bundles
+  for fetches, since the client will almost always be slightly ahead of
+  the bundle servers after performing its "catch-up" fetch to the remote
+  server. This extra work is most wasteful when the client is fetching
+  much more frequently than the server is computing bundles, such as if
+  the client is using hourly prefetches with background maintenance, but
+  the server is computing bundles weekly. For this reason, the client
+  should not use bundle URIs for fetch unless the server has explicitly
+  recommended it through a `bundle.heuristic` value.
+
+Example Bundle Provider organization
+------------------------------------
+
+The bundle URI feature is intentionally designed to be flexible to
+different ways a bundle provider wants to organize the object data.
+However, it can be helpful to have a complete organization model described
+here so providers can start from that base.
+
+This example organization is a simplified model of what is used by the
+GVFS Cache Servers (see section near the end of this document) which have
+been beneficial in speeding up clones and fetches for very large
+repositories, although using extra software outside of Git.
+
+The bundle provider deploys servers across multiple geographies. Each
+server manages its own bundle set. The server can track a number of Git
+repositories, but provides a bundle list for each based on a pattern. For
+example, when mirroring a repository at `https://<domain>/<org>/<repo>`
+the bundle server could have its bundle list available at
+`https://<server-url>/<domain>/<org>/<repo>`. The origin Git server can
+list all of these servers under the "any" mode:
+
+       [bundle]
+               version = 1
+               mode = any
+
+       [bundle "eastus"]
+               uri = https://eastus.example.com/<domain>/<org>/<repo>
+
+       [bundle "europe"]
+               uri = https://europe.example.com/<domain>/<org>/<repo>
+
+       [bundle "apac"]
+               uri = https://apac.example.com/<domain>/<org>/<repo>
+
+This "list of lists" is static and only changes if a bundle server is
+added or removed.
+
+Each bundle server manages its own set of bundles. The initial bundle list
+contains only a single bundle, containing all of the objects received from
+cloning the repository from the origin server. The list uses the
+`creationToken` heuristic and a `creationToken` is made for the bundle
+based on the server's timestamp.
+
+The bundle server runs regularly-scheduled updates for the bundle list,
+such as once a day. During this task, the server fetches the latest
+contents from the origin server and generates a bundle containing the
+objects reachable from the latest origin refs, but not contained in a
+previously-computed bundle. This bundle is added to the list, with care
+that the `creationToken` is strictly greater than the previous maximum
+`creationToken`.
+
+When the bundle list grows too large, say more than 30 bundles, then the
+oldest "_N_ minus 30" bundles are combined into a single bundle. This
+bundle's `creationToken` is equal to the maximum `creationToken` among the
+merged bundles.
+
+An example bundle list is provided here, although it only has two daily
+bundles and not a full list of 30:
+
+       [bundle]
+               version = 1
+               mode = all
+               heuristic = creationToken
+
+       [bundle "2022-02-13-1644770820-daily"]
+               uri = https://eastus.example.com/<domain>/<org>/<repo>/2022-02-09-1644770820-daily.bundle
+               creationToken = 1644770820
+
+       [bundle "2022-02-09-1644442601-daily"]
+               uri = https://eastus.example.com/<domain>/<org>/<repo>/2022-02-09-1644442601-daily.bundle
+               creationToken = 1644442601
+
+       [bundle "2022-02-02-1643842562"]
+               uri = https://eastus.example.com/<domain>/<org>/<repo>/2022-02-02-1643842562.bundle
+               creationToken = 1643842562
+
+To avoid storing and serving object data in perpetuity despite becoming
+unreachable in the origin server, this bundle merge can be more careful.
+Instead of taking an absolute union of the old bundles, instead the bundle
+can be created by looking at the newer bundles and ensuring that their
+necessary commits are all available in this merged bundle (or in another
+one of the newer bundles). This allows "expiring" object data that is not
+being used by new commits in this window of time. That data could be
+reintroduced by a later push.
+
+The intention of this data organization has two main goals. First, initial
+clones of the repository become faster by downloading precomputed object
+data from a closer source. Second, `git fetch` commands can be faster,
+especially if the client has not fetched for a few days. However, if a
+client does not fetch for 30 days, then the bundle list organization would
+cause redownloading a large amount of object data.
+
+One way to make this organization more useful to users who fetch frequently
+is to have more frequent bundle creation. For example, bundles could be
+created every hour, and then once a day those "hourly" bundles could be
+merged into a "daily" bundle. The daily bundles are merged into the
+oldest bundle after 30 days.
+
+It is recommened that this bundle strategy is repeated with the `blob:none`
+filter if clients of this repository are expecting to use blobless partial
+clones. This list of blobless bundles stays in the same list as the full
+bundles, but uses the `bundle.<id>.filter` key to separate the two groups.
+For very large repositories, the bundle provider may want to _only_ provide
+blobless bundles.
+
+Implementation Plan
+-------------------
+
+This design document is being submitted on its own as an aspirational
+document, with the goal of implementing all of the mentioned client
+features over the course of several patch series. Here is a potential
+outline for submitting these features:
+
+1. Integrate bundle URIs into `git clone` with a `--bundle-uri` option.
+   This will include a new `git fetch --bundle-uri` mode for use as the
+   implementation underneath `git clone`. The initial version here will
+   expect a single bundle at the given URI.
+
+2. Implement the ability to parse a bundle list from a bundle URI and
+   update the `git fetch --bundle-uri` logic to properly distinguish
+   between `bundle.mode` options. Specifically design the feature so
+   that the config format parsing feeds a list of key-value pairs into the
+   bundle list logic.
+
+3. Create the `bundle-uri` protocol v2 command so Git servers can advertise
+   bundle URIs using the key-value pairs. Plug into the existing key-value
+   input to the bundle list logic. Allow `git clone` to discover these
+   bundle URIs and bootstrap the client repository from the bundle data.
+   (This choice is an opt-in via a config option and a command-line
+   option.)
+
+4. Allow the client to understand the `bundle.flag=forFetch` configuration
+   and the `bundle.<id>.creationToken` heuristic. When `git clone`
+   discovers a bundle URI with `bundle.flag=forFetch`, it configures the
+   client repository to check that bundle URI during later `git fetch <remote>`
+   commands.
+
+5. Allow clients to discover bundle URIs during `git fetch` and configure
+   a bundle URI for later fetches if `bundle.flag=forFetch`.
+
+6. Implement the "inspect headers" heuristic to reduce data downloads when
+   the `bundle.<id>.creationToken` heuristic is not available.
+
+As these features are reviewed, this plan might be updated. We also expect
+that new designs will be discovered and implemented as this feature
+matures and becomes used in real-world scenarios.
+
+Related Work: Packfile URIs
+---------------------------
+
+The Git protocol already has a capability where the Git server can list
+a set of URLs along with the packfile response when serving a client
+request. The client is then expected to download the packfiles at those
+locations in order to have a complete understanding of the response.
+
+This mechanism is used by the Gerrit server (implemented with JGit) and
+has been effective at reducing CPU load and improving user performance for
+clones.
+
+A major downside to this mechanism is that the origin server needs to know
+_exactly_ what is in those packfiles, and the packfiles need to be available
+to the user for some time after the server has responded. This coupling
+between the origin and the packfile data is difficult to manage.
+
+Further, this implementation is extremely hard to make work with fetches.
+
+Related Work: GVFS Cache Servers
+--------------------------------
+
+The GVFS Protocol [2] is a set of HTTP endpoints designed independently of
+the Git project before Git's partial clone was created. One feature of this
+protocol is the idea of a "cache server" which can be colocated with build
+machines or developer offices to transfer Git data without overloading the
+central server.
+
+The endpoint that VFS for Git is famous for is the `GET /gvfs/objects/{oid}`
+endpoint, which allows downloading an object on-demand. This is a critical
+piece of the filesystem virtualization of that product.
+
+However, a more subtle need is the `GET /gvfs/prefetch?lastPackTimestamp=<t>`
+endpoint. Given an optional timestamp, the cache server responds with a list
+of precomputed packfiles containing the commits and trees that were introduced
+in those time intervals.
+
+The cache server computes these "prefetch" packfiles using the following
+strategy:
+
+1. Every hour, an "hourly" pack is generated with a given timestamp.
+2. Nightly, the previous 24 hourly packs are rolled up into a "daily" pack.
+3. Nightly, all prefetch packs more than 30 days old are rolled up into
+   one pack.
+
+When a user runs `gvfs clone` or `scalar clone` against a repo with cache
+servers, the client requests all prefetch packfiles, which is at most
+`24 + 30 + 1` packfiles downloading only commits and trees. The client
+then follows with a request to the origin server for the references, and
+attempts to checkout that tip reference. (There is an extra endpoint that
+helps get all reachable trees from a given commit, in case that commit
+was not already in a prefetch packfile.)
+
+During a `git fetch`, a hook requests the prefetch endpoint using the
+most-recent timestamp from a previously-downloaded prefetch packfile.
+Only the list of packfiles with later timestamps are downloaded. Most
+users fetch hourly, so they get at most one hourly prefetch pack. Users
+whose machines have been off or otherwise have not fetched in over 30 days
+might redownload all prefetch packfiles. This is rare.
+
+It is important to note that the clients always contact the origin server
+for the refs advertisement, so the refs are frequently "ahead" of the
+prefetched pack data. The missing objects are downloaded on-demand using
+the `GET gvfs/objects/{oid}` requests, when needed by a command such as
+`git checkout` or `git log`. Some Git optimizations disable checks that
+would cause these on-demand downloads to be too aggressive.
+
+See Also
+--------
+
+[1] https://lore.kernel.org/git/RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com/
+    An earlier RFC for a bundle URI feature.
+
+[2] https://github.com/microsoft/VFSForGit/blob/master/Protocol.md
+    The GVFS Protocol
diff --git a/Documentation/technical/cruft-packs.txt b/Documentation/technical/cruft-packs.txt
deleted file mode 100644 (file)
index d81f3a8..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-= Cruft packs
-
-The cruft packs feature offer an alternative to Git's traditional mechanism of
-removing unreachable objects. This document provides an overview of Git's
-pruning mechanism, and how a cruft pack can be used instead to accomplish the
-same.
-
-== Background
-
-To remove unreachable objects from your repository, Git offers `git repack -Ad`
-(see linkgit:git-repack[1]). Quoting from the documentation:
-
-[quote]
-[...] unreachable objects in a previous pack become loose, unpacked objects,
-instead of being left in the old pack. [...] loose unreachable objects will be
-pruned according to normal expiry rules with the next 'git gc' invocation.
-
-Unreachable objects aren't removed immediately, since doing so could race with
-an incoming push which may reference an object which is about to be deleted.
-Instead, those unreachable objects are stored as loose objects and stay that way
-until they are older than the expiration window, at which point they are removed
-by linkgit:git-prune[1].
-
-Git must store these unreachable objects loose in order to keep track of their
-per-object mtimes. If these unreachable objects were written into one big pack,
-then either freshening that pack (because an object contained within it was
-re-written) or creating a new pack of unreachable objects would cause the pack's
-mtime to get updated, and the objects within it would never leave the expiration
-window. Instead, objects are stored loose in order to keep track of the
-individual object mtimes and avoid a situation where all cruft objects are
-freshened at once.
-
-This can lead to undesirable situations when a repository contains many
-unreachable objects which have not yet left the grace period. Having large
-directories in the shards of `.git/objects` can lead to decreased performance in
-the repository. But given enough unreachable objects, this can lead to inode
-starvation and degrade the performance of the whole system. Since we
-can never pack those objects, these repositories often take up a large amount of
-disk space, since we can only zlib compress them, but not store them in delta
-chains.
-
-== Cruft packs
-
-A cruft pack eliminates the need for storing unreachable objects in a loose
-state by including the per-object mtimes in a separate file alongside a single
-pack containing all loose objects.
-
-A cruft pack is written by `git repack --cruft` when generating a new pack.
-linkgit:git-pack-objects[1]'s `--cruft` option. Note that `git repack --cruft`
-is a classic all-into-one repack, meaning that everything in the resulting pack is
-reachable, and everything else is unreachable. Once written, the `--cruft`
-option instructs `git repack` to generate another pack containing only objects
-not packed in the previous step (which equates to packing all unreachable
-objects together). This progresses as follows:
-
-  1. Enumerate every object, marking any object which is (a) not contained in a
-     kept-pack, and (b) whose mtime is within the grace period as a traversal
-     tip.
-
-  2. Perform a reachability traversal based on the tips gathered in the previous
-     step, adding every object along the way to the pack.
-
-  3. Write the pack out, along with a `.mtimes` file that records the per-object
-     timestamps.
-
-This mode is invoked internally by linkgit:git-repack[1] when instructed to
-write a cruft pack. Crucially, the set of in-core kept packs is exactly the set
-of packs which will not be deleted by the repack; in other words, they contain
-all of the repository's reachable objects.
-
-When a repository already has a cruft pack, `git repack --cruft` typically only
-adds objects to it. An exception to this is when `git repack` is given the
-`--cruft-expiration` option, which allows the generated cruft pack to omit
-expired objects instead of waiting for linkgit:git-gc[1] to expire those objects
-later on.
-
-It is linkgit:git-gc[1] that is typically responsible for removing expired
-unreachable objects.
-
-== Caution for mixed-version environments
-
-Repositories that have cruft packs in them will continue to work with any older
-version of Git. Note, however, that previous versions of Git which do not
-understand the `.mtimes` file will use the cruft pack's mtime as the mtime for
-all of the objects in it. In other words, do not expect older (pre-cruft pack)
-versions of Git to interpret or even read the contents of the `.mtimes` file.
-
-Note that having mixed versions of Git GC-ing the same repository can lead to
-unreachable objects never being completely pruned. This can happen under the
-following circumstances:
-
-  - An older version of Git running GC explodes the contents of an existing
-    cruft pack loose, using the cruft pack's mtime.
-  - A newer version running GC collects those loose objects into a cruft pack,
-    where the .mtime file reflects the loose object's actual mtimes, but the
-    cruft pack mtime is "now".
-
-Repeating this process will lead to unreachable objects not getting pruned as a
-result of repeatedly resetting the objects' mtimes to the present time.
-
-If you are GC-ing repositories in a mixed version environment, consider omitting
-the `--cruft` option when using linkgit:git-repack[1] and linkgit:git-gc[1], and
-leaving the `gc.cruftPacks` configuration unset until all writers understand
-cruft packs.
-
-== Alternatives
-
-Notable alternatives to this design include:
-
-  - The location of the per-object mtime data, and
-  - Storing unreachable objects in multiple cruft packs.
-
-On the location of mtime data, a new auxiliary file tied to the pack was chosen
-to avoid complicating the `.idx` format. If the `.idx` format were ever to gain
-support for optional chunks of data, it may make sense to consolidate the
-`.mtimes` format into the `.idx` itself.
-
-Storing unreachable objects among multiple cruft packs (e.g., creating a new
-cruft pack during each repacking operation including only unreachable objects
-which aren't already stored in an earlier cruft pack) is significantly more
-complicated to construct, and so aren't pursued here. The obvious drawback to
-the current implementation is that the entire cruft pack must be re-written from
-scratch.
index 260224b0331c709da9aa4bcfa06ae97146cfffb6..e2ac36dd210bef993e1cca06f37d788c3d4fe262 100644 (file)
@@ -205,7 +205,7 @@ SHA-1 content.
 Object storage
 ~~~~~~~~~~~~~~
 Loose objects use zlib compression and packed objects use the packed
-format described in Documentation/technical/pack-format.txt, just like
+format described in linkgit:gitformat-pack[5], just like
 today. The content that is compressed and stored uses SHA-256 content
 instead of SHA-1 content.
 
index aa0aa9af1c2eb8835e81a5a43a6332df902ceefe..6f33654b4288d4effafed8a78162da9da2bf7f9a 100644 (file)
@@ -3,7 +3,7 @@ Long-running process protocol
 
 This protocol is used when Git needs to communicate with an external
 process throughout the entire life of a single Git command. All
-communication is in pkt-line format (see technical/protocol-common.txt)
+communication is in pkt-line format (see linkgit:gitprotocol-common[5])
 over standard input and standard output.
 
 Handshake
index 1eb525fe760461608442b7a35a40a48e1fddee79..9d453d47651a03dd432c71a71828c1bae2234dfb 100644 (file)
@@ -18,7 +18,7 @@ a `packfile-uris` argument, the server MAY send a `packfile-uris` section
 directly before the `packfile` section (right after `wanted-refs` if it is
 sent) containing URIs of any of the given protocols. The URIs point to
 packfiles that use only features that the client has declared that it supports
-(e.g. ofs-delta and thin-pack). See protocol-v2.txt for the documentation of
+(e.g. ofs-delta and thin-pack). See linkgit:gitprotocol-v2[5] for the documentation of
 this section.
 
 Clients should then download and index all the given URIs (in addition to
index 99f0eb304061adeb6b9d2b487eb1c2c11d07463b..92fcee2bfffff8c42a07f0dd4b87421abd94f2bb 100644 (file)
@@ -79,7 +79,7 @@ Design Details
   upload-pack negotiation.
 +
 This uses the existing capability discovery mechanism.
-See "filter" in Documentation/technical/pack-protocol.txt.
+See "filter" in linkgit:gitprotocol-pack[5].
 
 - Clients pass a "filter-spec" to clone and fetch which is passed to the
   server to request filtering during packfile construction.
index 865074bed4eacac0ba3da5edb8f0094dc411227c..ca9decdd952f88b970999cfd5e772004c45d30d4 100644 (file)
@@ -3133,7 +3133,7 @@ those "loose" objects.
 You can save space and make Git faster by moving these loose objects in
 to a "pack file", which stores a group of objects in an efficient
 compressed format; the details of how pack files are formatted can be
-found in link:technical/pack-format.html[pack format].
+found in link:gitformat-pack[5].
 
 To put the loose objects into a pack, just run git repack:
 
index 9ceaf55582a709e8e44f97dc68c453eb51843240..cde5d5670221433a07c6ea07765a5e86efe8f9bd 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -918,6 +918,7 @@ LIB_OBJS += combine-diff.o
 LIB_OBJS += commit-graph.o
 LIB_OBJS += commit-reach.o
 LIB_OBJS += commit.o
+LIB_OBJS += compat/nonblock.o
 LIB_OBJS += compat/obstack.o
 LIB_OBJS += compat/terminal.o
 LIB_OBJS += compat/zlib-uncompress2.o
@@ -993,7 +994,6 @@ LIB_OBJS += merge-ort.o
 LIB_OBJS += merge-ort-wrappers.o
 LIB_OBJS += merge-recursive.o
 LIB_OBJS += merge.o
-LIB_OBJS += mergesort.o
 LIB_OBJS += midx.o
 LIB_OBJS += name-hash.o
 LIB_OBJS += negotiator/default.o
@@ -3534,6 +3534,7 @@ check-docs::
                sed -e '1,/^### command list/d' \
                    -e '/^#/d' \
                    -e '/guide$$/d' \
+                   -e '/interfaces$$/d' \
                    -e 's/[     ].*//' \
                    -e 's/^/listed /' command-list.txt; \
                $(MAKE) -C Documentation print-man1 | \
index b63669cc9d768fd1de2cbc06e20e942f77ffcda7..38b3891f3a6209d24b8f891ccbd274f829919c4c 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -648,11 +648,14 @@ static struct commit_list *managed_skipped(struct commit_list *list,
 }
 
 static void bisect_rev_setup(struct repository *r, struct rev_info *revs,
+                            struct strvec *rev_argv,
                             const char *prefix,
                             const char *bad_format, const char *good_format,
                             int read_paths)
 {
-       struct strvec rev_argv = STRVEC_INIT;
+       struct setup_revision_opt opt = {
+               .free_removed_argv_elements = 1,
+       };
        int i;
 
        repo_init_revisions(r, revs, prefix);
@@ -660,17 +663,16 @@ static void bisect_rev_setup(struct repository *r, struct rev_info *revs,
        revs->commit_format = CMIT_FMT_UNSPECIFIED;
 
        /* rev_argv.argv[0] will be ignored by setup_revisions */
-       strvec_push(&rev_argv, "bisect_rev_setup");
-       strvec_pushf(&rev_argv, bad_format, oid_to_hex(current_bad_oid));
+       strvec_push(rev_argv, "bisect_rev_setup");
+       strvec_pushf(rev_argv, bad_format, oid_to_hex(current_bad_oid));
        for (i = 0; i < good_revs.nr; i++)
-               strvec_pushf(&rev_argv, good_format,
+               strvec_pushf(rev_argv, good_format,
                             oid_to_hex(good_revs.oid + i));
-       strvec_push(&rev_argv, "--");
+       strvec_push(rev_argv, "--");
        if (read_paths)
-               read_bisect_paths(&rev_argv);
+               read_bisect_paths(rev_argv);
 
-       setup_revisions(rev_argv.nr, rev_argv.v, revs, NULL);
-       /* XXX leak rev_argv, as "revs" may still be pointing to it */
+       setup_revisions(rev_argv->nr, rev_argv->v, revs, &opt);
 }
 
 static void bisect_common(struct rev_info *revs)
@@ -873,10 +875,11 @@ static enum bisect_error check_merge_bases(int rev_nr, struct commit **rev, int
 static int check_ancestors(struct repository *r, int rev_nr,
                           struct commit **rev, const char *prefix)
 {
+       struct strvec rev_argv = STRVEC_INIT;
        struct rev_info revs;
        int res;
 
-       bisect_rev_setup(r, &revs, prefix, "^%s", "%s", 0);
+       bisect_rev_setup(r, &revs, &rev_argv, prefix, "^%s", "%s", 0);
 
        bisect_common(&revs);
        res = (revs.commits != NULL);
@@ -885,6 +888,7 @@ static int check_ancestors(struct repository *r, int rev_nr,
        clear_commit_marks_many(rev_nr, rev, ALL_REV_FLAGS);
 
        release_revisions(&revs);
+       strvec_clear(&rev_argv);
        return res;
 }
 
@@ -1010,6 +1014,7 @@ void read_bisect_terms(const char **read_bad, const char **read_good)
  */
 enum bisect_error bisect_next_all(struct repository *r, const char *prefix)
 {
+       struct strvec rev_argv = STRVEC_INIT;
        struct rev_info revs = REV_INFO_INIT;
        struct commit_list *tried;
        int reaches = 0, all = 0, nr, steps;
@@ -1037,7 +1042,7 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix)
        if (res)
                goto cleanup;
 
-       bisect_rev_setup(r, &revs, prefix, "%s", "^%s", 1);
+       bisect_rev_setup(r, &revs, &rev_argv, prefix, "%s", "^%s", 1);
 
        revs.first_parent_only = !!(bisect_flags & FIND_BISECTION_FIRST_PARENT_ONLY);
        revs.limited = 1;
@@ -1054,7 +1059,7 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix)
                 */
                res = error_if_skipped_commits(tried, NULL);
                if (res < 0)
-                       return res;
+                       goto cleanup;
                printf(_("%s was both %s and %s\n"),
                       oid_to_hex(current_bad_oid),
                       term_good,
@@ -1112,6 +1117,7 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix)
        res = bisect_checkout(bisect_rev, no_checkout);
 cleanup:
        release_revisions(&revs);
+       strvec_clear(&rev_argv);
        return res;
 }
 
diff --git a/blame.c b/blame.c
index da1052ac94bb47282fb6191e457dda6597c3e8fc..8bfeaa1c63aedc151b1125e98f52229842d48b19 100644 (file)
--- a/blame.c
+++ b/blame.c
@@ -1098,30 +1098,22 @@ static struct blame_entry *blame_merge(struct blame_entry *list1,
        }
 }
 
-static void *get_next_blame(const void *p)
-{
-       return ((struct blame_entry *)p)->next;
-}
-
-static void set_next_blame(void *p1, void *p2)
-{
-       ((struct blame_entry *)p1)->next = p2;
-}
+DEFINE_LIST_SORT(static, sort_blame_entries, struct blame_entry, next);
 
 /*
  * Final image line numbers are all different, so we don't need a
  * three-way comparison here.
  */
 
-static int compare_blame_final(const void *p1, const void *p2)
+static int compare_blame_final(const struct blame_entry *e1,
+                              const struct blame_entry *e2)
 {
-       return ((struct blame_entry *)p1)->lno > ((struct blame_entry *)p2)->lno
-               ? 1 : -1;
+       return e1->lno > e2->lno ? 1 : -1;
 }
 
-static int compare_blame_suspect(const void *p1, const void *p2)
+static int compare_blame_suspect(const struct blame_entry *s1,
+                                const struct blame_entry *s2)
 {
-       const struct blame_entry *s1 = p1, *s2 = p2;
        /*
         * to allow for collating suspects, we sort according to the
         * respective pointer value as the primary sorting criterion.
@@ -1138,8 +1130,7 @@ static int compare_blame_suspect(const void *p1, const void *p2)
 
 void blame_sort_final(struct blame_scoreboard *sb)
 {
-       sb->ent = llist_mergesort(sb->ent, get_next_blame, set_next_blame,
-                                 compare_blame_final);
+       sort_blame_entries(&sb->ent, compare_blame_final);
 }
 
 static int compare_commits_by_reverse_commit_date(const void *a,
@@ -1964,9 +1955,7 @@ static void pass_blame_to_parent(struct blame_scoreboard *sb,
                    parent, target, 0);
        *d.dstq = NULL;
        if (ignore_diffs)
-               newdest = llist_mergesort(newdest, get_next_blame,
-                                         set_next_blame,
-                                         compare_blame_suspect);
+               sort_blame_entries(&newdest, compare_blame_suspect);
        queue_blames(sb, parent, newdest);
 
        return;
@@ -2383,8 +2372,7 @@ static int num_scapegoats(struct rev_info *revs, struct commit *commit, int reve
  */
 static void distribute_blame(struct blame_scoreboard *sb, struct blame_entry *blamed)
 {
-       blamed = llist_mergesort(blamed, get_next_blame, set_next_blame,
-                                compare_blame_suspect);
+       sort_blame_entries(&blamed, compare_blame_suspect);
        while (blamed)
        {
                struct blame_origin *porigin = blamed->suspect;
diff --git a/bloom.c b/bloom.c
index 5e297038bb1f450dbda38fec633c5b2ca31fb358..816f063dca58bda64cd0ff452c9ae917d3c681a9 100644 (file)
--- a/bloom.c
+++ b/bloom.c
@@ -30,10 +30,9 @@ static inline unsigned char get_bitmask(uint32_t pos)
 
 static int load_bloom_filter_from_graph(struct commit_graph *g,
                                        struct bloom_filter *filter,
-                                       struct commit *c)
+                                       uint32_t graph_pos)
 {
        uint32_t lex_pos, start_index, end_index;
-       uint32_t graph_pos = commit_graph_position(c);
 
        while (graph_pos < g->num_commits_in_base)
                g = g->base_graph;
@@ -203,9 +202,10 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,
        filter = bloom_filter_slab_at(&bloom_filters, c);
 
        if (!filter->data) {
-               load_commit_graph_info(r, c);
-               if (commit_graph_position(c) != COMMIT_NOT_FROM_GRAPH)
-                       load_bloom_filter_from_graph(r->objects->commit_graph, filter, c);
+               uint32_t graph_pos;
+               if (repo_find_commit_pos_in_graph(r, c, &graph_pos))
+                       load_bloom_filter_from_graph(r->objects->commit_graph,
+                                                    filter, graph_pos);
        }
 
        if (filter->data && filter->len)
index f42782e955f35bdfa9a78c217337863364bce1d5..989eee0bb4c7d924b97b3aac6b43443f7c34ab67 100644 (file)
@@ -16,6 +16,7 @@
 #include "packfile.h"
 #include "object-store.h"
 #include "promisor-remote.h"
+#include "mailmap.h"
 
 enum batch_mode {
        BATCH_MODE_CONTENTS,
@@ -31,11 +32,28 @@ struct batch_options {
        int all_objects;
        int unordered;
        int transform_mode; /* may be 'w' or 'c' for --filters or --textconv */
+       int nul_terminated;
        const char *format;
 };
 
 static const char *force_path;
 
+static struct string_list mailmap = STRING_LIST_INIT_NODUP;
+static int use_mailmap;
+
+static char *replace_idents_using_mailmap(char *, size_t *);
+
+static char *replace_idents_using_mailmap(char *object_buf, size_t *size)
+{
+       struct strbuf sb = STRBUF_INIT;
+       const char *headers[] = { "author ", "committer ", "tagger ", NULL };
+
+       strbuf_attach(&sb, object_buf, *size, *size + 1);
+       apply_mailmap_to_header(&sb, headers, &mailmap);
+       *size = sb.len;
+       return strbuf_detach(&sb, NULL);
+}
+
 static int filter_object(const char *path, unsigned mode,
                         const struct object_id *oid,
                         char **buf, unsigned long *size)
@@ -160,6 +178,12 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
                if (!buf)
                        die("Cannot read object %s", obj_name);
 
+               if (use_mailmap) {
+                       size_t s = size;
+                       buf = replace_idents_using_mailmap(buf, &s);
+                       size = cast_size_t_to_ulong(s);
+               }
+
                /* otherwise just spit out the data */
                break;
 
@@ -193,6 +217,12 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
                }
                buf = read_object_with_reference(the_repository, &oid,
                                                 exp_type_id, &size, NULL);
+
+               if (use_mailmap) {
+                       size_t s = size;
+                       buf = replace_idents_using_mailmap(buf, &s);
+                       size = cast_size_t_to_ulong(s);
+               }
                break;
        }
        default:
@@ -360,11 +390,18 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d
                void *contents;
 
                contents = read_object_file(oid, &type, &size);
+
+               if (use_mailmap) {
+                       size_t s = size;
+                       contents = replace_idents_using_mailmap(contents, &s);
+                       size = cast_size_t_to_ulong(s);
+               }
+
                if (!contents)
                        die("object %s disappeared", oid_to_hex(oid));
                if (type != data->type)
                        die("object %s changed type!?", oid_to_hex(oid));
-               if (data->info.sizep && size != data->size)
+               if (data->info.sizep && size != data->size && !use_mailmap)
                        die("object %s changed size!?", oid_to_hex(oid));
 
                batch_write(opt, contents, size);
@@ -614,12 +651,20 @@ static void batch_objects_command(struct batch_options *opt,
        struct queued_cmd *queued_cmd = NULL;
        size_t alloc = 0, nr = 0;
 
-       while (!strbuf_getline(&input, stdin)) {
-               int i;
+       while (1) {
+               int i, ret;
                const struct parse_cmd *cmd = NULL;
                const char *p = NULL, *cmd_end;
                struct queued_cmd call = {0};
 
+               if (opt->nul_terminated)
+                       ret = strbuf_getline_nul(&input, stdin);
+               else
+                       ret = strbuf_getline(&input, stdin);
+
+               if (ret)
+                       break;
+
                if (!input.len)
                        die(_("empty command in input"));
                if (isspace(*input.buf))
@@ -763,7 +808,16 @@ static int batch_objects(struct batch_options *opt)
                goto cleanup;
        }
 
-       while (strbuf_getline(&input, stdin) != EOF) {
+       while (1) {
+               int ret;
+               if (opt->nul_terminated)
+                       ret = strbuf_getline_nul(&input, stdin);
+               else
+                       ret = strbuf_getline(&input, stdin);
+
+               if (ret == EOF)
+                       break;
+
                if (data.split_on_whitespace) {
                        /*
                         * Split at first whitespace, tying off the beginning
@@ -856,6 +910,8 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
                OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'),
                OPT_BOOL(0, "allow-unknown-type", &unknown_type,
                          N_("allow -s and -t to work with broken/corrupt objects")),
+               OPT_BOOL(0, "use-mailmap", &use_mailmap, N_("use mail map file")),
+               OPT_ALIAS(0, "mailmap", "use-mailmap"),
                /* Batch mode */
                OPT_GROUP(N_("Batch objects requested on stdin (or --batch-all-objects)")),
                OPT_CALLBACK_F(0, "batch", &batch, N_("format"),
@@ -866,6 +922,7 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
                        N_("like --batch, but don't emit <contents>"),
                        PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
                        batch_option_callback),
+               OPT_BOOL('z', NULL, &batch.nul_terminated, N_("stdin is NUL-terminated")),
                OPT_CALLBACK_F(0, "batch-command", &batch, N_("format"),
                        N_("read commands from stdin"),
                        PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
@@ -898,6 +955,9 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
        opt_cw = (opt == 'c' || opt == 'w');
        opt_epts = (opt == 'e' || opt == 'p' || opt == 't' || opt == 's');
 
+       if (use_mailmap)
+               read_mailmap(&mailmap);
+
        /* --batch-all-objects? */
        if (opt == 'b')
                batch.all_objects = 1;
@@ -921,6 +981,9 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
        else if (batch.all_objects)
                usage_msg_optf(_("'%s' requires a batch mode"), usage, options,
                               "--batch-all-objects");
+       else if (batch.nul_terminated)
+               usage_msg_optf(_("'%s' requires a batch mode"), usage, options,
+                              "-z");
 
        /* Batch defaults */
        if (batch.buffer_output < 0)
index 29c74f898bf183d724bba22457a1a3ab782e519a..f9d63d80b926c783265beb459962954128f6ea4c 100644 (file)
@@ -626,6 +626,7 @@ static void show_local_changes(struct object *head,
        repo_init_revisions(the_repository, &rev, NULL);
        rev.diffopt.flags = opts->flags;
        rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
+       rev.diffopt.flags.recursive = 1;
        diff_setup_done(&rev.diffopt);
        add_pending_object(&rev, head, NULL);
        run_diff_index(&rev, 0);
index 222f994f863cba4226e867bf0f1022e9d551e308..09ac4289f13065a20e0fb50d08d0a784d3dab8e0 100644 (file)
@@ -43,6 +43,8 @@ static enum help_action {
        HELP_ACTION_ALL = 1,
        HELP_ACTION_GUIDES,
        HELP_ACTION_CONFIG,
+       HELP_ACTION_USER_INTERFACES,
+       HELP_ACTION_DEVELOPER_INTERFACES,
        HELP_ACTION_CONFIG_FOR_COMPLETION,
        HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION,
 } cmd_mode;
@@ -69,6 +71,12 @@ static struct option builtin_help_options[] = {
 
        OPT_CMDMODE('g', "guides", &cmd_mode, N_("print list of useful guides"),
                    HELP_ACTION_GUIDES),
+       OPT_CMDMODE(0, "user-interfaces", &cmd_mode,
+                   N_("print list of user-facing repository, command and file interfaces"),
+                   HELP_ACTION_USER_INTERFACES),
+       OPT_CMDMODE(0, "developer-interfaces", &cmd_mode,
+                   N_("print list of file formats, protocols and other developer interfaces"),
+                   HELP_ACTION_DEVELOPER_INTERFACES),
        OPT_CMDMODE('c', "config", &cmd_mode, N_("print all configuration variable names"),
                    HELP_ACTION_CONFIG),
        OPT_CMDMODE_F(0, "config-for-completion", &cmd_mode, "",
@@ -81,9 +89,11 @@ static struct option builtin_help_options[] = {
 
 static const char * const builtin_help_usage[] = {
        "git help [-a|--all] [--[no-]verbose]] [--[no-]external-commands] [--[no-]aliases]",
-       N_("git help [[-i|--info] [-m|--man] [-w|--web]] [<command>]"),
+       N_("git help [[-i|--info] [-m|--man] [-w|--web]] [<command>|<doc>]"),
        "git help [-g|--guides]",
        "git help [-c|--config]",
+       "git help [--user-interfaces]",
+       "git help [--developer-interfaces]",
        NULL
 };
 
@@ -654,6 +664,14 @@ int cmd_help(int argc, const char **argv, const char *prefix)
                opt_mode_usage(argc, "--config-for-completion", help_format);
                list_config_help(SHOW_CONFIG_VARS);
                return 0;
+       case HELP_ACTION_USER_INTERFACES:
+               opt_mode_usage(argc, "--user-interfaces", help_format);
+               list_user_interfaces_help();
+               return 0;
+       case HELP_ACTION_DEVELOPER_INTERFACES:
+               opt_mode_usage(argc, "--developer-interfaces", help_format);
+               list_developer_interfaces_help();
+               return 0;
        case HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION:
                opt_mode_usage(argc, "--config-sections-for-completion",
                               help_format);
index 88a5e98875adb0398b2855460bddd5fa43e073ec..9b937d59b83dc29691d9cb940223c981f38ff7bf 100644 (file)
@@ -668,10 +668,10 @@ static void show_setup_revisions_tweak(struct rev_info *rev,
 int cmd_show(int argc, const char **argv, const char *prefix)
 {
        struct rev_info rev;
-       struct object_array_entry *objects;
+       unsigned int i;
        struct setup_revision_opt opt;
        struct pathspec match_all;
-       int i, count, ret = 0;
+       int ret = 0;
 
        init_log_defaults();
        git_config(git_log_config, NULL);
@@ -698,12 +698,10 @@ int cmd_show(int argc, const char **argv, const char *prefix)
        if (!rev.no_walk)
                return cmd_log_deinit(cmd_log_walk(&rev), &rev);
 
-       count = rev.pending.nr;
-       objects = rev.pending.objects;
        rev.diffopt.no_free = 1;
-       for (i = 0; i < count && !ret; i++) {
-               struct object *o = objects[i].item;
-               const char *name = objects[i].name;
+       for (i = 0; i < rev.pending.nr && !ret; i++) {
+               struct object *o = rev.pending.objects[i].item;
+               const char *name = rev.pending.objects[i].name;
                switch (o->type) {
                case OBJ_BLOB:
                        ret = show_blob_object(&o->oid, &rev, name);
@@ -726,7 +724,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
                        if (!o)
                                ret = error(_("could not read object %s"),
                                            oid_to_hex(oid));
-                       objects[i].item = o;
+                       rev.pending.objects[i].item = o;
                        i--;
                        break;
                }
@@ -743,11 +741,24 @@ int cmd_show(int argc, const char **argv, const char *prefix)
                        rev.shown_one = 1;
                        break;
                case OBJ_COMMIT:
-                       rev.pending.nr = rev.pending.alloc = 0;
-                       rev.pending.objects = NULL;
+               {
+                       struct object_array old;
+                       struct object_array blank = OBJECT_ARRAY_INIT;
+
+                       memcpy(&old, &rev.pending, sizeof(old));
+                       memcpy(&rev.pending, &blank, sizeof(rev.pending));
+
                        add_object_array(o, name, &rev.pending);
                        ret = cmd_log_walk_no_free(&rev);
+
+                       /*
+                        * No need for
+                        * object_array_clear(&pending). It was
+                        * cleared already in prepare_revision_walk()
+                        */
+                       memcpy(&rev.pending, &old, sizeof(rev.pending));
                        break;
+               }
                default:
                        ret = error(_("unknown type: %d"), o->type);
                }
index e791b65e7e9afb2b9f51fee4af9a0f1c978b9536..779dc18e59d56b46bcccba7ac9754726e6b1935e 100644 (file)
@@ -11,6 +11,7 @@
 #include "quote.h"
 #include "dir.h"
 #include "builtin.h"
+#include "strbuf.h"
 #include "tree.h"
 #include "cache-tree.h"
 #include "parse-options.h"
@@ -48,6 +49,7 @@ static char *ps_matched;
 static const char *with_tree;
 static int exc_given;
 static int exclude_args;
+static const char *format;
 
 static const char *tag_cached = "";
 static const char *tag_unmerged = "";
@@ -85,6 +87,16 @@ static void write_name(const char *name)
                                   stdout, line_terminator);
 }
 
+static void write_name_to_buf(struct strbuf *sb, const char *name)
+{
+       const char *rel = relative_path(name, prefix_len ? prefix : NULL, sb);
+
+       if (line_terminator)
+               quote_c_style(rel, sb, NULL, 0);
+       else
+               strbuf_addstr(sb, rel);
+}
+
 static const char *get_tag(const struct cache_entry *ce, const char *tag)
 {
        static char alttag[4];
@@ -222,6 +234,73 @@ static void show_submodule(struct repository *superproject,
        repo_clear(&subrepo);
 }
 
+struct show_index_data {
+       const char *pathname;
+       struct index_state *istate;
+       const struct cache_entry *ce;
+};
+
+static size_t expand_show_index(struct strbuf *sb, const char *start,
+                               void *context)
+{
+       struct show_index_data *data = context;
+       const char *end;
+       const char *p;
+       size_t len = strbuf_expand_literal_cb(sb, start, NULL);
+       struct stat st;
+
+       if (len)
+               return len;
+       if (*start != '(')
+               die(_("bad ls-files format: element '%s' "
+                     "does not start with '('"), start);
+
+       end = strchr(start + 1, ')');
+       if (!end)
+               die(_("bad ls-files format: element '%s'"
+                     "does not end in ')'"), start);
+
+       len = end - start + 1;
+       if (skip_prefix(start, "(objectmode)", &p))
+               strbuf_addf(sb, "%06o", data->ce->ce_mode);
+       else if (skip_prefix(start, "(objectname)", &p))
+               strbuf_add_unique_abbrev(sb, &data->ce->oid, abbrev);
+       else if (skip_prefix(start, "(stage)", &p))
+               strbuf_addf(sb, "%d", ce_stage(data->ce));
+       else if (skip_prefix(start, "(eolinfo:index)", &p))
+               strbuf_addstr(sb, S_ISREG(data->ce->ce_mode) ?
+                             get_cached_convert_stats_ascii(data->istate,
+                             data->ce->name) : "");
+       else if (skip_prefix(start, "(eolinfo:worktree)", &p))
+               strbuf_addstr(sb, !lstat(data->pathname, &st) &&
+                             S_ISREG(st.st_mode) ?
+                             get_wt_convert_stats_ascii(data->pathname) : "");
+       else if (skip_prefix(start, "(eolattr)", &p))
+               strbuf_addstr(sb, get_convert_attr_ascii(data->istate,
+                             data->pathname));
+       else if (skip_prefix(start, "(path)", &p))
+               write_name_to_buf(sb, data->pathname);
+       else
+               die(_("bad ls-files format: %%%.*s"), (int)len, start);
+
+       return len;
+}
+
+static void show_ce_fmt(struct repository *repo, const struct cache_entry *ce,
+                       const char *format, const char *fullname) {
+       struct show_index_data data = {
+               .pathname = fullname,
+               .istate = repo->index,
+               .ce = ce,
+       };
+       struct strbuf sb = STRBUF_INIT;
+
+       strbuf_expand(&sb, format, expand_show_index, &data);
+       strbuf_addch(&sb, line_terminator);
+       fwrite(sb.buf, sb.len, 1, stdout);
+       strbuf_release(&sb);
+}
+
 static void show_ce(struct repository *repo, struct dir_struct *dir,
                    const struct cache_entry *ce, const char *fullname,
                    const char *tag)
@@ -236,6 +315,12 @@ static void show_ce(struct repository *repo, struct dir_struct *dir,
                                  max_prefix_len, ps_matched,
                                  S_ISDIR(ce->ce_mode) ||
                                  S_ISGITLINK(ce->ce_mode))) {
+               if (format) {
+                       show_ce_fmt(repo, ce, format, fullname);
+                       print_debug(ce);
+                       return;
+               }
+
                tag = get_tag(ce, tag);
 
                if (!show_stage) {
@@ -675,6 +760,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
                         N_("suppress duplicate entries")),
                OPT_BOOL(0, "sparse", &show_sparse_dirs,
                         N_("show sparse directories in the presence of a sparse index")),
+               OPT_STRING_F(0, "format", &format, N_("format"),
+                            N_("format to use for the output"),
+                            PARSE_OPT_NONEG),
                OPT_END()
        };
        int ret = 0;
@@ -699,6 +787,13 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
        for (i = 0; i < exclude_list.nr; i++) {
                add_pattern(exclude_list.items[i].string, "", 0, pl, --exclude_args);
        }
+
+       if (format && (show_stage || show_others || show_killed ||
+               show_resolve_undo || skipping_duplicates || show_eol || show_tag))
+                       usage_msg_opt(_("--format cannot be used with -s, -o, -k, -t, "
+                                     "--resolve-undo, --deduplicate, --eol"),
+                                     ls_files_usage, builtin_ls_files_options);
+
        if (show_tag || show_valid_bit || show_fsmonitor_bit) {
                tag_cached = "H ";
                tag_unmerged = "M ";
index 23170f2d2a644c2ace8899bf32b4ea642dbf2384..f7c92c0e64ffa32866f53d90793f9d4b4d11be25 100644 (file)
@@ -313,8 +313,16 @@ static int save_state(struct object_id *stash)
        int len;
        struct child_process cp = CHILD_PROCESS_INIT;
        struct strbuf buffer = STRBUF_INIT;
+       struct lock_file lock_file = LOCK_INIT;
+       int fd;
        int rc = -1;
 
+       fd = repo_hold_locked_index(the_repository, &lock_file, 0);
+       refresh_cache(REFRESH_QUIET);
+       if (0 <= fd)
+               repo_update_index_if_able(the_repository, &lock_file);
+       rollback_lock_file(&lock_file);
+
        strvec_pushl(&cp.args, "stash", "create", NULL);
        cp.out = -1;
        cp.git_cmd = 1;
@@ -375,22 +383,26 @@ static void reset_hard(const struct object_id *oid, int verbose)
 static void restore_state(const struct object_id *head,
                          const struct object_id *stash)
 {
-       const char *args[] = { "stash", "apply", NULL, NULL };
-
-       if (is_null_oid(stash))
-               return;
+       struct strvec args = STRVEC_INIT;
 
        reset_hard(head, 1);
 
-       args[2] = oid_to_hex(stash);
+       if (is_null_oid(stash))
+               goto refresh_cache;
+
+       strvec_pushl(&args, "stash", "apply", "--index", "--quiet", NULL);
+       strvec_push(&args, oid_to_hex(stash));
 
        /*
         * It is OK to ignore error here, for example when there was
         * nothing to restore.
         */
-       run_command_v_opt(args, RUN_GIT_CMD);
+       run_command_v_opt(args.v, RUN_GIT_CMD);
+       strvec_clear(&args);
 
-       refresh_cache(REFRESH_QUIET);
+refresh_cache:
+       if (discard_cache() < 0 || read_cache() < 0)
+               die(_("could not read index"));
 }
 
 /* This is called when no merge was necessary. */
@@ -754,8 +766,10 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
                else
                        clean = merge_recursive(&o, head, remoteheads->item,
                                                reversed, &result);
-               if (clean < 0)
-                       exit(128);
+               if (clean < 0) {
+                       rollback_lock_file(&lock);
+                       return 2;
+               }
                if (write_locked_index(&the_index, &lock,
                                       COMMIT_LOCK | SKIP_IF_UNCHANGED))
                        die(_("unable to write %s"), get_index_file());
@@ -1599,6 +1613,21 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                 */
                refresh_cache(REFRESH_QUIET);
                if (allow_trivial && fast_forward != FF_ONLY) {
+                       /*
+                        * Must first ensure that index matches HEAD before
+                        * attempting a trivial merge.
+                        */
+                       struct tree *head_tree = get_commit_tree(head_commit);
+                       struct strbuf sb = STRBUF_INIT;
+
+                       if (repo_index_has_changes(the_repository, head_tree,
+                                                  &sb)) {
+                               error(_("Your local changes to the following files would be overwritten by merge:\n  %s"),
+                                     sb.buf);
+                               strbuf_release(&sb);
+                               return 2;
+                       }
+
                        /* See if it is really trivial. */
                        git_committer_info(IDENT_STRICT);
                        printf(_("Trying really trivial in-index merge...\n"));
@@ -1655,12 +1684,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
         * tree in the index -- this means that the index must be in
         * sync with the head commit.  The strategies are responsible
         * to ensure this.
+        *
+        * Stash away the local changes so that we can try more than one
+        * and/or recover from merge strategies bailing while leaving the
+        * index and working tree polluted.
         */
-       if (use_strategies_nr == 1 ||
-           /*
-            * Stash away the local changes so that we can try more than one.
-            */
-           save_state(&stash))
+       if (save_state(&stash))
                oidclr(&stash);
 
        for (i = 0; !merge_was_ok && i < use_strategies_nr; i++) {
index d9b8746cb3cb6cf78c8790971ad8f23e6a91d2e3..c713463d89d437916fc87beade4695991b17ed6a 100644 (file)
@@ -1229,10 +1229,9 @@ static int get_one_entry(struct remote *remote, void *priv)
 
 static int show_all(void)
 {
-       struct string_list list = STRING_LIST_INIT_NODUP;
+       struct string_list list = STRING_LIST_INIT_DUP;
        int result;
 
-       list.strdup_strings = 1;
        result = for_each_remote(get_one_entry, &list);
 
        if (!result) {
index 344fff8f3a9322a7895b607a8236c5461625407d..fdce6f8c85670c8b2b0e20304336debba3190408 100644 (file)
@@ -174,88 +174,6 @@ static void update_index_from_diff(struct diff_queue_struct *q,
        }
 }
 
-static int pathspec_needs_expanded_index(const struct pathspec *pathspec)
-{
-       unsigned int i, pos;
-       int res = 0;
-       char *skip_worktree_seen = NULL;
-
-       /*
-        * When using a magic pathspec, assume for the sake of simplicity that
-        * the index needs to be expanded to match all matchable files.
-        */
-       if (pathspec->magic)
-               return 1;
-
-       for (i = 0; i < pathspec->nr; i++) {
-               struct pathspec_item item = pathspec->items[i];
-
-               /*
-                * If the pathspec item has a wildcard, the index should be expanded
-                * if the pathspec has the possibility of matching a subset of entries inside
-                * of a sparse directory (but not the entire directory).
-                *
-                * If the pathspec item is a literal path, the index only needs to be expanded
-                * if a) the pathspec isn't in the sparse checkout cone (to make sure we don't
-                * expand for in-cone files) and b) it doesn't match any sparse directories
-                * (since we can reset whole sparse directories without expanding them).
-                */
-               if (item.nowildcard_len < item.len) {
-                       /*
-                        * Special case: if the pattern is a path inside the cone
-                        * followed by only wildcards, the pattern cannot match
-                        * partial sparse directories, so we know we don't need to
-                        * expand the index.
-                        *
-                        * Examples:
-                        * - in-cone/foo***: doesn't need expanded index
-                        * - not-in-cone/bar*: may need expanded index
-                        * - **.c: may need expanded index
-                        */
-                       if (strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len &&
-                           path_in_cone_mode_sparse_checkout(item.original, &the_index))
-                               continue;
-
-                       for (pos = 0; pos < active_nr; pos++) {
-                               struct cache_entry *ce = active_cache[pos];
-
-                               if (!S_ISSPARSEDIR(ce->ce_mode))
-                                       continue;
-
-                               /*
-                                * If the pre-wildcard length is longer than the sparse
-                                * directory name and the sparse directory is the first
-                                * component of the pathspec, need to expand the index.
-                                */
-                               if (item.nowildcard_len > ce_namelen(ce) &&
-                                   !strncmp(item.original, ce->name, ce_namelen(ce))) {
-                                       res = 1;
-                                       break;
-                               }
-
-                               /*
-                                * If the pre-wildcard length is shorter than the sparse
-                                * directory and the pathspec does not match the whole
-                                * directory, need to expand the index.
-                                */
-                               if (!strncmp(item.original, ce->name, item.nowildcard_len) &&
-                                   wildmatch(item.original, ce->name, 0)) {
-                                       res = 1;
-                                       break;
-                               }
-                       }
-               } else if (!path_in_cone_mode_sparse_checkout(item.original, &the_index) &&
-                          !matches_skip_worktree(pathspec, i, &skip_worktree_seen))
-                       res = 1;
-
-               if (res > 0)
-                       break;
-       }
-
-       free(skip_worktree_seen);
-       return res;
-}
-
 static int read_from_tree(const struct pathspec *pathspec,
                          struct object_id *tree_oid,
                          int intent_to_add)
@@ -273,7 +191,7 @@ static int read_from_tree(const struct pathspec *pathspec,
        opt.change = diff_change;
        opt.add_remove = diff_addremove;
 
-       if (pathspec->nr && the_index.sparse_index && pathspec_needs_expanded_index(pathspec))
+       if (pathspec->nr && pathspec_needs_expanded_index(&the_index, pathspec))
                ensure_full_index(&the_index);
 
        if (do_diff_cache(tree_oid, &opt))
index 30fd8e83eaf2ca767802343d2dcff1825f6b1b50..fba6f5d51f32d1217b89298e1361169b43feb38b 100644 (file)
@@ -46,6 +46,7 @@ static const char rev_list_usage[] =
 "    --parents\n"
 "    --children\n"
 "    --objects | --objects-edge\n"
+"    --disk-usage[=human]\n"
 "    --unpacked\n"
 "    --header | --pretty\n"
 "    --[no-]object-names\n"
@@ -81,6 +82,7 @@ static int arg_show_object_names = 1;
 
 static int show_disk_usage;
 static off_t total_disk_usage;
+static int human_readable;
 
 static off_t get_object_disk_usage(struct object *obj)
 {
@@ -368,6 +370,17 @@ static int show_object_fast(
        return 1;
 }
 
+static void print_disk_usage(off_t size)
+{
+       struct strbuf sb = STRBUF_INIT;
+       if (human_readable)
+               strbuf_humanise_bytes(&sb, size);
+       else
+               strbuf_addf(&sb, "%"PRIuMAX, (uintmax_t)size);
+       puts(sb.buf);
+       strbuf_release(&sb);
+}
+
 static inline int parse_missing_action_value(const char *value)
 {
        if (!strcmp(value, "error")) {
@@ -473,6 +486,7 @@ static int try_bitmap_disk_usage(struct rev_info *revs,
                                 int filter_provided_objects)
 {
        struct bitmap_index *bitmap_git;
+       off_t size_from_bitmap;
 
        if (!show_disk_usage)
                return -1;
@@ -481,8 +495,8 @@ static int try_bitmap_disk_usage(struct rev_info *revs,
        if (!bitmap_git)
                return -1;
 
-       printf("%"PRIuMAX"\n",
-              (uintmax_t)get_disk_usage_from_bitmap(bitmap_git, revs));
+       size_from_bitmap = get_disk_usage_from_bitmap(bitmap_git, revs);
+       print_disk_usage(size_from_bitmap);
        return 0;
 }
 
@@ -624,7 +638,21 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                        continue;
                }
 
-               if (!strcmp(arg, "--disk-usage")) {
+               if (skip_prefix(arg, "--disk-usage", &arg)) {
+                       if (*arg == '=') {
+                               if (!strcmp(++arg, "human")) {
+                                       human_readable = 1;
+                               } else
+                                       die(_("invalid value for '%s': '%s', the only allowed format is '%s'"),
+                                           "--disk-usage=<format>", arg, "human");
+                       } else if (*arg) {
+                               /*
+                                * Arguably should goto a label to continue chain of ifs?
+                                * Doesn't matter unless we try to add --disk-usage-foo
+                                * afterwards.
+                                */
+                               usage(rev_list_usage);
+                       }
                        show_disk_usage = 1;
                        info.flags |= REV_LIST_QUIET;
                        continue;
@@ -753,7 +781,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
        }
 
        if (show_disk_usage)
-               printf("%"PRIuMAX"\n", (uintmax_t)total_disk_usage);
+               print_disk_usage(total_disk_usage);
 
 cleanup:
        release_revisions(&revs);
index 84a935a16e8be447d0bc95ad2a1e5d133452e635..b6ba859fe42571fd868697db1da8dac6ffd2c32e 100644 (file)
@@ -287,6 +287,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
        if (!index_only)
                setup_work_tree();
 
+       prepare_repo_settings(the_repository);
+       the_repository->settings.command_requires_full_index = 0;
        hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
 
        if (read_cache() < 0)
@@ -296,8 +298,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 
        seen = xcalloc(pathspec.nr, 1);
 
-       /* TODO: audit for interaction with sparse-index. */
-       ensure_full_index(&the_index);
+       if (pathspec_needs_expanded_index(&the_index, &pathspec))
+               ensure_full_index(&the_index);
+
        for (i = 0; i < active_nr; i++) {
                const struct cache_entry *ce = active_cache[i];
 
index fac52ade5e1792dff7ea717bbc28321fd2489509..b63f420ecef3c8f53b6043b7fdacacc945bd99bb 100644 (file)
@@ -1104,6 +1104,9 @@ static int compute_summary_module_list(struct object_id *head_oid,
 {
        struct strvec diff_args = STRVEC_INIT;
        struct rev_info rev;
+       struct setup_revision_opt opt = {
+               .free_removed_argv_elements = 1,
+       };
        struct module_cb_list list = MODULE_CB_LIST_INIT;
        int ret = 0;
 
@@ -1121,7 +1124,7 @@ static int compute_summary_module_list(struct object_id *head_oid,
        init_revisions(&rev, info->prefix);
        rev.abbrev = 0;
        precompose_argv_prefix(diff_args.nr, diff_args.v, NULL);
-       setup_revisions(diff_args.nr, diff_args.v, &rev, NULL);
+       setup_revisions(diff_args.nr, diff_args.v, &rev, &opt);
        rev.diffopt.output_format = DIFF_FORMAT_NO_OUTPUT | DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = submodule_summary_callback;
        rev.diffopt.format_callback_data = &list;
index e547a08d6c7ce517a5a26598ade75356da93cdab..1b0f10225f0c2630fab0f67534e7135b30571c66 100644 (file)
@@ -71,6 +71,8 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
                if (!strcmp(argv[0], "HEAD") &&
                    !starts_with(argv[1], "refs/"))
                        die("Refusing to point HEAD outside of refs/");
+               if (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)
+                       die("Refusing to set '%s' to invalid ref '%s'", argv[0], argv[1]);
                ret = !!create_symref(argv[0], argv[1], msg);
                break;
        default:
index 98ec8938424406ef5973df797b8eca37fda71041..855b68ec23bdb1bdc85c1184a9c144c226f0245c 100644 (file)
@@ -340,6 +340,8 @@ void fsync_loose_object_bulk_checkin(int fd, const char *filename)
         */
        if (!bulk_fsync_objdir ||
            git_fsync(fd, FSYNC_WRITEOUT_ONLY) < 0) {
+               if (errno == ENOSYS)
+                       warning(_("core.fsyncMethod = batch is unsupported on this platform"));
                fsync_or_die(fd, filename);
        }
 }
diff --git a/cache.h b/cache.h
index ac5ab4ef9d341088d2b7a0aa5052e982197cf574..b4070511731ae2941d8e137ac5c64cb9fc089806 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -475,8 +475,7 @@ extern struct index_state the_index;
 
 /*
  * Values in this enum (except those outside the 3 bit range) are part
- * of pack file format. See Documentation/technical/pack-format.txt
- * for more information.
+ * of pack file format. See gitformat-pack(5) for more information.
  */
 enum object_type {
        OBJ_BAD = -1,
@@ -830,6 +829,15 @@ struct cache_entry *index_file_exists(struct index_state *istate, const char *na
  */
 int index_name_pos(struct index_state *, const char *name, int namelen);
 
+/*
+ * Like index_name_pos, returns the position of an entry of the given name in
+ * the index if one exists, otherwise returns a negative value where the negated
+ * value minus 1 is the position where the index entry would be inserted. Unlike
+ * index_name_pos, however, a sparse index is not expanded to find an entry
+ * inside a sparse directory.
+ */
+int index_name_pos_sparse(struct index_state *, const char *name, int namelen);
+
 /*
  * Determines whether an entry with the given name exists within the
  * given index. The return value is 1 if an exact match is found, otherwise
@@ -1688,6 +1696,12 @@ struct ident_split {
  */
 int split_ident_line(struct ident_split *, const char *, int);
 
+/*
+ * Given a commit or tag object buffer and the commit or tag headers, replaces
+ * the idents in the headers with their canonical versions using the mailmap mechanism.
+ */
+void apply_mailmap_to_header(struct strbuf *, const char **, struct string_list *);
+
 /*
  * Compare split idents for equality or strict ordering. Note that we
  * compare only the ident part of the line, ignoring any timestamp.
index f095519f8dba9672530430ced23eb5c66e7b8e67..1b0cc2b57db8667f3cbc9ae8b6223dd7997e7485 100755 (executable)
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -276,6 +276,7 @@ linux-musl)
 linux-leaks)
        export SANITIZE=leak
        export GIT_TEST_PASSING_SANITIZE_LEAK=true
+       export GIT_TEST_SANITIZE_LEAK_LOG=true
        ;;
 esac
 
index 9bd6f3c48f4d1853e4cc5df7a9685696f63d72ce..f96bdabd7d95d8c00468a969fdb83dc724f49ede 100644 (file)
 # specified here, which can only have "guide" attribute and nothing
 # else.
 #
+# User-facing repository, command and file interfaces such as
+# documentation for the .gitmodules, .mailmap etc. files lives in man
+# sections 5 and 7. These entries can only have the "userinterfaces"
+# attribute and nothing else.
+#
+# Git's file formats and protocols, such as documentation for the
+# *.bundle format lives in man section 5. These entries can only have
+# the "developerinterfaces" attribute and nothing else.
+#
 ### command list (do not change this line)
 # command name                          category [category] [category]
 git-add                                 mainporcelain           worktree
@@ -192,24 +201,35 @@ git-verify-tag                          ancillaryinterrogators
 git-whatchanged                         ancillaryinterrogators          complete
 git-worktree                            mainporcelain
 git-write-tree                          plumbingmanipulators
-gitattributes                           guide
-gitcli                                  guide
+gitattributes                           userinterfaces
+gitcli                                  userinterfaces
 gitcore-tutorial                        guide
 gitcredentials                          guide
 gitcvs-migration                        guide
 gitdiffcore                             guide
 giteveryday                             guide
 gitfaq                                  guide
+gitformat-bundle                        developerinterfaces
+gitformat-chunk                         developerinterfaces
+gitformat-commit-graph                  developerinterfaces
+gitformat-index                         developerinterfaces
+gitformat-pack                          developerinterfaces
+gitformat-signature                     developerinterfaces
 gitglossary                             guide
-githooks                                guide
-gitignore                               guide
+githooks                                userinterfaces
+gitignore                               userinterfaces
 gitk                                    mainporcelain
-gitmailmap                              guide
-gitmodules                              guide
+gitmailmap                              userinterfaces
+gitmodules                              userinterfaces
 gitnamespaces                           guide
+gitprotocol-capabilities                developerinterfaces
+gitprotocol-common                      developerinterfaces
+gitprotocol-http                        developerinterfaces
+gitprotocol-pack                        developerinterfaces
+gitprotocol-v2                          developerinterfaces
 gitremote-helpers                       guide
-gitrepository-layout                    guide
-gitrevisions                            guide
+gitrepository-layout                    userinterfaces
+gitrevisions                            userinterfaces
 gitsubmodules                           guide
 gittutorial                             guide
 gittutorial-2                           guide
index cc3c96659688a671ec7955cbc458f1c15970bd8f..f2a36032f84a606474607ede2ff7f0f5e743b7f3 100644 (file)
@@ -888,6 +888,14 @@ static int find_commit_pos_in_graph(struct commit *item, struct commit_graph *g,
        }
 }
 
+int repo_find_commit_pos_in_graph(struct repository *r, struct commit *c,
+                                 uint32_t *pos)
+{
+       if (!prepare_commit_graph(r))
+               return 0;
+       return find_commit_pos_in_graph(c, r->objects->commit_graph, pos);
+}
+
 struct commit *lookup_commit_in_graph(struct repository *repo, const struct object_id *id)
 {
        struct commit *commit;
@@ -945,9 +953,7 @@ int parse_commit_in_graph(struct repository *r, struct commit *item)
 void load_commit_graph_info(struct repository *r, struct commit *item)
 {
        uint32_t pos;
-       if (!prepare_commit_graph(r))
-               return;
-       if (find_commit_pos_in_graph(item, r->objects->commit_graph, &pos))
+       if (repo_find_commit_pos_in_graph(r, item, &pos))
                fill_commit_graph_info(item, r->objects->commit_graph, pos);
 }
 
index 25cdec127aca098ed4bd35bdfcf8e630d8758e01..37faee6b66d59c693dc9ee829dbd5ad61654fe3c 100644 (file)
@@ -40,6 +40,21 @@ int open_commit_graph(const char *graph_file, int *fd, struct stat *st);
  */
 int parse_commit_in_graph(struct repository *r, struct commit *item);
 
+/*
+ * Fills `*pos` with the graph position of `c`, and returns 1 if `c` is
+ * found in the commit-graph belonging to `r`, or 0 otherwise.
+ * Initializes the commit-graph belonging to `r` if it hasn't been
+ * already.
+ *
+ * Note: this is a low-level helper that does not alter any slab data
+ * associated with `c`. Useful in circumstances where the slab data is
+ * already being modified (e.g., writing the commit-graph itself).
+ *
+ * In most cases, callers should use `parse_commit_in_graph()` instead.
+ */
+int repo_find_commit_pos_in_graph(struct repository *r, struct commit *c,
+                                 uint32_t *pos);
+
 /*
  * Look up the given commit ID in the commit-graph. This will only return a
  * commit if the ID exists both in the graph and in the object database such
index 1fb1b2ea90c5953eb465d3b108c01f23fb442a32..0db461f97355d4ae02a2106da0219012ff16efe9 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -642,10 +642,11 @@ struct commit_list * commit_list_insert_by_date(struct commit *item, struct comm
        return commit_list_insert(item, pp);
 }
 
-static int commit_list_compare_by_date(const void *a, const void *b)
+static int commit_list_compare_by_date(const struct commit_list *a,
+                                      const struct commit_list *b)
 {
-       timestamp_t a_date = ((const struct commit_list *)a)->item->date;
-       timestamp_t b_date = ((const struct commit_list *)b)->item->date;
+       timestamp_t a_date = a->item->date;
+       timestamp_t b_date = b->item->date;
        if (a_date < b_date)
                return 1;
        if (a_date > b_date)
@@ -653,20 +654,11 @@ static int commit_list_compare_by_date(const void *a, const void *b)
        return 0;
 }
 
-static void *commit_list_get_next(const void *a)
-{
-       return ((const struct commit_list *)a)->next;
-}
-
-static void commit_list_set_next(void *a, void *next)
-{
-       ((struct commit_list *)a)->next = next;
-}
+DEFINE_LIST_SORT(static, commit_list_sort, struct commit_list, next);
 
 void commit_list_sort_by_date(struct commit_list **list)
 {
-       *list = llist_mergesort(*list, commit_list_get_next, commit_list_set_next,
-                               commit_list_compare_by_date);
+       commit_list_sort(list, commit_list_compare_by_date);
 }
 
 struct commit *pop_most_recent_commit(struct commit_list **list,
index b5502997e2dce6f98890e790733b7d387ccaf25c..901375d58415a3ae21f03a15e7b78da6f8b08efa 100644 (file)
@@ -1,6 +1,7 @@
 #include "../git-compat-util.h"
 #include "win32.h"
 #include <aclapi.h>
+#include <sddl.h>
 #include <conio.h>
 #include <wchar.h>
 #include "../strbuf.h"
@@ -768,8 +769,8 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
                wfilename[n] = L'\0';
                attributes = GetFileAttributesW(wfilename);
                wfilename[n] = c;
-               if (attributes == FILE_ATTRIBUTE_DIRECTORY ||
-                               attributes == FILE_ATTRIBUTE_DEVICE)
+               if (attributes &
+                   (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE))
                        return 1;
                if (attributes == INVALID_FILE_ATTRIBUTES)
                        switch (GetLastError()) {
@@ -2670,7 +2671,22 @@ static PSID get_current_user_sid(void)
        return result;
 }
 
-int is_path_owned_by_current_sid(const char *path)
+static int acls_supported(const char *path)
+{
+       size_t offset = offset_1st_component(path);
+       WCHAR wroot[MAX_PATH];
+       DWORD file_system_flags;
+
+       if (offset &&
+           xutftowcsn(wroot, path, MAX_PATH, offset) > 0 &&
+           GetVolumeInformationW(wroot, NULL, 0, NULL, NULL,
+                                 &file_system_flags, NULL, 0))
+               return !!(file_system_flags & FILE_PERSISTENT_ACLS);
+
+       return 0;
+}
+
+int is_path_owned_by_current_sid(const char *path, struct strbuf *report)
 {
        WCHAR wpath[MAX_PATH];
        PSID sid = NULL;
@@ -2709,6 +2725,7 @@ int is_path_owned_by_current_sid(const char *path)
        else if (sid && IsValidSid(sid)) {
                /* Now, verify that the SID matches the current user's */
                static PSID current_user_sid;
+               BOOL is_member;
 
                if (!current_user_sid)
                        current_user_sid = get_current_user_sid();
@@ -2717,6 +2734,46 @@ int is_path_owned_by_current_sid(const char *path)
                    IsValidSid(current_user_sid) &&
                    EqualSid(sid, current_user_sid))
                        result = 1;
+               else if (IsWellKnownSid(sid, WinBuiltinAdministratorsSid) &&
+                        CheckTokenMembership(NULL, sid, &is_member) &&
+                        is_member)
+                       /*
+                        * If owned by the Administrators group, and the
+                        * current user is an administrator, we consider that
+                        * okay, too.
+                        */
+                       result = 1;
+               else if (report &&
+                        IsWellKnownSid(sid, WinWorldSid) &&
+                        !acls_supported(path)) {
+                       /*
+                        * On FAT32 volumes, ownership is not actually recorded.
+                        */
+                       strbuf_addf(report, "'%s' is on a file system that does"
+                                   "not record ownership\n", path);
+               } else if (report) {
+                       LPSTR str1, str2, to_free1 = NULL, to_free2 = NULL;
+
+                       if (ConvertSidToStringSidA(sid, &str1))
+                               to_free1 = str1;
+                       else
+                               str1 = "(inconvertible)";
+
+                       if (!current_user_sid)
+                               str2 = "(none)";
+                       else if (!IsValidSid(current_user_sid))
+                               str2 = "(invalid)";
+                       else if (ConvertSidToStringSidA(current_user_sid, &str2))
+                               to_free2 = str2;
+                       else
+                               str2 = "(inconvertible)";
+                       strbuf_addf(report,
+                                   "'%s' is owned by:\n"
+                                   "\t'%s'\nbut the current user is:\n"
+                                   "\t'%s'\n", path, str1, str2);
+                       LocalFree(to_free1);
+                       LocalFree(to_free2);
+               }
        }
 
        /*
index a74da68f31321c18b25ed86299f7d84fa38a69aa..209cf7cebadd1746c8b79e1105bac103b8a72910 100644 (file)
@@ -463,7 +463,7 @@ char *mingw_query_user_email(void);
  * Verifies that the specified path is owned by the user running the
  * current process.
  */
-int is_path_owned_by_current_sid(const char *path);
+int is_path_owned_by_current_sid(const char *path, struct strbuf *report);
 #define is_path_owned_by_current_user is_path_owned_by_current_sid
 
 /**
diff --git a/compat/nonblock.c b/compat/nonblock.c
new file mode 100644 (file)
index 0000000..9694ebd
--- /dev/null
@@ -0,0 +1,50 @@
+#include "git-compat-util.h"
+#include "nonblock.h"
+
+#ifdef O_NONBLOCK
+
+int enable_pipe_nonblock(int fd)
+{
+       int flags = fcntl(fd, F_GETFL);
+       if (flags < 0)
+               return -1;
+       flags |= O_NONBLOCK;
+       return fcntl(fd, F_SETFL, flags);
+}
+
+#elif defined(GIT_WINDOWS_NATIVE)
+
+#include "win32.h"
+
+int enable_pipe_nonblock(int fd)
+{
+       HANDLE h = (HANDLE)_get_osfhandle(fd);
+       DWORD mode;
+       DWORD type = GetFileType(h);
+       if (type == FILE_TYPE_UNKNOWN && GetLastError() != NO_ERROR) {
+               errno = EBADF;
+               return -1;
+       }
+       if (type != FILE_TYPE_PIPE)
+               BUG("unsupported file type: %lu", type);
+       if (!GetNamedPipeHandleState(h, &mode, NULL, NULL, NULL, NULL, 0)) {
+               errno = err_win_to_posix(GetLastError());
+               return -1;
+       }
+       mode |= PIPE_NOWAIT;
+       if (!SetNamedPipeHandleState(h, &mode, NULL, NULL)) {
+               errno = err_win_to_posix(GetLastError());
+               return -1;
+       }
+       return 0;
+}
+
+#else
+
+int enable_pipe_nonblock(int fd)
+{
+       errno = ENOSYS;
+       return -1;
+}
+
+#endif
diff --git a/compat/nonblock.h b/compat/nonblock.h
new file mode 100644 (file)
index 0000000..af1a331
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef COMPAT_NONBLOCK_H
+#define COMPAT_NONBLOCK_H
+
+/*
+ * Enable non-blocking I/O for the pipe specified by the passed-in descriptor.
+ */
+int enable_pipe_nonblock(int fd);
+
+#endif
index 015bec360f51e4934eb58f876abcb5b4c8dbb143..e8ebef77d5c92423f7af85d9176059e2a772a320 100644 (file)
--- a/config.c
+++ b/config.c
@@ -1979,6 +1979,8 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,
        int ret = -1;
        FILE *f;
 
+       if (!filename)
+               BUG("filename cannot be NULL");
        f = fopen_or_warn(filename, "r");
        if (f) {
                ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,
@@ -2645,9 +2647,12 @@ static void read_protected_config(void)
        system_config = git_system_config();
        git_global_config(&user_config, &xdg_config);
 
-       git_configset_add_file(&protected_config, system_config);
-       git_configset_add_file(&protected_config, xdg_config);
-       git_configset_add_file(&protected_config, user_config);
+       if (system_config)
+               git_configset_add_file(&protected_config, system_config);
+       if (xdg_config)
+               git_configset_add_file(&protected_config, xdg_config);
+       if (user_config)
+               git_configset_add_file(&protected_config, user_config);
        git_configset_add_parameters(&protected_config);
 
        free(system_config);
index 335efd4620301a3927a0134df9019b7053ebf1d0..4fa19d361b7837b894376b02941df7f9f971b7a7 100644 (file)
@@ -59,9 +59,13 @@ endif
 
 # uninitialized warnings on gcc 4.9.2 in xdiff/xdiffi.c and config.c
 # not worth fixing since newer compilers correctly stop complaining
+#
+# Likewise, gcc older than 4.9 complains about initializing a
+# struct-within-a-struct using just "{ 0 }"
 ifneq ($(filter gcc4,$(COMPILER_FEATURES)),)
 ifeq ($(filter gcc5,$(COMPILER_FEATURES)),)
 DEVELOPER_CFLAGS += -Wno-uninitialized
+DEVELOPER_CFLAGS += -Wno-missing-braces
 endif
 endif
 
index ce83cad47a24f70fc04bb0efeac5857a8c62ef52..d63629fe807f59deda80ed780c1915df011bf862 100644 (file)
@@ -656,7 +656,6 @@ ifeq ($(uname_S),MINGW)
        UNRELIABLE_FSTAT = UnfortunatelyYes
        OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
        NO_REGEX = YesPlease
-       NO_PYTHON = YesPlease
        ETAGS_TARGET = ETAGS
        NO_POSIX_GOODIES = UnfortunatelyYes
        DEFAULT_HELP_FORMAT = html
@@ -686,6 +685,7 @@ ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
        INTERNAL_QSORT = YesPlease
        HAVE_LIBCHARSET_H = YesPlease
        NO_GETTEXT = YesPlease
+       NO_PYTHON = YesPlease
        COMPAT_CFLAGS += -D__USE_MINGW_ACCESS
 else
        ifneq ($(shell expr "$(uname_R)" : '1\.'),2)
@@ -717,10 +717,8 @@ else
                INSTALL = /bin/install
                INTERNAL_QSORT = YesPlease
                HAVE_LIBCHARSET_H = YesPlease
-               NO_GETTEXT =
                USE_GETTEXT_SCHEME = fallthrough
                USE_LIBPCRE = YesPlease
-               NO_CURL =
                USE_NED_ALLOCATOR = YesPlease
                ifeq (/mingw64,$(subst 32,64,$(prefix)))
                        # Move system config into top-level /etc/
@@ -730,6 +728,7 @@ else
        else
                COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO
                NO_CURL = YesPlease
+               NO_PYTHON = YesPlease
        endif
 endif
 endif
index 1b23f2440d8e4514fea064a7c4d7937b8598f6fc..2237109b57fd12ad58b1ff1ed8bcbb4a3bffc5a5 100644 (file)
@@ -77,7 +77,7 @@ if(USE_VCPKG)
        set(CMAKE_TOOLCHAIN_FILE ${VCPKG_DIR}/scripts/buildsystems/vcpkg.cmake CACHE STRING "Vcpkg toolchain file")
 endif()
 
-find_program(SH_EXE sh PATHS "C:/Program Files/Git/bin")
+find_program(SH_EXE sh PATHS "C:/Program Files/Git/bin" "$ENV{LOCALAPPDATA}/Programs/Git/bin")
 if(NOT SH_EXE)
        message(FATAL_ERROR "sh: shell interpreter was not found in your path, please install one."
                        "On Windows, you can get it as part of 'Git for Windows' install at https://gitforwindows.org/")
index 07227d02287618394d649ae915a1b42d93140950..bf2777308a56b6f233553196821ab2750e269e8d 100755 (executable)
@@ -3,16 +3,9 @@
        cd ../../../t
        test_description='git-credential-netrc'
        . ./test-lib.sh
+       . "$TEST_DIRECTORY"/lib-perl.sh
 
-       if ! test_have_prereq PERL; then
-               skip_all='skipping perl interface tests, perl not available'
-               test_done
-       fi
-
-       perl -MTest::More -e 0 2>/dev/null || {
-               skip_all="Perl Test::More unavailable, skipping test"
-               test_done
-       }
+       skip_all_if_no_Test_More
 
        # set up test repository
 
                'set up test repository' \
                'git config --add gpg.program test.git-config-gpg'
 
-       # The external test will outputs its own plan
-       test_external_has_tap=1
-
        export PERL5LIB="$GITPERLLIB"
-       test_external \
-               'git-credential-netrc' \
+       test_expect_success 'git-credential-netrc' '
                perl "$GIT_BUILD_DIR"/contrib/credential/netrc/test.pl
+       '
 
        test_done
 )
index 01e82e56d15629abd0444646341c1a9d639fc310..1ed174a8cf38e35a993ddeae4e85c234bcc34928 100644 (file)
@@ -42,7 +42,7 @@ $(T):
        @echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
 
 clean-except-prove-cache:
-       $(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)'
+       $(RM) -r 'trash directory'.*
        $(RM) -r valgrind/bin
 
 clean: clean-except-prove-cache
index 276898eb6bd720ac63a87a0f3d7a2bebb542cb37..3d278bb0edbc3696ad99f8c6e89ede6c8141ef04 100644 (file)
@@ -47,7 +47,7 @@ pre-clean:
        $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)'
 
 clean-except-prove-cache:
-       $(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)'
+       $(RM) -r 'trash directory'.*
        $(RM) -r valgrind/bin
 
 clean: clean-except-prove-cache
index 7eb66a417aa08daa6f22753eba5c52f32361dfc8..2edea41a2345af623017d43e14a74d71a32bb873 100644 (file)
@@ -466,6 +466,11 @@ static void do_oneway_diff(struct unpack_trees_options *o,
         * Something removed from the tree?
         */
        if (!idx) {
+               if (S_ISSPARSEDIR(tree->ce_mode)) {
+                       diff_tree_oid(&tree->oid, NULL, tree->name, &revs->diffopt);
+                       return;
+               }
+
                diff_index_show_file(revs, "-", tree, &tree->oid, 1,
                                     tree->ce_mode, 0);
                return;
index cb6647d65703aca1ec1ec7014ac433de31852fcc..a1a508ee72e2f6f58ed4b61430bf2a6d61c00651 100644 (file)
@@ -26,6 +26,7 @@
 #include "commit-reach.h"
 #include "commit-graph.h"
 #include "sigchain.h"
+#include "mergesort.h"
 
 static int transfer_unpack_limit = -1;
 static int fetch_unpack_limit = -1;
@@ -292,6 +293,29 @@ static void mark_tips(struct fetch_negotiator *negotiator,
        return;
 }
 
+static void send_filter(struct fetch_pack_args *args,
+                       struct strbuf *req_buf,
+                       int server_supports_filter)
+{
+       if (args->filter_options.choice) {
+               const char *spec =
+                       expand_list_objects_filter_spec(&args->filter_options);
+               if (server_supports_filter) {
+                       print_verbose(args, _("Server supports filter"));
+                       packet_buf_write(req_buf, "filter %s", spec);
+                       trace2_data_string("fetch", the_repository,
+                                          "filter/effective", spec);
+               } else {
+                       warning("filtering not recognized by server, ignoring");
+                       trace2_data_string("fetch", the_repository,
+                                          "filter/unsupported", spec);
+               }
+       } else {
+               trace2_data_string("fetch", the_repository,
+                                  "filter/none", "");
+       }
+}
+
 static int find_common(struct fetch_negotiator *negotiator,
                       struct fetch_pack_args *args,
                       int fd[2], struct object_id *result_oid,
@@ -299,6 +323,7 @@ static int find_common(struct fetch_negotiator *negotiator,
 {
        int fetching;
        int count = 0, flushes = 0, flush_at = INITIAL_FLUSH, retval;
+       int negotiation_round = 0, haves = 0;
        const struct object_id *oid;
        unsigned in_vain = 0;
        int got_continue = 0;
@@ -389,11 +414,7 @@ static int find_common(struct fetch_negotiator *negotiator,
                        packet_buf_write(&req_buf, "deepen-not %s", s->string);
                }
        }
-       if (server_supports_filtering && args->filter_options.choice) {
-               const char *spec =
-                       expand_list_objects_filter_spec(&args->filter_options);
-               packet_buf_write(&req_buf, "filter %s", spec);
-       }
+       send_filter(args, &req_buf, server_supports_filtering);
        packet_buf_flush(&req_buf);
        state_len = req_buf.len;
 
@@ -441,9 +462,19 @@ static int find_common(struct fetch_negotiator *negotiator,
                packet_buf_write(&req_buf, "have %s\n", oid_to_hex(oid));
                print_verbose(args, "have %s", oid_to_hex(oid));
                in_vain++;
+               haves++;
                if (flush_at <= ++count) {
                        int ack;
 
+                       negotiation_round++;
+                       trace2_region_enter_printf("negotiation_v0_v1", "round",
+                                                  the_repository, "%d",
+                                                  negotiation_round);
+                       trace2_data_intmax("negotiation_v0_v1", the_repository,
+                                          "haves_added", haves);
+                       trace2_data_intmax("negotiation_v0_v1", the_repository,
+                                          "in_vain", in_vain);
+                       haves = 0;
                        packet_buf_flush(&req_buf);
                        send_request(args, fd[1], &req_buf);
                        strbuf_setlen(&req_buf, state_len);
@@ -465,6 +496,9 @@ static int find_common(struct fetch_negotiator *negotiator,
                                                      ack, oid_to_hex(result_oid));
                                switch (ack) {
                                case ACK:
+                                       trace2_region_leave_printf("negotiation_v0_v1", "round",
+                                                                  the_repository, "%d",
+                                                                  negotiation_round);
                                        flushes = 0;
                                        multi_ack = 0;
                                        retval = 0;
@@ -490,6 +524,7 @@ static int find_common(struct fetch_negotiator *negotiator,
                                                const char *hex = oid_to_hex(result_oid);
                                                packet_buf_write(&req_buf, "have %s\n", hex);
                                                state_len = req_buf.len;
+                                               haves++;
                                                /*
                                                 * Reset in_vain because an ack
                                                 * for this commit has not been
@@ -508,6 +543,9 @@ static int find_common(struct fetch_negotiator *negotiator,
                                }
                        } while (ack);
                        flushes--;
+                       trace2_region_leave_printf("negotiation_v0_v1", "round",
+                                                  the_repository, "%d",
+                                                  negotiation_round);
                        if (got_continue && MAX_IN_VAIN < in_vain) {
                                print_verbose(args, _("giving up"));
                                break; /* give up */
@@ -518,6 +556,8 @@ static int find_common(struct fetch_negotiator *negotiator,
        }
 done:
        trace2_region_leave("fetch-pack", "negotiation_v0_v1", the_repository);
+       trace2_data_intmax("negotiation_v0_v1", the_repository, "total_rounds",
+                          negotiation_round);
        if (!got_ready || !no_done) {
                packet_buf_write(&req_buf, "done\n");
                send_request(args, fd[1], &req_buf);
@@ -1025,6 +1065,13 @@ static int get_pack(struct fetch_pack_args *args,
        return 0;
 }
 
+static int ref_compare_name(const struct ref *a, const struct ref *b)
+{
+       return strcmp(a->name, b->name);
+}
+
+DEFINE_LIST_SORT(static, sort_ref_list, struct ref, next);
+
 static int cmp_ref_by_name(const void *a_, const void *b_)
 {
        const struct ref *a = *((const struct ref **)a_);
@@ -1323,15 +1370,8 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
                die(_("Server does not support shallow requests"));
 
        /* Add filter */
-       if (server_supports_feature("fetch", "filter", 0) &&
-           args->filter_options.choice) {
-               const char *spec =
-                       expand_list_objects_filter_spec(&args->filter_options);
-               print_verbose(args, _("Server supports filter"));
-               packet_buf_write(&req_buf, "filter %s", spec);
-       } else if (args->filter_options.choice) {
-               warning("filtering not recognized by server, ignoring");
-       }
+       send_filter(args, &req_buf,
+                   server_supports_feature("fetch", "filter", 0));
 
        if (server_supports_feature("fetch", "packfile-uris", 0)) {
                int i;
@@ -1361,6 +1401,8 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
 
        haves_added = add_haves(negotiator, &req_buf, haves_to_send);
        *in_vain += haves_added;
+       trace2_data_intmax("negotiation_v2", the_repository, "haves_added", haves_added);
+       trace2_data_intmax("negotiation_v2", the_repository, "in_vain", *in_vain);
        if (!haves_added || (seen_ack && *in_vain >= MAX_IN_VAIN)) {
                /* Send Done */
                packet_buf_write(&req_buf, "done\n");
@@ -1603,6 +1645,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
        struct oidset common = OIDSET_INIT;
        struct packet_reader reader;
        int in_vain = 0, negotiation_started = 0;
+       int negotiation_round = 0;
        int haves_to_send = INITIAL_FLUSH;
        struct fetch_negotiator negotiator_alloc;
        struct fetch_negotiator *negotiator;
@@ -1659,12 +1702,20 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
                                                    "negotiation_v2",
                                                    the_repository);
                        }
+                       negotiation_round++;
+                       trace2_region_enter_printf("negotiation_v2", "round",
+                                                  the_repository, "%d",
+                                                  negotiation_round);
                        if (send_fetch_request(negotiator, fd[1], args, ref,
                                               &common,
                                               &haves_to_send, &in_vain,
                                               reader.use_sideband,
-                                              seen_ack))
+                                              seen_ack)) {
+                               trace2_region_leave_printf("negotiation_v2", "round",
+                                                          the_repository, "%d",
+                                                          negotiation_round);
                                state = FETCH_GET_PACK;
+                       }
                        else
                                state = FETCH_PROCESS_ACKS;
                        break;
@@ -1677,6 +1728,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
                                seen_ack = 1;
                                oidset_insert(&common, &common_oid);
                        }
+                       trace2_region_leave_printf("negotiation_v2", "round",
+                                                  the_repository, "%d",
+                                                  negotiation_round);
                        if (received_ready) {
                                /*
                                 * Don't check for response delimiter; get_pack() will
@@ -1692,6 +1746,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
                        trace2_region_leave("fetch-pack",
                                            "negotiation_v2",
                                            the_repository);
+                       trace2_data_intmax("negotiation_v2", the_repository,
+                                          "total_rounds", negotiation_round);
                        /* Check for shallow-info section */
                        if (process_section_header(&reader, "shallow-info", 1))
                                receive_shallow_info(args, &reader, shallows, si);
@@ -2071,6 +2127,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,
        int in_vain = 0;
        int seen_ack = 0;
        int last_iteration = 0;
+       int negotiation_round = 0;
        timestamp_t min_generation = GENERATION_NUMBER_INFINITY;
 
        fetch_negotiator_init(the_repository, &negotiator);
@@ -2084,11 +2141,17 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,
                           add_to_object_array,
                           &nt_object_array);
 
+       trace2_region_enter("fetch-pack", "negotiate_using_fetch", the_repository);
        while (!last_iteration) {
                int haves_added;
                struct object_id common_oid;
                int received_ready = 0;
 
+               negotiation_round++;
+
+               trace2_region_enter_printf("negotiate_using_fetch", "round",
+                                          the_repository, "%d",
+                                          negotiation_round);
                strbuf_reset(&req_buf);
                write_fetch_command_and_capabilities(&req_buf, server_options);
 
@@ -2099,6 +2162,11 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,
                if (!haves_added || (seen_ack && in_vain >= MAX_IN_VAIN))
                        last_iteration = 1;
 
+               trace2_data_intmax("negotiate_using_fetch", the_repository,
+                                  "haves_added", haves_added);
+               trace2_data_intmax("negotiate_using_fetch", the_repository,
+                                  "in_vain", in_vain);
+
                /* Send request */
                packet_buf_flush(&req_buf);
                if (write_in_full(fd[1], req_buf.buf, req_buf.len) < 0)
@@ -2131,7 +2199,13 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,
                                                 REACH_SCRATCH, 0,
                                                 min_generation))
                        last_iteration = 1;
+               trace2_region_leave_printf("negotiation", "round",
+                                          the_repository, "%d",
+                                          negotiation_round);
        }
+       trace2_region_enter("fetch-pack", "negotiate_using_fetch", the_repository);
+       trace2_data_intmax("negotiate_using_fetch", the_repository,
+                          "total_rounds", negotiation_round);
        clear_common_flag(acked_commits);
        strbuf_release(&req_buf);
 }
diff --git a/fsck.c b/fsck.c
index dd4822ba1be7fe2de830613757110e6d55bfb566..b3da1d68c0b15d8066a5d44ecdfd24cde9fa1ade 100644 (file)
--- a/fsck.c
+++ b/fsck.c
@@ -308,7 +308,7 @@ static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *op
                return -1;
 
        name = fsck_get_object_name(options, &tree->object.oid);
-       if (init_tree_desc_gently(&desc, tree->buffer, tree->size))
+       if (init_tree_desc_gently(&desc, tree->buffer, tree->size, 0))
                return -1;
        while (tree_entry_gently(&desc, &entry)) {
                struct object *obj;
@@ -578,7 +578,7 @@ static int fsck_tree(const struct object_id *tree_oid,
        const char *o_name;
        struct name_stack df_dup_candidates = { NULL };
 
-       if (init_tree_desc_gently(&desc, buffer, size)) {
+       if (init_tree_desc_gently(&desc, buffer, size, TREE_DESC_RAW_MODES)) {
                retval += report(options, tree_oid, OBJ_TREE,
                                 FSCK_MSG_BAD_TREE,
                                 "cannot be parsed as a tree");
diff --git a/fsck.h b/fsck.h
index d07f7a2459e8a264fe8cb2b6457d0ea729bb9e49..6f801e53b1d70ea9e522babbb119b913a7b32b62 100644 (file)
--- a/fsck.h
+++ b/fsck.h
@@ -56,7 +56,6 @@ enum fsck_msg_type {
        FUNC(GITMODULES_PATH, ERROR) \
        FUNC(GITMODULES_UPDATE, ERROR) \
        /* warnings */ \
-       FUNC(BAD_FILEMODE, WARN) \
        FUNC(EMPTY_NAME, WARN) \
        FUNC(FULL_PATHNAME, WARN) \
        FUNC(HAS_DOT, WARN) \
@@ -66,6 +65,7 @@ enum fsck_msg_type {
        FUNC(ZERO_PADDED_FILEMODE, WARN) \
        FUNC(NUL_IN_COMMIT, WARN) \
        /* infos (reported as warnings, but ignored by default) */ \
+       FUNC(BAD_FILEMODE, INFO) \
        FUNC(GITMODULES_PARSE, INFO) \
        FUNC(GITIGNORE_SYMLINK, INFO) \
        FUNC(GITATTRIBUTES_SYMLINK, INFO) \
index 9a62e3a0d2df49e737450bef8b69828a4aa24197..4e51a1c48bc007c9f65c9750bc55933064981270 100644 (file)
@@ -23,6 +23,9 @@
 #include <crtdbg.h>
 #endif
 
+struct strbuf;
+
+
 #define _FILE_OFFSET_BITS 64
 
 
@@ -488,7 +491,7 @@ static inline void extract_id_from_env(const char *env, uid_t *id)
        }
 }
 
-static inline int is_path_owned_by_current_uid(const char *path)
+static inline int is_path_owned_by_current_uid(const char *path, struct strbuf *report)
 {
        struct stat st;
        uid_t euid;
@@ -996,6 +999,28 @@ static inline unsigned long cast_size_t_to_ulong(size_t a)
        return (unsigned long)a;
 }
 
+/*
+ * Limit size of IO chunks, because huge chunks only cause pain.  OS X
+ * 64-bit is buggy, returning EINVAL if len >= INT_MAX; and even in
+ * the absence of bugs, large chunks can result in bad latencies when
+ * you decide to kill the process.
+ *
+ * We pick 8 MiB as our default, but if the platform defines SSIZE_MAX
+ * that is smaller than that, clip it to SSIZE_MAX, as a call to
+ * read(2) or write(2) larger than that is allowed to fail.  As the last
+ * resort, we allow a port to pass via CFLAGS e.g. "-DMAX_IO_SIZE=value"
+ * to override this, if the definition of SSIZE_MAX given by the platform
+ * is broken.
+ */
+#ifndef MAX_IO_SIZE
+# define MAX_IO_SIZE_DEFAULT (8*1024*1024)
+# if defined(SSIZE_MAX) && (SSIZE_MAX < MAX_IO_SIZE_DEFAULT)
+#  define MAX_IO_SIZE SSIZE_MAX
+# else
+#  define MAX_IO_SIZE MAX_IO_SIZE_DEFAULT
+# endif
+#endif
+
 #ifdef HAVE_ALLOCA_H
 # include <alloca.h>
 # define xalloca(size)      (alloca(size))
index 343fe7bccd0d64f0caff1ad5d3f981729a8e5fc9..77e93121bf8c19714b19097b4739b206230677cb 100755 (executable)
@@ -5,6 +5,16 @@
 #
 # Resolve two trees, using enhanced multi-base read-tree.
 
+. git-sh-setup
+
+# Abort if index does not match HEAD
+if ! git diff-index --quiet --cached HEAD --
+then
+    gettextln "Error: Your local changes to the following files would be overwritten by merge"
+    git diff-index --cached --name-only HEAD -- | sed -e 's/^/    /'
+    exit 2
+fi
+
 # The first parameters up to -- are merge bases; the rest are heads.
 bases= head= remotes= sep_seen=
 for arg
index 1835487ab2aeed33a4aa3e6dbde84df7ae7e6d1b..e66eb3d9bad7cf627d5ed35e13e32dafb556d5cd 100755 (executable)
@@ -3560,23 +3560,6 @@ sub parse_commit_text {
                $title =~ s/^    //;
                if ($title ne "") {
                        $co{'title'} = chop_str($title, 80, 5);
-                       # remove leading stuff of merges to make the interesting part visible
-                       if (length($title) > 50) {
-                               $title =~ s/^Automatic //;
-                               $title =~ s/^merge (of|with) /Merge ... /i;
-                               if (length($title) > 50) {
-                                       $title =~ s/(http|rsync):\/\///;
-                               }
-                               if (length($title) > 50) {
-                                       $title =~ s/(master|www|rsync)\.//;
-                               }
-                               if (length($title) > 50) {
-                                       $title =~ s/kernel.org:?//;
-                               }
-                               if (length($title) > 50) {
-                                       $title =~ s/\/pub\/scm//;
-                               }
-                       }
                        $co{'title_short'} = chop_str($title, 50, 5);
                        last;
                }
diff --git a/help.c b/help.c
index 41c41c2aa11757645be53662b6d42011d93b00e2..991e33f8a6eb1950cff6fa9f9fbaa2a4909d640b 100644 (file)
--- a/help.c
+++ b/help.c
@@ -38,19 +38,30 @@ static struct category_description main_categories[] = {
        { CAT_plumbinginterrogators, N_("Low-level Commands / Interrogators") },
        { CAT_synchingrepositories, N_("Low-level Commands / Syncing Repositories") },
        { CAT_purehelpers, N_("Low-level Commands / Internal Helpers") },
+       { CAT_userinterfaces, N_("User-facing repository, command and file interfaces") },
+       { CAT_developerinterfaces, N_("Developer-facing file file formats, protocols and interfaces") },
        { 0, NULL }
 };
 
 static const char *drop_prefix(const char *name, uint32_t category)
 {
        const char *new_name;
-
-       if (skip_prefix(name, "git-", &new_name))
-               return new_name;
-       if (category == CAT_guide && skip_prefix(name, "git", &new_name))
+       const char *prefix;
+
+       switch (category) {
+       case CAT_guide:
+       case CAT_userinterfaces:
+       case CAT_developerinterfaces:
+               prefix = "git";
+               break;
+       default:
+               prefix = "git-";
+               break;
+       }
+       if (skip_prefix(name, prefix, &new_name))
                return new_name;
-       return name;
 
+       return name;
 }
 
 static void extract_cmds(struct cmdname_help **p_cmds, uint32_t mask)
@@ -426,6 +437,26 @@ void list_guides_help(void)
        putchar('\n');
 }
 
+void list_user_interfaces_help(void)
+{
+       struct category_description catdesc[] = {
+               { CAT_userinterfaces, N_("User-facing repository, command and file interfaces:") },
+               { 0, NULL }
+       };
+       print_cmd_by_category(catdesc, NULL);
+       putchar('\n');
+}
+
+void list_developer_interfaces_help(void)
+{
+       struct category_description catdesc[] = {
+               { CAT_developerinterfaces, N_("File formats, protocols and other developer interfaces:") },
+               { 0, NULL }
+       };
+       print_cmd_by_category(catdesc, NULL);
+       putchar('\n');
+}
+
 static int get_alias(const char *var, const char *value, void *data)
 {
        struct string_list *list = data;
diff --git a/help.h b/help.h
index 971a3ad855acdc2d02697993ed8a7730626b8572..af073a7a0263e7bdacf6dd01f13235b2a655f360 100644 (file)
--- a/help.h
+++ b/help.h
@@ -22,6 +22,8 @@ static inline void mput_char(char c, unsigned int num)
 void list_common_cmds_help(void);
 void list_all_cmds_help(int show_external_commands, int show_aliases);
 void list_guides_help(void);
+void list_user_interfaces_help(void);
+void list_developer_interfaces_help(void);
 
 void list_all_main_cmds(struct string_list *list);
 void list_all_other_cmds(struct string_list *list);
diff --git a/hook.c b/hook.c
index d113ee7faaec8d7c6d78c79fc0d4ea9d0d229e85..a493939a4fc5901d85a9ad08ca10669641598389 100644 (file)
--- a/hook.c
+++ b/hook.c
@@ -62,9 +62,6 @@ static int pick_next_hook(struct child_process *cp,
        strvec_push(&cp->args, hook_path);
        strvec_pushv(&cp->args, hook_cb->options->args.v);
 
-       /* Provide context for errors if necessary */
-       *pp_task_cb = (char *)hook_path;
-
        /*
         * This pick_next_hook() will be called again, we're only
         * running one hook, so indicate that no more work will be
@@ -80,13 +77,9 @@ static int notify_start_failure(struct strbuf *out,
                                void *pp_task_cp)
 {
        struct hook_cb_data *hook_cb = pp_cb;
-       const char *hook_path = pp_task_cp;
 
        hook_cb->rc |= 1;
 
-       strbuf_addf(out, _("Couldn't start hook '%s'\n"),
-                   hook_path);
-
        return 1;
 }
 
diff --git a/ident.c b/ident.c
index 89ca5b47008ee50749744914dd80a009d9adcaff..7f66beda428ced55235fa9507e68cfa90cb72209 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -8,6 +8,7 @@
 #include "cache.h"
 #include "config.h"
 #include "date.h"
+#include "mailmap.h"
 
 static struct strbuf git_default_name = STRBUF_INIT;
 static struct strbuf git_default_email = STRBUF_INIT;
@@ -346,6 +347,79 @@ person_only:
        return 0;
 }
 
+/*
+ * Returns the difference between the new and old length of the ident line.
+ */
+static ssize_t rewrite_ident_line(const char *person, size_t len,
+                                  struct strbuf *buf,
+                                  struct string_list *mailmap)
+{
+       size_t namelen, maillen;
+       const char *name;
+       const char *mail;
+       struct ident_split ident;
+
+       if (split_ident_line(&ident, person, len))
+               return 0;
+
+       mail = ident.mail_begin;
+       maillen = ident.mail_end - ident.mail_begin;
+       name = ident.name_begin;
+       namelen = ident.name_end - ident.name_begin;
+
+       if (map_user(mailmap, &mail, &maillen, &name, &namelen)) {
+               struct strbuf namemail = STRBUF_INIT;
+               size_t newlen;
+
+               strbuf_addf(&namemail, "%.*s <%.*s>",
+                           (int)namelen, name, (int)maillen, mail);
+
+               strbuf_splice(buf, ident.name_begin - buf->buf,
+                             ident.mail_end - ident.name_begin + 1,
+                             namemail.buf, namemail.len);
+               newlen = namemail.len;
+
+               strbuf_release(&namemail);
+
+               return newlen - (ident.mail_end - ident.name_begin);
+       }
+
+       return 0;
+}
+
+void apply_mailmap_to_header(struct strbuf *buf, const char **header,
+                              struct string_list *mailmap)
+{
+       size_t buf_offset = 0;
+
+       if (!mailmap)
+               return;
+
+       for (;;) {
+               const char *person, *line;
+               size_t i;
+               int found_header = 0;
+
+               line = buf->buf + buf_offset;
+               if (!*line || *line == '\n')
+                       return; /* End of headers */
+
+               for (i = 0; header[i]; i++)
+                       if (skip_prefix(line, header[i], &person)) {
+                               const char *endp = strchrnul(person, '\n');
+                               found_header = 1;
+                               buf_offset += endp - line;
+                               buf_offset += rewrite_ident_line(person, endp - person, buf, mailmap);
+                               break;
+                       }
+
+               if (!found_header) {
+                       buf_offset = strchrnul(line, '\n') - buf->buf;
+                       if (buf->buf[buf_offset] == '\n')
+                               buf_offset++;
+               }
+       }
+}
 
 static void ident_env_hint(enum want_ident whose_ident)
 {
index ad041061695223566ebfb2b9dacfc5aa7d215733..748924a69ba3262088c444c0145bc407750daa0a 100644 (file)
@@ -10,8 +10,8 @@ static int unclean(struct merge_options *opt, struct tree *head)
        struct strbuf sb = STRBUF_INIT;
 
        if (head && repo_index_has_changes(opt->repo, head, &sb)) {
-               fprintf(stderr, _("Your local changes to the following files would be overwritten by merge:\n  %s"),
-                   sb.buf);
+               error(_("Your local changes to the following files would be overwritten by merge:\n  %s"),
+                     sb.buf);
                strbuf_release(&sb);
                return -1;
        }
index 8b7de0fbd8e87118aad09fad3be8ef77cd0bdbbc..e634a7624af983b742ab4c87d7cc30177e7b616f 100644 (file)
@@ -387,8 +387,24 @@ struct merge_options_internal {
 
        /* call_depth: recursion level counter for merging merge bases */
        int call_depth;
+
+       /* field that holds submodule conflict information */
+       struct string_list conflicted_submodules;
+};
+
+struct conflicted_submodule_item {
+       char *abbrev;
+       int flag;
 };
 
+static void conflicted_submodule_item_free(void *util, const char *str)
+{
+       struct conflicted_submodule_item *item = util;
+
+       free(item->abbrev);
+       free(item);
+}
+
 struct version_info {
        struct object_id oid;
        unsigned short mode;
@@ -517,6 +533,7 @@ enum conflict_and_info_types {
        CONFLICT_SUBMODULE_NOT_INITIALIZED,
        CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE,
        CONFLICT_SUBMODULE_MAY_HAVE_REWINDS,
+       CONFLICT_SUBMODULE_NULL_MERGE_BASE,
 
        /* Keep this entry _last_ in the list */
        NB_CONFLICT_TYPES,
@@ -570,6 +587,8 @@ static const char *type_short_descriptions[] = {
                "CONFLICT (submodule history not available)",
        [CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] =
                "CONFLICT (submodule may have rewinds)",
+       [CONFLICT_SUBMODULE_NULL_MERGE_BASE] =
+               "CONFLICT (submodule lacks merge base)"
 };
 
 struct logical_conflict_info {
@@ -686,6 +705,9 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,
 
        mem_pool_discard(&opti->pool, 0);
 
+       string_list_clear_func(&opti->conflicted_submodules,
+                                       conflicted_submodule_item_free);
+
        /* Clean out callback_data as well. */
        FREE_AND_NULL(renames->callback_data);
        renames->callback_data_nr = renames->callback_data_alloc = 0;
@@ -1744,24 +1766,32 @@ static int merge_submodule(struct merge_options *opt,
 
        int i;
        int search = !opt->priv->call_depth;
+       int sub_not_initialized = 1;
+       int sub_flag = CONFLICT_SUBMODULE_FAILED_TO_MERGE;
 
        /* store fallback answer in result in case we fail */
        oidcpy(result, opt->priv->call_depth ? o : a);
 
        /* we can not handle deletion conflicts */
-       if (is_null_oid(o))
-               return 0;
-       if (is_null_oid(a))
-               return 0;
-       if (is_null_oid(b))
-               return 0;
+       if (is_null_oid(a) || is_null_oid(b))
+               BUG("submodule deleted on one side; this should be handled outside of merge_submodule()");
 
-       if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {
+       if ((sub_not_initialized = repo_submodule_init(&subrepo,
+               opt->repo, path, null_oid()))) {
                path_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0,
                         path, NULL, NULL, NULL,
                         _("Failed to merge submodule %s (not checked out)"),
                         path);
-               return 0;
+               sub_flag = CONFLICT_SUBMODULE_NOT_INITIALIZED;
+               goto cleanup;
+       }
+
+       if (is_null_oid(o)) {
+               path_msg(opt, CONFLICT_SUBMODULE_NULL_MERGE_BASE, 0,
+                        path, NULL, NULL, NULL,
+                        _("Failed to merge submodule %s (no merge base)"),
+                        path);
+               goto cleanup;
        }
 
        if (!(commit_o = lookup_commit_reference(&subrepo, o)) ||
@@ -1771,6 +1801,7 @@ static int merge_submodule(struct merge_options *opt,
                         path, NULL, NULL, NULL,
                         _("Failed to merge submodule %s (commits not present)"),
                         path);
+               sub_flag = CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE;
                goto cleanup;
        }
 
@@ -1849,7 +1880,23 @@ static int merge_submodule(struct merge_options *opt,
 
        object_array_clear(&merges);
 cleanup:
-       repo_clear(&subrepo);
+       if (!opt->priv->call_depth && !ret) {
+               struct string_list *csub = &opt->priv->conflicted_submodules;
+               struct conflicted_submodule_item *util;
+               const char *abbrev;
+
+               util = xmalloc(sizeof(*util));
+               util->flag = sub_flag;
+               util->abbrev = NULL;
+               if (!sub_not_initialized) {
+                       abbrev = repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV);
+                       util->abbrev = xstrdup(abbrev);
+               }
+               string_list_append(csub, path)->util = util;
+       }
+
+       if (!sub_not_initialized)
+               repo_clear(&subrepo);
        return ret;
 }
 
@@ -4434,6 +4481,63 @@ static int record_conflicted_index_entries(struct merge_options *opt)
        return errs;
 }
 
+static void print_submodule_conflict_suggestion(struct string_list *csub) {
+       struct string_list_item *item;
+       struct strbuf msg = STRBUF_INIT;
+       struct strbuf tmp = STRBUF_INIT;
+       struct strbuf subs = STRBUF_INIT;
+
+       if (!csub->nr)
+               return;
+
+       strbuf_add_separated_string_list(&subs, " ", csub);
+       for_each_string_list_item(item, csub) {
+               struct conflicted_submodule_item *util = item->util;
+
+               /*
+                * NEEDSWORK: The steps to resolve these errors deserve a more
+                * detailed explanation than what is currently printed below.
+                */
+               if (util->flag == CONFLICT_SUBMODULE_NOT_INITIALIZED ||
+                   util->flag == CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE)
+                       continue;
+
+               /*
+                * TRANSLATORS: This is a line of advice to resolve a merge
+                * conflict in a submodule. The first argument is the submodule
+                * name, and the second argument is the abbreviated id of the
+                * commit that needs to be merged.  For example:
+                *  - go to submodule (mysubmodule), and either merge commit abc1234"
+                */
+               strbuf_addf(&tmp, _(" - go to submodule (%s), and either merge commit %s\n"
+                                   "   or update to an existing commit which has merged those changes\n"),
+                           item->string, util->abbrev);
+       }
+
+       /*
+        * TRANSLATORS: This is a detailed message for resolving submodule
+        * conflicts.  The first argument is string containing one step per
+        * submodule.  The second is a space-separated list of submodule names.
+        */
+       strbuf_addf(&msg,
+                   _("Recursive merging with submodules currently only supports trivial cases.\n"
+                     "Please manually handle the merging of each conflicted submodule.\n"
+                     "This can be accomplished with the following steps:\n"
+                     "%s"
+                     " - come back to superproject and run:\n\n"
+                     "      git add %s\n\n"
+                     "   to record the above merge or update\n"
+                     " - resolve any other conflicts in the superproject\n"
+                     " - commit the resulting index in the superproject\n"),
+                   tmp.buf, subs.buf);
+
+       printf("%s", msg.buf);
+
+       strbuf_release(&subs);
+       strbuf_release(&tmp);
+       strbuf_release(&msg);
+}
+
 void merge_display_update_messages(struct merge_options *opt,
                                   int detailed,
                                   struct merge_result *result)
@@ -4483,6 +4587,8 @@ void merge_display_update_messages(struct merge_options *opt,
        }
        string_list_clear(&olist, 0);
 
+       print_submodule_conflict_suggestion(&opti->conflicted_submodules);
+
        /* Also include needed rename limit adjustment now */
        diff_warn_rename_limit("merge.renamelimit",
                               opti->renames.needed_limit, 0);
@@ -4536,6 +4642,8 @@ void merge_switch_to_result(struct merge_options *opt,
                if (checkout(opt, head, result->tree)) {
                        /* failure to function */
                        result->clean = -1;
+                       merge_finalize(opt, result);
+                       trace2_region_leave("merge", "checkout", opt->repo);
                        return;
                }
                trace2_region_leave("merge", "checkout", opt->repo);
@@ -4546,6 +4654,9 @@ void merge_switch_to_result(struct merge_options *opt,
                        /* failure to function */
                        opt->priv = NULL;
                        result->clean = -1;
+                       merge_finalize(opt, result);
+                       trace2_region_leave("merge", "record_conflicted",
+                                           opt->repo);
                        return;
                }
                opt->priv = NULL;
@@ -4679,6 +4790,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
        trace2_region_enter("merge", "allocate/init", opt->repo);
        if (opt->priv) {
                clear_or_reinit_internal_opts(opt->priv, 1);
+               string_list_init_nodup(&opt->priv->conflicted_submodules);
                trace2_region_leave("merge", "allocate/init", opt->repo);
                return;
        }
diff --git a/mergesort.c b/mergesort.c
deleted file mode 100644 (file)
index bd9c6ef..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-#include "cache.h"
-#include "mergesort.h"
-
-/* Combine two sorted lists.  Take from `list` on equality. */
-static void *llist_merge(void *list, void *other,
-                        void *(*get_next_fn)(const void *),
-                        void (*set_next_fn)(void *, void *),
-                        int (*compare_fn)(const void *, const void *))
-{
-       void *result = list, *tail;
-
-       if (compare_fn(list, other) > 0) {
-               result = other;
-               goto other;
-       }
-       for (;;) {
-               do {
-                       tail = list;
-                       list = get_next_fn(list);
-                       if (!list) {
-                               set_next_fn(tail, other);
-                               return result;
-                       }
-               } while (compare_fn(list, other) <= 0);
-               set_next_fn(tail, other);
-       other:
-               do {
-                       tail = other;
-                       other = get_next_fn(other);
-                       if (!other) {
-                               set_next_fn(tail, list);
-                               return result;
-                       }
-               } while (compare_fn(list, other) > 0);
-               set_next_fn(tail, list);
-       }
-}
-
-/*
- * Perform an iterative mergesort using an array of sublists.
- *
- * n is the number of items.
- * ranks[i] is undefined if n & 2^i == 0, and assumed empty.
- * ranks[i] contains a sublist of length 2^i otherwise.
- *
- * The number of bits in a void pointer limits the number of objects
- * that can be created, and thus the number of array elements necessary
- * to be able to sort any valid list.
- *
- * Adding an item to this array is like incrementing a binary number;
- * positional values for set bits correspond to sublist lengths.
- */
-void *llist_mergesort(void *list,
-                     void *(*get_next_fn)(const void *),
-                     void (*set_next_fn)(void *, void *),
-                     int (*compare_fn)(const void *, const void *))
-{
-       void *ranks[bitsizeof(void *)];
-       size_t n = 0;
-       int i;
-
-       while (list) {
-               void *next = get_next_fn(list);
-               if (next)
-                       set_next_fn(list, NULL);
-               for (i = 0; n & ((size_t)1 << i); i++)
-                       list = llist_merge(ranks[i], list, get_next_fn,
-                                          set_next_fn, compare_fn);
-               n++;
-               ranks[i] = list;
-               list = next;
-       }
-
-       for (i = 0; n; i++, n >>= 1) {
-               if (!(n & 1))
-                       continue;
-               if (list)
-                       list = llist_merge(ranks[i], list, get_next_fn,
-                                          set_next_fn, compare_fn);
-               else
-                       list = ranks[i];
-       }
-       return list;
-}
index 644cff1f9640f4f3a146d7217a8deab34282ff53..7c36f08bd5f9668521ff993986b757b9766ec1c0 100644 (file)
 #ifndef MERGESORT_H
 #define MERGESORT_H
 
+/* Combine two sorted lists.  Take from `list` on equality. */
+#define DEFINE_LIST_MERGE_INTERNAL(name, type)                         \
+static type *name##__merge(type *list, type *other,                    \
+                          int (*compare_fn)(const type *, const type *))\
+{                                                                      \
+       type *result = list, *tail;                                     \
+       int prefer_list = compare_fn(list, other) <= 0;                 \
+                                                                       \
+       if (!prefer_list) {                                             \
+               result = other;                                         \
+               SWAP(list, other);                                      \
+       }                                                               \
+       for (;;) {                                                      \
+               do {                                                    \
+                       tail = list;                                    \
+                       list = name##__get_next(list);                  \
+                       if (!list) {                                    \
+                               name##__set_next(tail, other);          \
+                               return result;                          \
+                       }                                               \
+               } while (compare_fn(list, other) < prefer_list);        \
+               name##__set_next(tail, other);                          \
+               prefer_list ^= 1;                                       \
+               SWAP(list, other);                                      \
+       }                                                               \
+}
+
 /*
- * Sort linked list in place.
- * - get_next_fn() returns the next element given an element of a linked list.
- * - set_next_fn() takes two elements A and B, and makes B the "next" element
- *   of A on the list.
- * - compare_fn() takes two elements A and B, and returns negative, 0, positive
- *   as the same sign as "subtracting" B from A.
+ * Perform an iterative mergesort using an array of sublists.
+ *
+ * n is the number of items.
+ * ranks[i] is undefined if n & 2^i == 0, and assumed empty.
+ * ranks[i] contains a sublist of length 2^i otherwise.
+ *
+ * The number of bits in a void pointer limits the number of objects
+ * that can be created, and thus the number of array elements necessary
+ * to be able to sort any valid list.
+ *
+ * Adding an item to this array is like incrementing a binary number;
+ * positional values for set bits correspond to sublist lengths.
  */
-void *llist_mergesort(void *list,
-                     void *(*get_next_fn)(const void *),
-                     void (*set_next_fn)(void *, void *),
-                     int (*compare_fn)(const void *, const void *));
+#define DEFINE_LIST_SORT_INTERNAL(scope, name, type)                   \
+scope void name(type **listp,                                          \
+               int (*compare_fn)(const type *, const type *))          \
+{                                                                      \
+       type *list = *listp;                                            \
+       type *ranks[bitsizeof(type *)];                                 \
+       size_t n = 0;                                                   \
+                                                                       \
+       if (!list)                                                      \
+               return;                                                 \
+                                                                       \
+       for (;;) {                                                      \
+               int i;                                                  \
+               size_t m;                                               \
+               type *next = name##__get_next(list);                    \
+               if (next)                                               \
+                       name##__set_next(list, NULL);                   \
+               for (i = 0, m = n;; i++, m >>= 1) {                     \
+                       if (m & 1) {                                    \
+                               list = name##__merge(ranks[i], list,    \
+                                                   compare_fn);        \
+                       } else if (next) {                              \
+                               break;                                  \
+                       } else if (!m) {                                \
+                               *listp = list;                          \
+                               return;                                 \
+                       }                                               \
+               }                                                       \
+               n++;                                                    \
+               ranks[i] = list;                                        \
+               list = next;                                            \
+       }                                                               \
+}
+
+#define DECLARE_LIST_SORT(scope, name, type)                   \
+scope void name(type **listp,                                  \
+               int (*compare_fn)(const type *, const type *))
+
+#define DEFINE_LIST_SORT_DEBUG(scope, name, type, next_member, \
+                              on_get_next, on_set_next)        \
+                                                               \
+static inline type *name##__get_next(const type *elem)         \
+{                                                              \
+       on_get_next;                                            \
+       return elem->next_member;                               \
+}                                                              \
+                                                               \
+static inline void name##__set_next(type *elem, type *next)    \
+{                                                              \
+       on_set_next;                                            \
+       elem->next_member = next;                               \
+}                                                              \
+                                                               \
+DEFINE_LIST_MERGE_INTERNAL(name, type)                         \
+DEFINE_LIST_SORT_INTERNAL(scope, name, type)                   \
+DECLARE_LIST_SORT(scope, name, type)
+
+#define DEFINE_LIST_SORT(scope, name, type, next_member) \
+DEFINE_LIST_SORT_DEBUG(scope, name, type, next_member, (void)0, (void)0)
 
 #endif
index f770b8fe244c8ddd9dc349125a8e8e0ac0325574..06937acbf5497115c9ba4d9a2377634b1885db7e 100644 (file)
@@ -29,8 +29,8 @@
 ################################################################################
 
 debug_print () {
-       # Send message to stderr if global variable GIT_MERGETOOL_VIMDIFF is set
-       # to "true"
+       # Send message to stderr if global variable GIT_MERGETOOL_VIMDIFF_DEBUG
+       # is set.
 
        if test -n "$GIT_MERGETOOL_VIMDIFF_DEBUG"
        then
@@ -66,11 +66,6 @@ gen_cmd_aux () {
        debug_print "LAYOUT    : $LAYOUT"
        debug_print "CMD       : $CMD"
 
-       if test -z "$CMD"
-       then
-               CMD="echo" # vim "nop" operator
-       fi
-
        start=0
        end=${#LAYOUT}
 
@@ -144,11 +139,10 @@ gen_cmd_aux () {
 
        # Step 2:
        #
-       # Search for all valid separators ("+", "/" or ",") which are *not*
+       # Search for all valid separators ("/" or ",") which are *not*
        # inside parenthesis. Save the index at which each of them makes the
        # first appearance.
 
-       index_new_tab=""
        index_horizontal_split=""
        index_vertical_split=""
 
@@ -182,14 +176,7 @@ gen_cmd_aux () {
                then
                        current=$c
 
-                       if test "$current" = "+"
-                       then
-                               if test -z "$index_new_tab"
-                               then
-                                       index_new_tab=$i
-                               fi
-
-                       elif test "$current" = "/"
+                       if test "$current" = "/"
                        then
                                if test -z "$index_horizontal_split"
                                then
@@ -219,14 +206,7 @@ gen_cmd_aux () {
 
        terminate="false"
 
-       if ! test -z "$index_new_tab"
-       then
-               before="-tabnew"
-               after="tabnext"
-               index=$index_new_tab
-               terminate="true"
-
-       elif ! test -z "$index_horizontal_split"
+       if ! test -z "$index_horizontal_split"
        then
                before="leftabove split"
                after="wincmd j"
@@ -333,25 +313,31 @@ gen_cmd () {
 
        # Obtain the first part of vim "-c" option to obtain the desired layout
 
-       CMD=$(gen_cmd_aux "$LAYOUT")
-
-
-       # Adjust the just obtained script depending on whether more than one
-       # windows are visible or not
+       CMD=
+       oldIFS=$IFS
+       IFS=+
+       for tab in $LAYOUT
+       do
+               if test -z "$CMD"
+               then
+                       CMD="echo" # vim "nop" operator
+               else
+                       CMD="$CMD | tabnew"
+               fi
 
-       if echo "$LAYOUT" | grep ",\|/" >/dev/null
-       then
-               CMD="$CMD | tabdo windo diffthis"
-       else
-               CMD="$CMD | bufdo diffthis"
-       fi
+               # If this is a single window diff with all the buffers
+               if ! echo "$tab" | grep ",\|/" >/dev/null
+               then
+                       CMD="$CMD | silent execute 'bufdo diffthis'"
+               fi
 
+               CMD=$(gen_cmd_aux "$tab" "$CMD")
+       done
+       IFS=$oldIFS
 
-       # Add an extra "-c" option to move to the first tab (notice that we
-       # can't simply append the command to the previous "-c" string as
-       # explained here: https://github.com/vim/vim/issues/9076
+       CMD="$CMD | execute 'tabdo windo diffthis'"
 
-       FINAL_CMD="-c \"$CMD\" -c \"tabfirst\""
+       FINAL_CMD="-c \"set hidden diffopt-=hiddenoff | $CMD | tabfirst\""
 }
 
 
@@ -555,22 +541,22 @@ run_unit_tests () {
        TEST_CASE_15="  ((  (LOCAL , BASE , REMOTE) / MERGED))   +(BASE)   , LOCAL+ BASE , REMOTE+ (((LOCAL / BASE / REMOTE)) ,    MERGED   )  "
        TEST_CASE_16="LOCAL,BASE,REMOTE / MERGED + BASE,LOCAL + BASE,REMOTE + (LOCAL / BASE / REMOTE),MERGED"
 
-       EXPECTED_CMD_01="-c \"echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabdo windo diffthis\" -c \"tabfirst\""
-       EXPECTED_CMD_02="-c \"echo | leftabove vertical split | 1b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\""
-       EXPECTED_CMD_03="-c \"echo | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 4b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\""
-       EXPECTED_CMD_04="-c \"echo | 4b | bufdo diffthis\" -c \"tabfirst\""
-       EXPECTED_CMD_05="-c \"echo | leftabove split | 1b | wincmd j | leftabove split | 4b | wincmd j | 3b | tabdo windo diffthis\" -c \"tabfirst\""
-       EXPECTED_CMD_06="-c \"echo | leftabove vertical split | leftabove split | 1b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
-       EXPECTED_CMD_07="-c \"echo | leftabove vertical split | 4b | wincmd l | leftabove split | 1b | wincmd j | 3b | tabdo windo diffthis\" -c \"tabfirst\""
-       EXPECTED_CMD_08="-c \"echo | leftabove split | leftabove vertical split | 1b | wincmd l | 3b | wincmd j | 4b | tabdo windo diffthis\" -c \"tabfirst\""
-       EXPECTED_CMD_09="-c \"echo | leftabove split | 4b | wincmd j | leftabove vertical split | 1b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\""
-       EXPECTED_CMD_10="-c \"echo | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
-       EXPECTED_CMD_11="-c \"echo | -tabnew | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 1b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnext | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
-       EXPECTED_CMD_12="-c \"echo | leftabove vertical split | leftabove split | leftabove vertical split | 1b | wincmd l | 3b | wincmd j | 2b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
-       EXPECTED_CMD_13="-c \"echo | leftabove vertical split | leftabove split | leftabove vertical split | 1b | wincmd l | 3b | wincmd j | 2b | wincmd l | leftabove vertical split | leftabove split | 1b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
-       EXPECTED_CMD_14="-c \"echo | -tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnext | leftabove vertical split | 2b | wincmd l | 1b | tabdo windo diffthis\" -c \"tabfirst\""
-       EXPECTED_CMD_15="-c \"echo | -tabnew | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 1b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnext | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
-       EXPECTED_CMD_16="-c \"echo | -tabnew | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 1b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnext | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
+       EXPECTED_CMD_01="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | execute 'tabdo windo diffthis' | tabfirst\""
+       EXPECTED_CMD_02="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | 1b | wincmd l | 3b | execute 'tabdo windo diffthis' | tabfirst\""
+       EXPECTED_CMD_03="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 4b | wincmd l | 3b | execute 'tabdo windo diffthis' | tabfirst\""
+       EXPECTED_CMD_04="-c \"set hidden diffopt-=hiddenoff | echo | silent execute 'bufdo diffthis' | 4b | execute 'tabdo windo diffthis' | tabfirst\""
+       EXPECTED_CMD_05="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | 1b | wincmd j | leftabove split | 4b | wincmd j | 3b | execute 'tabdo windo diffthis' | tabfirst\""
+       EXPECTED_CMD_06="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | leftabove split | 1b | wincmd j | 3b | wincmd l | 4b | execute 'tabdo windo diffthis' | tabfirst\""
+       EXPECTED_CMD_07="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | 4b | wincmd l | leftabove split | 1b | wincmd j | 3b | execute 'tabdo windo diffthis' | tabfirst\""
+       EXPECTED_CMD_08="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | leftabove vertical split | 1b | wincmd l | 3b | wincmd j | 4b | execute 'tabdo windo diffthis' | tabfirst\""
+       EXPECTED_CMD_09="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | 4b | wincmd j | leftabove vertical split | 1b | wincmd l | 3b | execute 'tabdo windo diffthis' | tabfirst\""
+       EXPECTED_CMD_10="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | execute 'tabdo windo diffthis' | tabfirst\""
+       EXPECTED_CMD_11="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnew | leftabove vertical split | 2b | wincmd l | 1b | tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnew | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | execute 'tabdo windo diffthis' | tabfirst\""
+       EXPECTED_CMD_12="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | leftabove split | leftabove vertical split | 1b | wincmd l | 3b | wincmd j | 2b | wincmd l | 4b | execute 'tabdo windo diffthis' | tabfirst\""
+       EXPECTED_CMD_13="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | leftabove split | leftabove vertical split | 1b | wincmd l | 3b | wincmd j | 2b | wincmd l | leftabove vertical split | leftabove split | 1b | wincmd j | 3b | wincmd l | 4b | execute 'tabdo windo diffthis' | tabfirst\""
+       EXPECTED_CMD_14="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | 2b | wincmd l | 3b | tabnew | leftabove vertical split | 2b | wincmd l | 1b | execute 'tabdo windo diffthis' | tabfirst\""
+       EXPECTED_CMD_15="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnew | leftabove vertical split | 2b | wincmd l | 1b | tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnew | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | execute 'tabdo windo diffthis' | tabfirst\""
+       EXPECTED_CMD_16="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnew | leftabove vertical split | 2b | wincmd l | 1b | tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnew | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | execute 'tabdo windo diffthis' | tabfirst\""
 
        EXPECTED_TARGET_01="MERGED"
        EXPECTED_TARGET_02="LOCAL"
@@ -635,9 +621,7 @@ run_unit_tests () {
        cat >expect <<-\EOF
        -f
        -c
-       echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | quit | wincmd l | 2b | wincmd j | 3b | tabdo windo diffthis
-       -c
-       tabfirst
+       set hidden diffopt-=hiddenoff | echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | quit | wincmd l | 2b | wincmd j | 3b | execute 'tabdo windo diffthis' | tabfirst
        lo cal
        ' '
        mer ged
diff --git a/midx.c b/midx.c
index 5f0dd386b0266b6549c85499ab50e964ed868f1d..4e956cacb710c8638ebfa6d2e562167cfd46c283 100644 (file)
--- a/midx.c
+++ b/midx.c
@@ -1053,40 +1053,34 @@ static struct commit **find_commits_for_midx_bitmap(uint32_t *indexed_commits_nr
        return cb.commits;
 }
 
-static int write_midx_bitmap(char *midx_name, unsigned char *midx_hash,
-                            struct write_midx_context *ctx,
-                            const char *refs_snapshot,
+static int write_midx_bitmap(const char *midx_name,
+                            const unsigned char *midx_hash,
+                            struct packing_data *pdata,
+                            struct commit **commits,
+                            uint32_t commits_nr,
+                            uint32_t *pack_order,
                             unsigned flags)
 {
-       struct packing_data pdata;
-       struct pack_idx_entry **index;
-       struct commit **commits = NULL;
-       uint32_t i, commits_nr;
+       int ret, i;
        uint16_t options = 0;
-       char *bitmap_name = xstrfmt("%s-%s.bitmap", midx_name, hash_to_hex(midx_hash));
-       int ret;
-
-       if (!ctx->entries_nr)
-               BUG("cannot write a bitmap without any objects");
+       struct pack_idx_entry **index;
+       char *bitmap_name = xstrfmt("%s-%s.bitmap", midx_name,
+                                       hash_to_hex(midx_hash));
 
        if (flags & MIDX_WRITE_BITMAP_HASH_CACHE)
                options |= BITMAP_OPT_HASH_CACHE;
 
-       prepare_midx_packing_data(&pdata, ctx);
-
-       commits = find_commits_for_midx_bitmap(&commits_nr, refs_snapshot, ctx);
-
        /*
         * Build the MIDX-order index based on pdata.objects (which is already
         * in MIDX order; c.f., 'midx_pack_order_cmp()' for the definition of
         * this order).
         */
-       ALLOC_ARRAY(index, pdata.nr_objects);
-       for (i = 0; i < pdata.nr_objects; i++)
-               index[i] = &pdata.objects[i].idx;
+       ALLOC_ARRAY(index, pdata->nr_objects);
+       for (i = 0; i < pdata->nr_objects; i++)
+               index[i] = &pdata->objects[i].idx;
 
        bitmap_writer_show_progress(flags & MIDX_PROGRESS);
-       bitmap_writer_build_type_index(&pdata, index, pdata.nr_objects);
+       bitmap_writer_build_type_index(pdata, index, pdata->nr_objects);
 
        /*
         * bitmap_writer_finish expects objects in lex order, but pack_order
@@ -1101,16 +1095,16 @@ static int write_midx_bitmap(char *midx_name, unsigned char *midx_hash,
         * happens between bitmap_writer_build_type_index() and
         * bitmap_writer_finish().
         */
-       for (i = 0; i < pdata.nr_objects; i++)
-               index[ctx->pack_order[i]] = &pdata.objects[i].idx;
+       for (i = 0; i < pdata->nr_objects; i++)
+               index[pack_order[i]] = &pdata->objects[i].idx;
 
        bitmap_writer_select_commits(commits, commits_nr, -1);
-       ret = bitmap_writer_build(&pdata);
+       ret = bitmap_writer_build(pdata);
        if (ret < 0)
                goto cleanup;
 
        bitmap_writer_set_checksum(midx_hash);
-       bitmap_writer_finish(index, pdata.nr_objects, bitmap_name, options);
+       bitmap_writer_finish(index, pdata->nr_objects, bitmap_name, options);
 
 cleanup:
        free(index);
@@ -1443,14 +1437,40 @@ static int write_midx_internal(const char *object_dir,
        if (flags & MIDX_WRITE_REV_INDEX &&
            git_env_bool("GIT_TEST_MIDX_WRITE_REV", 0))
                write_midx_reverse_index(midx_name.buf, midx_hash, &ctx);
+
        if (flags & MIDX_WRITE_BITMAP) {
-               if (write_midx_bitmap(midx_name.buf, midx_hash, &ctx,
-                                     refs_snapshot, flags) < 0) {
+               struct packing_data pdata;
+               struct commit **commits;
+               uint32_t commits_nr;
+
+               if (!ctx.entries_nr)
+                       BUG("cannot write a bitmap without any objects");
+
+               prepare_midx_packing_data(&pdata, &ctx);
+
+               commits = find_commits_for_midx_bitmap(&commits_nr, refs_snapshot, &ctx);
+
+               /*
+                * The previous steps translated the information from
+                * 'entries' into information suitable for constructing
+                * bitmaps. We no longer need that array, so clear it to
+                * reduce memory pressure.
+                */
+               FREE_AND_NULL(ctx.entries);
+               ctx.entries_nr = 0;
+
+               if (write_midx_bitmap(midx_name.buf, midx_hash, &pdata,
+                                     commits, commits_nr, ctx.pack_order,
+                                     flags) < 0) {
                        error(_("could not write multi-pack bitmap"));
                        result = 1;
                        goto cleanup;
                }
        }
+       /*
+        * NOTE: Do not use ctx.entries beyond this point, since it might
+        * have been freed in the previous if block.
+        */
 
        if (ctx.m)
                close_object_store(the_repository->objects);
index c43375bd344f22473adcd7cac09c9f1572e27079..4fcfaed428f27ae7d9f95b925e035fd4f1b75cbb 100644 (file)
@@ -683,7 +683,7 @@ static void write_hash_cache(struct hashfile *f,
        }
 }
 
-void bitmap_writer_set_checksum(unsigned char *sha1)
+void bitmap_writer_set_checksum(const unsigned char *sha1)
 {
        hashcpy(writer.pack_checksum, sha1);
 }
index 3d3ddd77345002f3075047b1f0bf545a4e229157..f3a57ca065f579948426e1b68545a3abcf6fffdb 100644 (file)
@@ -75,7 +75,7 @@ int bitmap_has_oid_in_uninteresting(struct bitmap_index *, const struct object_i
 off_t get_disk_usage_from_bitmap(struct bitmap_index *, struct rev_info *);
 
 void bitmap_writer_show_progress(int show);
-void bitmap_writer_set_checksum(unsigned char *sha1);
+void bitmap_writer_set_checksum(const unsigned char *sha1);
 void bitmap_writer_build_type_index(struct packing_data *to_pack,
                                    struct pack_idx_entry **index,
                                    uint32_t index_nr);
index 74f4eae668d5555f99fa4d6af0f486ddc94b5447..4974e75eb4d0e9d002fc70a87e5bb4526e1e49f7 100644 (file)
@@ -22,7 +22,7 @@
  *
  *   - pack position refers to an object's position within a non-existent pack
  *     described by the MIDX. The pack structure is described in
- *     Documentation/technical/pack-format.txt.
+ *     gitformat-pack(5).
  *
  *     It is effectively a concatanation of all packs in the MIDX (ordered by
  *     their numeric ID within the MIDX) in their original order within each
index ed69fe457b5cc746c3bd82791e92e94f22c3e149..a41887c94458669bda7285f50711b90ec53855bb 100644 (file)
@@ -941,20 +941,10 @@ unsigned long repo_approximate_object_count(struct repository *r)
        return r->objects->approximate_object_count;
 }
 
-static void *get_next_packed_git(const void *p)
-{
-       return ((const struct packed_git *)p)->next;
-}
-
-static void set_next_packed_git(void *p, void *next)
-{
-       ((struct packed_git *)p)->next = next;
-}
+DEFINE_LIST_SORT(static, sort_packs, struct packed_git, next);
 
-static int sort_pack(const void *a_, const void *b_)
+static int sort_pack(const struct packed_git *a, const struct packed_git *b)
 {
-       const struct packed_git *a = a_;
-       const struct packed_git *b = b_;
        int st;
 
        /*
@@ -981,9 +971,7 @@ static int sort_pack(const void *a_, const void *b_)
 
 static void rearrange_packed_git(struct repository *r)
 {
-       r->objects->packed_git = llist_mergesort(
-               r->objects->packed_git, get_next_packed_git,
-               set_next_packed_git, sort_pack);
+       sort_packs(&r->objects->packed_git, sort_pack);
 }
 
 static void prepare_packed_git_mru(struct repository *r)
@@ -2229,7 +2217,17 @@ static int add_promisor_object(const struct object_id *oid,
                               void *set_)
 {
        struct oidset *set = set_;
-       struct object *obj = parse_object(the_repository, oid);
+       struct object *obj;
+       int we_parsed_object;
+
+       obj = lookup_object(the_repository, oid);
+       if (obj && obj->parsed) {
+               we_parsed_object = 0;
+       } else {
+               we_parsed_object = 1;
+               obj = parse_object(the_repository, oid);
+       }
+
        if (!obj)
                return 1;
 
@@ -2243,7 +2241,7 @@ static int add_promisor_object(const struct object_id *oid,
                struct tree *tree = (struct tree *)obj;
                struct tree_desc desc;
                struct name_entry entry;
-               if (init_tree_desc_gently(&desc, tree->buffer, tree->size))
+               if (init_tree_desc_gently(&desc, tree->buffer, tree->size, 0))
                        /*
                         * Error messages are given when packs are
                         * verified, so do not print any here.
@@ -2251,7 +2249,8 @@ static int add_promisor_object(const struct object_id *oid,
                        return 0;
                while (tree_entry_gently(&desc, &entry))
                        oidset_insert(set, &entry.oid);
-               free_tree_buffer(tree);
+               if (we_parsed_object)
+                       free_tree_buffer(tree);
        } else if (obj->type == OBJ_COMMIT) {
                struct commit *commit = (struct commit *) obj;
                struct commit_list *parents = commit->parents;
index 84ad9c73cfb50a9fd34eed3b5e68fae522c796fe..46e77a85fee9d86e6e16e9f466ecfc3db893fd16 100644 (file)
@@ -759,3 +759,92 @@ int match_pathspec_attrs(struct index_state *istate,
 
        return 1;
 }
+
+int pathspec_needs_expanded_index(struct index_state *istate,
+                                 const struct pathspec *pathspec)
+{
+       unsigned int i, pos;
+       int res = 0;
+       char *skip_worktree_seen = NULL;
+
+       /*
+        * If index is not sparse, no index expansion is needed.
+        */
+       if (!istate->sparse_index)
+               return 0;
+
+       /*
+        * When using a magic pathspec, assume for the sake of simplicity that
+        * the index needs to be expanded to match all matchable files.
+        */
+       if (pathspec->magic)
+               return 1;
+
+       for (i = 0; i < pathspec->nr; i++) {
+               struct pathspec_item item = pathspec->items[i];
+
+               /*
+                * If the pathspec item has a wildcard, the index should be expanded
+                * if the pathspec has the possibility of matching a subset of entries inside
+                * of a sparse directory (but not the entire directory).
+                *
+                * If the pathspec item is a literal path, the index only needs to be expanded
+                * if a) the pathspec isn't in the sparse checkout cone (to make sure we don't
+                * expand for in-cone files) and b) it doesn't match any sparse directories
+                * (since we can reset whole sparse directories without expanding them).
+                */
+               if (item.nowildcard_len < item.len) {
+                       /*
+                        * Special case: if the pattern is a path inside the cone
+                        * followed by only wildcards, the pattern cannot match
+                        * partial sparse directories, so we know we don't need to
+                        * expand the index.
+                        *
+                        * Examples:
+                        * - in-cone/foo***: doesn't need expanded index
+                        * - not-in-cone/bar*: may need expanded index
+                        * - **.c: may need expanded index
+                        */
+                       if (strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len &&
+                           path_in_cone_mode_sparse_checkout(item.original, istate))
+                               continue;
+
+                       for (pos = 0; pos < istate->cache_nr; pos++) {
+                               struct cache_entry *ce = istate->cache[pos];
+
+                               if (!S_ISSPARSEDIR(ce->ce_mode))
+                                       continue;
+
+                               /*
+                                * If the pre-wildcard length is longer than the sparse
+                                * directory name and the sparse directory is the first
+                                * component of the pathspec, need to expand the index.
+                                */
+                               if (item.nowildcard_len > ce_namelen(ce) &&
+                                   !strncmp(item.original, ce->name, ce_namelen(ce))) {
+                                       res = 1;
+                                       break;
+                               }
+
+                               /*
+                                * If the pre-wildcard length is shorter than the sparse
+                                * directory and the pathspec does not match the whole
+                                * directory, need to expand the index.
+                                */
+                               if (!strncmp(item.original, ce->name, item.nowildcard_len) &&
+                                   wildmatch(item.original, ce->name, 0)) {
+                                       res = 1;
+                                       break;
+                               }
+                       }
+               } else if (!path_in_cone_mode_sparse_checkout(item.original, istate) &&
+                          !matches_skip_worktree(pathspec, i, &skip_worktree_seen))
+                       res = 1;
+
+               if (res > 0)
+                       break;
+       }
+
+       free(skip_worktree_seen);
+       return res;
+}
index 402ebb808081e386da46786fe1d5a1521e3cc4ac..41f6adfbb421fee745e3717ffd57ff92acfc82b1 100644 (file)
@@ -171,4 +171,16 @@ int match_pathspec_attrs(struct index_state *istate,
                         const char *name, int namelen,
                         const struct pathspec_item *item);
 
+/*
+ * Determine whether a pathspec will match only entire index entries (non-sparse
+ * files and/or entire sparse directories). If the pathspec has the potential to
+ * match partial contents of a sparse directory, return 1 to indicate the index
+ * should be expanded to match the  appropriate index entries.
+ *
+ * For the sake of simplicity, always return 1 if using a more complex "magic"
+ * pathspec.
+ */
+int pathspec_needs_expanded_index(struct index_state *istate,
+                                 const struct pathspec *pathspec);
+
 #endif /* PATHSPEC_H */
index 76f372ff9175eaf3a4a47cc97fb8b475393fbc64..b09128b188431857450336481898f27f8894a241 100644 (file)
@@ -620,6 +620,11 @@ int index_name_pos(struct index_state *istate, const char *name, int namelen)
        return index_name_stage_pos(istate, name, namelen, 0, EXPAND_SPARSE);
 }
 
+int index_name_pos_sparse(struct index_state *istate, const char *name, int namelen)
+{
+       return index_name_stage_pos(istate, name, namelen, 0, NO_EXPAND_SPARSE);
+}
+
 int index_entry_exists(struct index_state *istate, const char *name, int namelen)
 {
        return index_name_stage_pos(istate, name, namelen, 0, NO_EXPAND_SPARSE) >= 0;
@@ -2294,6 +2299,8 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)
        fd = open(path, O_RDONLY);
        if (fd < 0) {
                if (!must_exist && errno == ENOENT) {
+                       if (!istate->repo)
+                               istate->repo = the_repository;
                        set_new_index_sparsity(istate);
                        return 0;
                }
index 8b79891d3218d526ef1216bc1525b6040730185a..8c0c44699335e65af07ee2ea3ebd4fb266db32a5 100644 (file)
--- a/refspec.h
+++ b/refspec.h
@@ -69,7 +69,7 @@ int valid_remote_name(const char *name);
 struct strvec;
 /*
  * Determine what <prefix> values to pass to the peer in ref-prefix lines
- * (see Documentation/technical/protocol-v2.txt).
+ * (see linkgit:gitprotocol-v2[5]).
  */
 void refspec_ref_prefixes(const struct refspec *rs,
                          struct strvec *ref_prefixes);
index 1ee2b145d07f7247d8f6e7476e020174520e9faf..618ad5a0f16aa5eb2359d4c015f8dd476570dcf1 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -11,7 +11,6 @@
 #include "dir.h"
 #include "tag.h"
 #include "string-list.h"
-#include "mergesort.h"
 #include "strvec.h"
 #include "commit-reach.h"
 #include "advice.h"
@@ -1082,27 +1081,6 @@ void free_refs(struct ref *ref)
        }
 }
 
-int ref_compare_name(const void *va, const void *vb)
-{
-       const struct ref *a = va, *b = vb;
-       return strcmp(a->name, b->name);
-}
-
-static void *ref_list_get_next(const void *a)
-{
-       return ((const struct ref *)a)->next;
-}
-
-static void ref_list_set_next(void *a, void *next)
-{
-       ((struct ref *)a)->next = next;
-}
-
-void sort_ref_list(struct ref **l, int (*cmp)(const void *, const void *))
-{
-       *l = llist_mergesort(*l, ref_list_get_next, ref_list_set_next, cmp);
-}
-
 int count_refspec_match(const char *pattern,
                        struct ref *refs,
                        struct ref **matched_ref)
@@ -2169,6 +2147,9 @@ static int stat_branch_pair(const char *branch_name, const char *base,
        struct object_id oid;
        struct commit *ours, *theirs;
        struct rev_info revs;
+       struct setup_revision_opt opt = {
+               .free_removed_argv_elements = 1,
+       };
        struct strvec argv = STRVEC_INIT;
 
        /* Cannot stat if what we used to build on no longer exists */
@@ -2203,7 +2184,7 @@ static int stat_branch_pair(const char *branch_name, const char *base,
        strvec_push(&argv, "--");
 
        repo_init_revisions(the_repository, &revs, NULL);
-       setup_revisions(argv.nr, argv.v, &revs, NULL);
+       setup_revisions(argv.nr, argv.v, &revs, &opt);
        if (prepare_revision_walk(&revs))
                die(_("revision walk setup failed"));
 
index 448675e11259b1cc109af9a7d562930246c164ac..1c4621b414bdc0372613e58c3b2133b84004d1e7 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -207,9 +207,7 @@ struct ref *find_ref_by_name(const struct ref *list, const char *name);
 struct ref *alloc_ref(const char *name);
 struct ref *copy_ref(const struct ref *ref);
 struct ref *copy_ref_list(const struct ref *ref);
-void sort_ref_list(struct ref **, int (*cmp)(const void *, const void *));
 int count_refspec_match(const char *, struct ref *refs, struct ref **matched_ref);
-int ref_compare_name(const void *, const void *);
 
 int check_ref_type(const struct ref *ref, int flags);
 
index 0c6e26cd9c8ff8d4f6fdb6050d7df57be460256e..f4eee11cc8b424fd92d43bc30bf9c3a0ae1b3492 100644 (file)
@@ -2784,6 +2784,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                        const char *arg = argv[i];
                        if (strcmp(arg, "--"))
                                continue;
+                       if (opt && opt->free_removed_argv_elements)
+                               free((char *)argv[i]);
                        argv[i] = NULL;
                        argc = i;
                        if (argv[i + 1])
@@ -3791,51 +3793,6 @@ int rewrite_parents(struct rev_info *revs, struct commit *commit,
        return 0;
 }
 
-static int commit_rewrite_person(struct strbuf *buf, const char *what, struct string_list *mailmap)
-{
-       char *person, *endp;
-       size_t len, namelen, maillen;
-       const char *name;
-       const char *mail;
-       struct ident_split ident;
-
-       person = strstr(buf->buf, what);
-       if (!person)
-               return 0;
-
-       person += strlen(what);
-       endp = strchr(person, '\n');
-       if (!endp)
-               return 0;
-
-       len = endp - person;
-
-       if (split_ident_line(&ident, person, len))
-               return 0;
-
-       mail = ident.mail_begin;
-       maillen = ident.mail_end - ident.mail_begin;
-       name = ident.name_begin;
-       namelen = ident.name_end - ident.name_begin;
-
-       if (map_user(mailmap, &mail, &maillen, &name, &namelen)) {
-               struct strbuf namemail = STRBUF_INIT;
-
-               strbuf_addf(&namemail, "%.*s <%.*s>",
-                           (int)namelen, name, (int)maillen, mail);
-
-               strbuf_splice(buf, ident.name_begin - buf->buf,
-                             ident.mail_end - ident.name_begin + 1,
-                             namemail.buf, namemail.len);
-
-               strbuf_release(&namemail);
-
-               return 1;
-       }
-
-       return 0;
-}
-
 static int commit_match(struct commit *commit, struct rev_info *opt)
 {
        int retval;
@@ -3868,11 +3825,12 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
                strbuf_addstr(&buf, message);
 
        if (opt->grep_filter.header_list && opt->mailmap) {
+               const char *commit_headers[] = { "author ", "committer ", NULL };
+
                if (!buf.len)
                        strbuf_addstr(&buf, message);
 
-               commit_rewrite_person(&buf, "\nauthor ", opt->mailmap);
-               commit_rewrite_person(&buf, "\ncommitter ", opt->mailmap);
+               apply_mailmap_to_header(&buf, commit_headers, opt->mailmap);
        }
 
        /* Append "fake" message parts as needed */
index e576845cdd103731f3e8d3ea1a4b0eefb21e2063..bb91e7ed9148644355e03e6b3190cebf767289a1 100644 (file)
@@ -375,7 +375,8 @@ struct setup_revision_opt {
        const char *def;
        void (*tweak)(struct rev_info *, struct setup_revision_opt *);
        unsigned int    assume_dashdash:1,
-                       allow_exclude_promisor_objects:1;
+                       allow_exclude_promisor_objects:1,
+                       free_removed_argv_elements:1;
        unsigned revarg_opt;
 };
 int setup_revisions(int argc, const char **argv, struct rev_info *revs,
index 14f17830f51254511e2c184293a6816a132d287f..5ec3a46dccf959bd54af42fbeaaf4027dc64996a 100644 (file)
@@ -10,6 +10,7 @@
 #include "config.h"
 #include "packfile.h"
 #include "hook.h"
+#include "compat/nonblock.h"
 
 void child_process_init(struct child_process *child)
 {
@@ -1364,12 +1365,25 @@ static int pump_io_round(struct io_pump *slots, int nr, struct pollfd *pfd)
                        continue;
 
                if (io->type == POLLOUT) {
-                       ssize_t len = xwrite(io->fd,
-                                            io->u.out.buf, io->u.out.len);
+                       ssize_t len;
+
+                       /*
+                        * Don't use xwrite() here. It loops forever on EAGAIN,
+                        * and we're in our own poll() loop here.
+                        *
+                        * Note that we lose xwrite()'s handling of MAX_IO_SIZE
+                        * and EINTR, so we have to implement those ourselves.
+                        */
+                       len = write(io->fd, io->u.out.buf,
+                                   io->u.out.len <= MAX_IO_SIZE ?
+                                   io->u.out.len : MAX_IO_SIZE);
                        if (len < 0) {
-                               io->error = errno;
-                               close(io->fd);
-                               io->fd = -1;
+                               if (errno != EINTR && errno != EAGAIN &&
+                                   errno != ENOSPC) {
+                                       io->error = errno;
+                                       close(io->fd);
+                                       io->fd = -1;
+                               }
                        } else {
                                io->u.out.buf += len;
                                io->u.out.len -= len;
@@ -1438,6 +1452,15 @@ int pipe_command(struct child_process *cmd,
                return -1;
 
        if (in) {
+               if (enable_pipe_nonblock(cmd->in) < 0) {
+                       error_errno("unable to make pipe non-blocking");
+                       close(cmd->in);
+                       if (out)
+                               close(cmd->out);
+                       if (err)
+                               close(cmd->err);
+                       return -1;
+               }
                io[nr].fd = cmd->in;
                io[nr].type = POLLOUT;
                io[nr].u.out.buf = in;
diff --git a/setup.c b/setup.c
index 8c683e92b62e76cfbc350c8d8f4e9372f62c5f8f..cefd5f63c4680f7f656084ef72f74784f86e4562 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -1142,16 +1142,17 @@ static int safe_directory_cb(const char *key, const char *value, void *d)
  * added, for bare ones their git directory.
  */
 static int ensure_valid_ownership(const char *gitfile,
-                               const char *worktree, const char *gitdir)
+                                 const char *worktree, const char *gitdir,
+                                 struct strbuf *report)
 {
        struct safe_directory_data data = {
                .path = worktree ? worktree : gitdir
        };
 
        if (!git_env_bool("GIT_TEST_ASSUME_DIFFERENT_OWNER", 0) &&
-          (!gitfile || is_path_owned_by_current_user(gitfile)) &&
-          (!worktree || is_path_owned_by_current_user(worktree)) &&
-          (!gitdir || is_path_owned_by_current_user(gitdir)))
+           (!gitfile || is_path_owned_by_current_user(gitfile, report)) &&
+           (!worktree || is_path_owned_by_current_user(worktree, report)) &&
+           (!gitdir || is_path_owned_by_current_user(gitdir, report)))
                return 1;
 
        /*
@@ -1232,6 +1233,7 @@ enum discovery_result {
  */
 static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
                                                          struct strbuf *gitdir,
+                                                         struct strbuf *report,
                                                          int die_on_error)
 {
        const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
@@ -1316,10 +1318,11 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
                strbuf_setlen(dir, offset);
                if (gitdirenv) {
                        enum discovery_result ret;
+                       const char *gitdir_candidate =
+                               gitdir_path ? gitdir_path : gitdirenv;
 
-                       if (ensure_valid_ownership(gitfile,
-                                                dir->buf,
-                                (gitdir_path ? gitdir_path : gitdirenv))) {
+                       if (ensure_valid_ownership(gitfile, dir->buf,
+                                                  gitdir_candidate, report)) {
                                strbuf_addstr(gitdir, gitdirenv);
                                ret = GIT_DIR_DISCOVERED;
                        } else
@@ -1344,7 +1347,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
                if (is_git_directory(dir->buf)) {
                        if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT)
                                return GIT_DIR_DISALLOWED_BARE;
-                       if (!ensure_valid_ownership(NULL, NULL, dir->buf))
+                       if (!ensure_valid_ownership(NULL, NULL, dir->buf, report))
                                return GIT_DIR_INVALID_OWNERSHIP;
                        strbuf_addstr(gitdir, ".");
                        return GIT_DIR_BARE;
@@ -1377,7 +1380,7 @@ int discover_git_directory(struct strbuf *commondir,
                return -1;
 
        cwd_len = dir.len;
-       if (setup_git_directory_gently_1(&dir, gitdir, 0) <= 0) {
+       if (setup_git_directory_gently_1(&dir, gitdir, NULL, 0) <= 0) {
                strbuf_release(&dir);
                return -1;
        }
@@ -1424,7 +1427,7 @@ int discover_git_directory(struct strbuf *commondir,
 const char *setup_git_directory_gently(int *nongit_ok)
 {
        static struct strbuf cwd = STRBUF_INIT;
-       struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT;
+       struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT, report = STRBUF_INIT;
        const char *prefix = NULL;
        struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
 
@@ -1449,7 +1452,7 @@ const char *setup_git_directory_gently(int *nongit_ok)
                die_errno(_("Unable to read current working directory"));
        strbuf_addbuf(&dir, &cwd);
 
-       switch (setup_git_directory_gently_1(&dir, &gitdir, 1)) {
+       switch (setup_git_directory_gently_1(&dir, &gitdir, &report, 1)) {
        case GIT_DIR_EXPLICIT:
                prefix = setup_explicit_git_dir(gitdir.buf, &cwd, &repo_fmt, nongit_ok);
                break;
@@ -1481,12 +1484,14 @@ const char *setup_git_directory_gently(int *nongit_ok)
                if (!nongit_ok) {
                        struct strbuf quoted = STRBUF_INIT;
 
+                       strbuf_complete(&report, '\n');
                        sq_quote_buf_pretty(&quoted, dir.buf);
                        die(_("detected dubious ownership in repository at '%s'\n"
+                             "%s"
                              "To add an exception for this directory, call:\n"
                              "\n"
                              "\tgit config --global --add safe.directory %s"),
-                           dir.buf, quoted.buf);
+                           dir.buf, report.buf, quoted.buf);
                }
                *nongit_ok = 1;
                break;
@@ -1573,6 +1578,7 @@ const char *setup_git_directory_gently(int *nongit_ok)
 
        strbuf_release(&dir);
        strbuf_release(&gitdir);
+       strbuf_release(&report);
        clear_repository_format(&repo_fmt);
 
        return prefix;
index 7f56e52f7677b8c74e64b1dc0569b7af30a7ff46..1c80c0c79a05d8de64b2ac0702112337d2db60f1 100644 (file)
@@ -62,7 +62,7 @@ pre-clean:
        $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)'
 
 clean-except-prove-cache: clean-chainlint
-       $(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)'
+       $(RM) -r 'trash directory'.*
        $(RM) -r valgrind/bin
 
 clean: clean-except-prove-cache
index 4f9981cf5e3c29d255ed0d2a3f399cd992e961c7..2f439f96589f821b74e7f203c8627d8b5a807bdf 100644 (file)
--- a/t/README
+++ b/t/README
@@ -366,12 +366,47 @@ excluded as so much relies on it, but this might change in the future.
 GIT_TEST_SPLIT_INDEX=<boolean> forces split-index mode on the whole
 test suite. Accept any boolean values that are accepted by git-config.
 
-GIT_TEST_PASSING_SANITIZE_LEAK=<boolean> when compiled with
-SANITIZE=leak will run only those tests that have whitelisted
-themselves as passing with no memory leaks. Tests can be whitelisted
-by setting "TEST_PASSES_SANITIZE_LEAK=true" before sourcing
-"test-lib.sh" itself at the top of the test script. This test mode is
-used by the "linux-leaks" CI target.
+GIT_TEST_PASSING_SANITIZE_LEAK=true skips those tests that haven't
+declared themselves as leak-free by setting
+"TEST_PASSES_SANITIZE_LEAK=true" before sourcing "test-lib.sh". This
+test mode is used by the "linux-leaks" CI target.
+
+GIT_TEST_PASSING_SANITIZE_LEAK=check checks that our
+"TEST_PASSES_SANITIZE_LEAK=true" markings are current. Rather than
+skipping those tests that haven't set "TEST_PASSES_SANITIZE_LEAK=true"
+before sourcing "test-lib.sh" this mode runs them with
+"--invert-exit-code". This is used to check that there's a one-to-one
+mapping between "TEST_PASSES_SANITIZE_LEAK=true" and those tests that
+pass under "SANITIZE=leak". This is especially useful when testing a
+series that fixes various memory leaks with "git rebase -x".
+
+GIT_TEST_SANITIZE_LEAK_LOG=true will log memory leaks to
+"test-results/$TEST_NAME.leak/trace.*" files. The logs include a
+"dedup_token" (see +"ASAN_OPTIONS=help=1 ./git") and other options to
+make logs +machine-readable.
+
+With GIT_TEST_SANITIZE_LEAK_LOG=true we'll look at the leak logs
+before exiting and exit on failure if the logs showed that we had a
+memory leak, even if the test itself would have otherwise passed. This
+allows us to catch e.g. missing &&-chaining. This is especially useful
+when combined with "GIT_TEST_PASSING_SANITIZE_LEAK", see below.
+
+GIT_TEST_PASSING_SANITIZE_LEAK=check when combined with "--immediate"
+will run to completion faster, and result in the same failing
+tests. The only practical reason to run
+GIT_TEST_PASSING_SANITIZE_LEAK=check without "--immediate" is to
+combine it with "GIT_TEST_SANITIZE_LEAK_LOG=true". If we stop at the
+first failing test case our leak logs won't show subsequent leaks we
+might have run into.
+
+GIT_TEST_PASSING_SANITIZE_LEAK=(true|check) will not catch all memory
+leaks unless combined with GIT_TEST_SANITIZE_LEAK_LOG=true. Some tests
+run "git" (or "test-tool" etc.) without properly checking the exit
+code, or git will invoke itself and fail to ferry the abort() exit
+code to the original caller. When the two modes are combined we'll
+look at the "test-results/$TEST_NAME.leak/trace.*" files at the end of
+the test run to see if had memory leaks which the test itself didn't
+catch.
 
 GIT_TEST_PROTOCOL_VERSION=<n>, when set, makes 'protocol.version'
 default to n.
@@ -935,32 +970,6 @@ see test-lib-functions.sh for the full list and their options.
            test_done
        fi
 
- - test_external [<prereq>] <message> <external> <script>
-
-   Execute a <script> with an <external> interpreter (like perl). This
-   was added for tests like t9700-perl-git.sh which do most of their
-   work in an external test script.
-
-       test_external \
-           'GitwebCache::*FileCache*' \
-           perl "$TEST_DIRECTORY"/t9503/test_cache_interface.pl
-
-   If the test is outputting its own TAP you should set the
-   test_external_has_tap variable somewhere before calling the first
-   test_external* function. See t9700-perl-git.sh for an example.
-
-       # The external test will outputs its own plan
-       test_external_has_tap=1
-
- - test_external_without_stderr [<prereq>] <message> <external> <script>
-
-   Like test_external but fail if there's any output on stderr,
-   instead of checking the exit code.
-
-       test_external_without_stderr \
-           'Perl API' \
-           perl "$TEST_DIRECTORY"/t9700/test.pl
-
  - test_expect_code <exit-code> <command>
 
    Run a command and ensure that it exits with the given exit code.
index 4e5553e20249debf36a76296f18599300ee91af5..45665ec19a5d6d4e2cf37c1a2a9f87d69ff3be02 100644 (file)
@@ -184,8 +184,6 @@ int cmd__fast_rebase(int argc, const char **argv)
                last_picked_commit = commit;
                last_commit = create_commit(result.tree, commit, last_commit);
        }
-       /* TODO: There should be some kind of rev_info_free(&revs) call... */
-       memset(&revs, 0, sizeof(revs));
 
        merge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean);
 
index ebf68f7de82465d2761bceedd47715badbf76dbb..202e54a7ffcb0d789326c4a43c5c3e798194addd 100644 (file)
@@ -13,19 +13,10 @@ struct line {
        struct line *next;
 };
 
-static void *get_next(const void *a)
-{
-       return ((const struct line *)a)->next;
-}
-
-static void set_next(void *a, void *b)
-{
-       ((struct line *)a)->next = b;
-}
+DEFINE_LIST_SORT(static, sort_lines, struct line, next);
 
-static int compare_strings(const void *a, const void *b)
+static int compare_strings(const struct line *x, const struct line *y)
 {
-       const struct line *x = a, *y = b;
        return strcmp(x->text, y->text);
 }
 
@@ -47,7 +38,7 @@ static int sort_stdin(void)
                p = line;
        }
 
-       lines = llist_mergesort(lines, get_next, set_next, compare_strings);
+       sort_lines(&lines, compare_strings);
 
        while (lines) {
                puts(lines->text);
@@ -273,21 +264,11 @@ struct number {
        struct number *next;
 };
 
-static void *get_next_number(const void *a)
-{
-       stats.get_next++;
-       return ((const struct number *)a)->next;
-}
-
-static void set_next_number(void *a, void *b)
-{
-       stats.set_next++;
-       ((struct number *)a)->next = b;
-}
+DEFINE_LIST_SORT_DEBUG(static, sort_numbers, struct number, next,
+                      stats.get_next++, stats.set_next++);
 
-static int compare_numbers(const void *av, const void *bv)
+static int compare_numbers(const struct number *an, const struct number *bn)
 {
-       const struct number *an = av, *bn = bv;
        int a = an->value, b = bn->value;
        stats.compare++;
        return (a > b) - (a < b);
@@ -325,8 +306,7 @@ static int test(const struct dist *dist, const struct mode *mode, int n, int m)
        *tail = NULL;
 
        stats.get_next = stats.set_next = stats.compare = 0;
-       list = llist_mergesort(list, get_next_number, set_next_number,
-                              compare_numbers);
+       sort_numbers(&list, compare_numbers);
 
        QSORT(arr, n, compare_ints);
        for (i = 0, curr = list; i < n && curr; i++, curr = curr->next) {
diff --git a/t/lib-perl.sh b/t/lib-perl.sh
new file mode 100644 (file)
index 0000000..d0bf509
--- /dev/null
@@ -0,0 +1,19 @@
+# Copyright (c) 2022 Ã†var Arnfjörð Bjarmason
+
+test_lazy_prereq PERL_TEST_MORE '
+       perl -MTest::More -e 0
+'
+
+skip_all_if_no_Test_More () {
+       if ! test_have_prereq PERL
+       then
+               skip_all='skipping perl interface tests, perl not available'
+               test_done
+       fi
+
+       if ! test_have_prereq PERL_TEST_MORE
+       then
+               skip_all="Perl Test::More unavailable, skipping test"
+               test_done
+       fi
+}
index ed366e2e1295254d176941a60e1fa5128d24f02e..ae4ddac8640c1aa8dd8e2ddd9babe01cb8cdbcf7 100755 (executable)
@@ -40,11 +40,11 @@ done
 
 for file in unsorted sorted reversed
 do
-       test_perf "llist_mergesort() $file" "
+       test_perf "DEFINE_LIST_SORT $file" "
                test-tool mergesort sort <$file >actual
        "
 
-       test_expect_success "llist_mergesort() $file sorts like sort(1)" "
+       test_expect_success "DEFINE_LIST_SORT $file sorts like sort(1)" "
                test_cmp_bin sorted actual
        "
 done
index c181110a43931fb2a8131270a4d8bdcb0a2e7a4b..fce8151d41cbbd9eadd098b26c864d61b5b74bcc 100755 (executable)
@@ -123,5 +123,6 @@ test_perf_on_all git blame $SPARSE_CONE/f3/a
 test_perf_on_all git read-tree -mu HEAD
 test_perf_on_all git checkout-index -f --all
 test_perf_on_all git update-index --add --remove $SPARSE_CONE/a
+test_perf_on_all "git rm -f $SPARSE_CONE/a && git checkout HEAD -- $SPARSE_CONE/a"
 
 test_done
index 17a268ccd1b014201e25c810ebade41f6f31aecf..502b4bcf9ea0ad06cddaad50b999dd8024af28eb 100755 (executable)
@@ -578,6 +578,78 @@ test_expect_success 'subtest: --run invalid range end' '
        EOF_ERR
 '
 
+test_expect_success 'subtest: --invert-exit-code without --immediate' '
+       run_sub_test_lib_test_err full-pass \
+               --invert-exit-code &&
+       check_sub_test_lib_test_err full-pass \
+               <<-\EOF_OUT 3<<-EOF_ERR
+       ok 1 - passing test #1
+       ok 2 - passing test #2
+       ok 3 - passing test #3
+       # passed all 3 test(s)
+       1..3
+       # faking up non-zero exit with --invert-exit-code
+       EOF_OUT
+       EOF_ERR
+'
+
+test_expect_success 'subtest: --invert-exit-code with --immediate: all passed' '
+       run_sub_test_lib_test_err full-pass \
+               --invert-exit-code --immediate &&
+       check_sub_test_lib_test_err full-pass \
+               <<-\EOF_OUT 3<<-EOF_ERR
+       ok 1 - passing test #1
+       ok 2 - passing test #2
+       ok 3 - passing test #3
+       # passed all 3 test(s)
+       1..3
+       # faking up non-zero exit with --invert-exit-code
+       EOF_OUT
+       EOF_ERR
+'
+
+test_expect_success 'subtest: --invert-exit-code without --immediate: partial pass' '
+       run_sub_test_lib_test partial-pass \
+               --invert-exit-code &&
+       check_sub_test_lib_test partial-pass <<-\EOF
+       ok 1 - passing test #1
+       not ok 2 - # TODO induced breakage (--invert-exit-code): failing test #2
+       #       false
+       ok 3 - passing test #3
+       # failed 1 among 3 test(s)
+       1..3
+       # faked up failures as TODO & now exiting with 0 due to --invert-exit-code
+       EOF
+'
+
+test_expect_success 'subtest: --invert-exit-code with --immediate: partial pass' '
+       run_sub_test_lib_test partial-pass \
+               --invert-exit-code --immediate &&
+       check_sub_test_lib_test partial-pass \
+               <<-\EOF_OUT 3<<-EOF_ERR
+       ok 1 - passing test #1
+       not ok 2 - # TODO induced breakage (--invert-exit-code): failing test #2
+       #       false
+       1..2
+       # faked up failures as TODO & now exiting with 0 due to --invert-exit-code
+       EOF_OUT
+       EOF_ERR
+'
+
+test_expect_success 'subtest: --invert-exit-code --immediate: got a failure' '
+       run_sub_test_lib_test partial-pass \
+               --invert-exit-code --immediate &&
+       check_sub_test_lib_test_err partial-pass \
+               <<-\EOF_OUT 3<<-EOF_ERR
+       ok 1 - passing test #1
+       not ok 2 - # TODO induced breakage (--invert-exit-code): failing test #2
+       #       false
+       1..2
+       # faked up failures as TODO & now exiting with 0 due to --invert-exit-code
+       EOF_OUT
+       EOF_ERR
+'
+
 test_expect_success 'subtest: tests respect prerequisites' '
        write_and_run_sub_test_lib_test prereqs <<-\EOF &&
 
index f6356db183b98c5c29acdb4b03dcb453533a5728..26eaca095a26a6a654364bdd8acf208af30b2d00 100755 (executable)
@@ -65,7 +65,7 @@ test_expect_success 'check commit-tree' '
        test_path_is_file "$REAL/objects/$(objpath $SHA)"
 '
 
-test_expect_success !SANITIZE_LEAK 'check rev-list' '
+test_expect_success 'check rev-list' '
        git update-ref "HEAD" "$SHA" &&
        git rev-list HEAD >actual &&
        echo $SHA >expected &&
index 2e9d652d826af230fc90166c9ef53da3f6e7aa7e..8114fac73b320b85949d8d7ce1422c35e1eb8158 100755 (executable)
@@ -31,7 +31,7 @@ test_expect_success WRITE_TREE_OUT 'write-tree output on unwritable repository'
        test_cmp expect out.write-tree
 '
 
-test_expect_success POSIXPERM,SANITY,!SANITIZE_LEAK 'commit should notice unwritable repository' '
+test_expect_success POSIXPERM,SANITY 'commit should notice unwritable repository' '
        test_when_finished "chmod 775 .git/objects .git/objects/??" &&
        chmod a-w .git/objects .git/objects/?? &&
        test_must_fail git commit -m second 2>out.commit
index 6c33a4369015c813a73384ff68149463c5189105..4ed2f242eb246b05e82527d16f7203ec2d163af9 100755 (executable)
@@ -44,6 +44,8 @@ test_expect_success 'invalid usage' '
        test_expect_code 129 git help -g add &&
        test_expect_code 129 git help -a -g &&
 
+       test_expect_code 129 git help --user-interfaces add &&
+
        test_expect_code 129 git help -g -c &&
        test_expect_code 129 git help --config-for-completion add &&
        test_expect_code 129 git help --config-sections-for-completion add
@@ -104,9 +106,9 @@ test_expect_success 'git help' '
        test_i18ngrep "^   commit " help.output &&
        test_i18ngrep "^   fetch  " help.output
 '
+
 test_expect_success 'git help -g' '
        git help -g >help.output &&
-       test_i18ngrep "^   attributes " help.output &&
        test_i18ngrep "^   everyday   " help.output &&
        test_i18ngrep "^   tutorial   " help.output
 '
@@ -127,6 +129,12 @@ test_expect_success 'git help succeeds without git.html' '
        test_cmp expect test-browser.log
 '
 
+test_expect_success 'git help --user-interfaces' '
+       git help --user-interfaces >help.output &&
+       grep "^   attributes   " help.output &&
+       grep "^   mailmap   " help.output
+'
+
 test_expect_success 'git help -c' '
        git help -c >help.output &&
        cat >expect <<-\EOF &&
@@ -220,6 +228,10 @@ test_expect_success "'git help -a' section spacing" '
        Low-level Commands / Syncing Repositories
 
        Low-level Commands / Internal Helpers
+
+       User-facing repository, command and file interfaces
+
+       Developer-facing file file formats, protocols and interfaces
        EOF
        test_cmp expect actual
 '
index 7f80f463930407410c6d3f671fb1f7833ec3b01e..a22e0e1382c42f192778e34d985c135d8008f232 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='CRLF conversion all combinations'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 compare_files () {
index 0ed14971a5801b68045f27232d9912cc09154a57..471cb37ac28affec3771d0b46e8dae02d4f09716 100755 (executable)
@@ -5,6 +5,7 @@
 
 test_description='reftable unittests'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'unittests' '
index f4d737dadd04a1b582ed765acdddd5ddad14d5de..aecb308cf668057995a167090a421b906983f32b 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='verify safe.directory checks'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 GIT_TEST_ASSUME_DIFFERENT_OWNER=1
index 5c9dc90d0b096d9f104caedeb035b50b919b6811..325eb1c3cd0add75bcaf3b629c2692420c279f8a 100755 (executable)
@@ -5,6 +5,7 @@ test_description='Various filesystem issues'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 auml=$(printf '\303\244')
index 6f9a501c72b3a2520fd4d4734b45fe56a2a6d35e..ba8ad1d1ca0adeee8f001450951ec782e1fc3d74 100755 (executable)
@@ -5,7 +5,7 @@ test_description='verify sort functions'
 TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
-test_expect_success 'llist_mergesort()' '
+test_expect_success 'DEFINE_LIST_SORT_DEBUG' '
        test-tool mergesort test
 '
 
index daeb4a5e3e7514ea0bba1e10af58d5249b45e555..b567383eb836bff0c743522692cecdb354cbd0e6 100755 (executable)
@@ -1,6 +1,8 @@
 #!/bin/sh
 
 test_description='Testing the various Bloom filter computations in bloom.c'
+
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'compute unseeded murmur3 hash for empty string' '
index df2ea34932bcfe99cd5719cc2e2f587e6b09c49e..5a6f28051bd275577110ddbba5618f756ca0937e 100755 (executable)
@@ -7,22 +7,12 @@ test_description='Perl gettext interface (Git::I18N)'
 
 TEST_PASSES_SANITIZE_LEAK=true
 . ./lib-gettext.sh
+. "$TEST_DIRECTORY"/lib-perl.sh
+skip_all_if_no_Test_More
 
-if ! test_have_prereq PERL; then
-       skip_all='skipping perl interface tests, perl not available'
-       test_done
-fi
-
-perl -MTest::More -e 0 2>/dev/null || {
-       skip_all="Perl Test::More unavailable, skipping test"
-       test_done
-}
-
-# The external test will outputs its own plan
-test_external_has_tap=1
-
-test_external_without_stderr \
-    'Perl Git::I18N API' \
-    perl "$TEST_DIRECTORY"/t0202/test.pl
+test_expect_success 'run t0202/test.pl to test Git::I18N.pm' '
+       "$PERL_PATH" "$TEST_DIRECTORY"/t0202/test.pl 2>stderr &&
+       test_must_be_empty stderr
+'
 
 test_done
index 0ce1f22eff66285ee0da9b1830de961555fbad2b..86cff324ff181110cf1ef8a44af6c4295b7f5d40 100755 (executable)
@@ -5,6 +5,7 @@
 
 test_description="The Git C functions aren't broken by setlocale(3)"
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./lib-gettext.sh
 
 test_expect_success 'git show a ISO-8859-1 commit under C locale' '
index dadf3b14583bec460a51cbf69362a875dd3cc867..23b8942edba45c32822886ee93acef9b5474ea02 100755 (executable)
@@ -88,7 +88,8 @@ done
 
 for opt in --buffer \
        --follow-symlinks \
-       --batch-all-objects
+       --batch-all-objects \
+       -z
 do
        test_expect_success "usage: bad option combination: $opt without batch mode" '
                test_incompatible_usage git cat-file $opt &&
@@ -100,6 +101,10 @@ echo_without_newline () {
     printf '%s' "$*"
 }
 
+echo_without_newline_nul () {
+       echo_without_newline "$@" | tr '\n' '\0'
+}
+
 strlen () {
     echo_without_newline "$1" | wc -c | sed -e 's/^ *//'
 }
@@ -398,6 +403,12 @@ test_expect_success '--batch with multiple sha1s gives correct format' '
        test "$(maybe_remove_timestamp "$batch_output" 1)" = "$(maybe_remove_timestamp "$(echo_without_newline "$batch_input" | git cat-file --batch)" 1)"
 '
 
+test_expect_success '--batch, -z with multiple sha1s gives correct format' '
+       echo_without_newline_nul "$batch_input" >in &&
+       test "$(maybe_remove_timestamp "$batch_output" 1)" = \
+       "$(maybe_remove_timestamp "$(git cat-file --batch -z <in)" 1)"
+'
+
 batch_check_input="$hello_sha1
 $tree_sha1
 $commit_sha1
@@ -418,6 +429,30 @@ test_expect_success "--batch-check with multiple sha1s gives correct format" '
     "$(echo_without_newline "$batch_check_input" | git cat-file --batch-check)"
 '
 
+test_expect_success "--batch-check, -z with multiple sha1s gives correct format" '
+    echo_without_newline_nul "$batch_check_input" >in &&
+    test "$batch_check_output" = "$(git cat-file --batch-check -z <in)"
+'
+
+test_expect_success FUNNYNAMES '--batch-check, -z with newline in input' '
+       touch -- "newline${LF}embedded" &&
+       git add -- "newline${LF}embedded" &&
+       git commit -m "file with newline embedded" &&
+       test_tick &&
+
+       printf "HEAD:newline${LF}embedded" >in &&
+       git cat-file --batch-check -z <in >actual &&
+
+       echo "$(git rev-parse "HEAD:newline${LF}embedded") blob 0" >expect &&
+       test_cmp expect actual
+'
+
+batch_command_multiple_info="info $hello_sha1
+info $tree_sha1
+info $commit_sha1
+info $tag_sha1
+info deadbeef"
+
 test_expect_success '--batch-command with multiple info calls gives correct format' '
        cat >expect <<-EOF &&
        $hello_sha1 blob $hello_size
@@ -427,17 +462,23 @@ test_expect_success '--batch-command with multiple info calls gives correct form
        deadbeef missing
        EOF
 
-       git cat-file --batch-command --buffer >actual <<-EOF &&
-       info $hello_sha1
-       info $tree_sha1
-       info $commit_sha1
-       info $tag_sha1
-       info deadbeef
-       EOF
+       echo "$batch_command_multiple_info" >in &&
+       git cat-file --batch-command --buffer <in >actual &&
+
+       test_cmp expect actual &&
+
+       echo "$batch_command_multiple_info" | tr "\n" "\0" >in &&
+       git cat-file --batch-command --buffer -z <in >actual &&
 
        test_cmp expect actual
 '
 
+batch_command_multiple_contents="contents $hello_sha1
+contents $commit_sha1
+contents $tag_sha1
+contents deadbeef
+flush"
+
 test_expect_success '--batch-command with multiple command calls gives correct format' '
        remove_timestamp >expect <<-EOF &&
        $hello_sha1 blob $hello_size
@@ -449,13 +490,14 @@ test_expect_success '--batch-command with multiple command calls gives correct f
        deadbeef missing
        EOF
 
-       git cat-file --batch-command --buffer >actual_raw <<-EOF &&
-       contents $hello_sha1
-       contents $commit_sha1
-       contents $tag_sha1
-       contents deadbeef
-       flush
-       EOF
+       echo "$batch_command_multiple_contents" >in &&
+       git cat-file --batch-command --buffer <in >actual_raw &&
+
+       remove_timestamp <actual_raw >actual &&
+       test_cmp expect actual &&
+
+       echo "$batch_command_multiple_contents" | tr "\n" "\0" >in &&
+       git cat-file --batch-command --buffer -z <in >actual_raw &&
 
        remove_timestamp <actual_raw >actual &&
        test_cmp expect actual
index 9fdbb2af80e0a82429289d06b24d4dcfac3f263d..45eef9457fe13d9eef6e463405190ca84c67bda5 100755 (executable)
@@ -6,6 +6,7 @@
 test_description='Try various core-level commands in subdirectory.
 '
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-read-tree.sh
 
index 763c6cc684687a3aee6b915dc676f4425065eeeb..a6a14c8a21f73b48510b6a40cc1896145cbafad8 100755 (executable)
@@ -372,6 +372,14 @@ test_expect_success 'deep changes during checkout' '
        test_all_match git checkout base
 '
 
+test_expect_success 'checkout with modified sparse directory' '
+       init_repos &&
+
+       test_all_match git checkout rename-in-to-out -- . &&
+       test_sparse_match git sparse-checkout reapply &&
+       test_all_match git checkout base
+'
+
 test_expect_success 'add outside sparse cone' '
        init_repos &&
 
@@ -687,6 +695,23 @@ test_expect_success 'reset with wildcard pathspec' '
        test_all_match git ls-files -s -- folder1
 '
 
+test_expect_success 'reset hard with removed sparse dir' '
+       init_repos &&
+
+       run_on_all git rm -r --sparse folder1 &&
+       test_all_match git status --porcelain=v2 &&
+
+       test_all_match git reset --hard &&
+       test_all_match git status --porcelain=v2 &&
+
+       cat >expect <<-\EOF &&
+       folder1/
+       EOF
+
+       git -C sparse-index ls-files --sparse folder1 >out &&
+       test_cmp expect out
+'
+
 test_expect_success 'update-index modify outside sparse definition' '
        init_repos &&
 
@@ -912,7 +937,7 @@ test_expect_success 'read-tree --prefix' '
        test_all_match git read-tree --prefix=deep/deeper1/deepest -u deepest &&
        test_all_match git status --porcelain=v2 &&
 
-       test_all_match git rm -rf --sparse folder1/ &&
+       run_on_all git rm -rf --sparse folder1/ &&
        test_all_match git read-tree --prefix=folder1/ -u update-folder1 &&
        test_all_match git status --porcelain=v2 &&
 
@@ -1340,10 +1365,14 @@ ensure_not_expanded () {
                shift &&
                test_must_fail env \
                        GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
-                       git -C sparse-index "$@" || return 1
+                       git -C sparse-index "$@" \
+                       >sparse-index-out \
+                       2>sparse-index-error || return 1
        else
                GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
-                       git -C sparse-index "$@" || return 1
+                       git -C sparse-index "$@" \
+                       >sparse-index-out \
+                       2>sparse-index-error || return 1
        fi &&
        test_region ! index ensure_full_index trace2.txt
 }
@@ -1853,4 +1882,94 @@ test_expect_success 'mv directory from out-of-cone to in-cone' '
        grep -e "H deep/0/1" actual
 '
 
+test_expect_success 'rm pathspec inside sparse definition' '
+       init_repos &&
+
+       test_all_match git rm deep/a &&
+       test_all_match git status --porcelain=v2 &&
+
+       # test wildcard
+       run_on_all git reset --hard &&
+       test_all_match git rm deep/* &&
+       test_all_match git status --porcelain=v2 &&
+
+       # test recursive rm
+       run_on_all git reset --hard &&
+       test_all_match git rm -r deep &&
+       test_all_match git status --porcelain=v2
+'
+
+test_expect_success 'rm pathspec outside sparse definition' '
+       init_repos &&
+
+       for file in folder1/a folder1/0/1
+       do
+               test_sparse_match test_must_fail git rm $file &&
+               test_sparse_match test_must_fail git rm --cached $file &&
+               test_sparse_match git rm --sparse $file &&
+               test_sparse_match git status --porcelain=v2
+       done &&
+
+       cat >folder1-full <<-EOF &&
+       rm ${SQ}folder1/0/0/0${SQ}
+       rm ${SQ}folder1/0/1${SQ}
+       rm ${SQ}folder1/a${SQ}
+       EOF
+
+       cat >folder1-sparse <<-EOF &&
+       rm ${SQ}folder1/${SQ}
+       EOF
+
+       # test wildcard
+       run_on_sparse git reset --hard &&
+       run_on_sparse git sparse-checkout reapply &&
+       test_sparse_match test_must_fail git rm folder1/* &&
+       run_on_sparse git rm --sparse folder1/* &&
+       test_cmp folder1-full sparse-checkout-out &&
+       test_cmp folder1-sparse sparse-index-out &&
+       test_sparse_match git status --porcelain=v2 &&
+
+       # test recursive rm
+       run_on_sparse git reset --hard &&
+       run_on_sparse git sparse-checkout reapply &&
+       test_sparse_match test_must_fail git rm --sparse folder1 &&
+       run_on_sparse git rm --sparse -r folder1 &&
+       test_cmp folder1-full sparse-checkout-out &&
+       test_cmp folder1-sparse sparse-index-out &&
+       test_sparse_match git status --porcelain=v2
+'
+
+test_expect_success 'rm pathspec expands index when necessary' '
+       init_repos &&
+
+       # in-cone pathspec (do not expand)
+       ensure_not_expanded rm "deep/deep*" &&
+       test_must_be_empty sparse-index-err &&
+
+       # out-of-cone pathspec (expand)
+       ! ensure_not_expanded rm --sparse "folder1/a*" &&
+       test_must_be_empty sparse-index-err &&
+
+       # pathspec that should expand index
+       ! ensure_not_expanded rm "*/a" &&
+       test_must_be_empty sparse-index-err &&
+
+       ! ensure_not_expanded rm "**a" &&
+       test_must_be_empty sparse-index-err
+'
+
+test_expect_success 'sparse index is not expanded: rm' '
+       init_repos &&
+
+       ensure_not_expanded rm deep/a &&
+
+       # test in-cone wildcard
+       git -C sparse-index reset --hard &&
+       ensure_not_expanded rm deep/* &&
+
+       # test recursive rm
+       git -C sparse-index reset --hard &&
+       ensure_not_expanded rm -r deep
+'
+
 test_done
index 9fb0b90f252aa17a04a52678eb48a3a9bf9883ee..0c204089b83595bc516e9c26416cd67191d3c083 100755 (executable)
@@ -165,4 +165,14 @@ test_expect_success 'symbolic-ref can resolve d/f name (ENOTDIR)' '
        test_cmp expect actual
 '
 
+test_expect_success 'symbolic-ref refuses invalid target for non-HEAD' '
+       test_must_fail git symbolic-ref refs/heads/invalid foo..bar
+'
+
+test_expect_success 'symbolic-ref allows top-level target for non-HEAD' '
+       git symbolic-ref refs/heads/top-level FETCH_HEAD &&
+       git update-ref FETCH_HEAD HEAD &&
+       test_cmp_rev top-level HEAD
+'
+
 test_done
index 51f829162819740fea8b080db3be6b422452b366..e4627cf1b61f0b3128a79e28f15a9d5e5693e30c 100755 (executable)
@@ -5,6 +5,7 @@ test_description='test main ref store api'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 RUN="test-tool ref-store main"
index ad8006c81397336fc5919858d0b13ec4d564b723..05b1881c5911780b53a3d882dbe1a4dfae0034d8 100755 (executable)
@@ -5,6 +5,7 @@ test_description='test worktree ref store api'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 RWT="test-tool ref-store worktree:wt"
index d51ecd5e9250f004e0b0c894b308172237e5666c..2268bca3c11ac8e2d73e3a399b8debc44fc712cb 100755 (executable)
@@ -4,6 +4,7 @@ test_description='Test reflog display routines'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'setup' '
index ab7f31f1dcd5b5b7224ded4099f8316171bdea7d..53c2aa10b72745f96c6619fc5f8e5c9f50a48d54 100755 (executable)
@@ -364,6 +364,20 @@ test_expect_success 'tree entry with type mismatch' '
        test_i18ngrep ! "dangling blob" out
 '
 
+test_expect_success 'tree entry with bogus mode' '
+       test_when_finished "remove_object \$blob" &&
+       test_when_finished "remove_object \$tree" &&
+       blob=$(echo blob | git hash-object -w --stdin) &&
+       blob_oct=$(echo $blob | hex2oct) &&
+       tree=$(printf "100000 foo\0${blob_oct}" |
+              git hash-object -t tree --stdin -w --literally) &&
+       git fsck 2>err &&
+       cat >expect <<-EOF &&
+       warning in tree $tree: badFilemode: contains bad file modes
+       EOF
+       test_cmp expect err
+'
+
 test_expect_success 'tag pointing to nonexistent' '
        badoid=$(test_oid deadbeef) &&
        cat >invalid-tag <<-EOF &&
index ba43168d1237ad5216e3ba5ed22069323fbf93b0..bc136833c1098d1c4e43d4b05cfadc7bef50b24c 100755 (executable)
@@ -132,7 +132,7 @@ test_expect_success 'use --default' '
        test_must_fail git rev-parse --verify --default bar
 '
 
-test_expect_success !SANITIZE_LEAK 'main@{n} for various n' '
+test_expect_success 'main@{n} for various n' '
        git reflog >out &&
        N=$(wc -l <out) &&
        Nm1=$(($N-1)) &&
index 5dc221ef382df13089b84dcb03002c2a8f19a9ba..d8fa489998acc5984da7e21a8a2adf43cfe88cee 100755 (executable)
@@ -5,6 +5,7 @@
 
 test_description='racy split index'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'setup' '
index 210f4298872f947be121cea1ce02b468a3421308..64096adac7e108cb605cce1d7f3a40a28b87c676 100755 (executable)
@@ -151,4 +151,30 @@ test_expect_success TTY 'git commit: stdout and stderr are connected to a TTY' '
        test_hook_tty commit -m"B.new"
 '
 
+test_expect_success 'git hook run a hook with a bad shebang' '
+       test_when_finished "rm -rf bad-hooks" &&
+       mkdir bad-hooks &&
+       write_script bad-hooks/test-hook "/bad/path/no/spaces" </dev/null &&
+
+       # TODO: We should emit the same (or at least a more similar)
+       # error on Windows and !Windows. See the OS-specific code in
+       # start_command()
+       if test_have_prereq !WINDOWS
+       then
+               cat >expect <<-\EOF
+               fatal: cannot run bad-hooks/test-hook: ...
+               EOF
+       else
+               cat >expect <<-\EOF
+               error: cannot spawn bad-hooks/test-hook: ...
+               EOF
+       fi &&
+       test_expect_code 1 git \
+               -c core.hooksPath=bad-hooks \
+               hook run test-hook >out 2>err &&
+       test_must_be_empty out &&
+       sed -e "s/test-hook: .*/test-hook: .../" <err >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 7705e3a31708355c05271aa9d5eafa5af359043f..5d119871d416cd4e79b3bff7c56aeac729b276e6 100755 (executable)
@@ -3,6 +3,7 @@
 test_description='basic checkout-index tests
 '
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'checkout-index --gobbledegook' '
index bc46713a43e24193bae9a4be204455eb5c066501..2eab6474f8d0f4b3116455c95cb71cd69f78a311 100755 (executable)
@@ -4,6 +4,7 @@ test_description='checkout into detached HEAD state'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 check_detached () {
index 7b327b754494a8ebb27bb11c7f70649063ccd8d2..81e772fb4ebbf1d6c4d156560aa10dfdb02a0852 100755 (executable)
@@ -7,6 +7,7 @@ Ensures that checkout -m on a resolved file restores the conflicted file'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success setup '
index 43d950de6400fca709286c5316e71e037791f04f..98265ba1b495eb91df0fa60467ebf0ac932cf1b5 100755 (executable)
@@ -17,6 +17,7 @@ outside the repository.  Two instances for which this can occur are tested:
           repository can be added to the index.
        '
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success '1a: setup--config worktree' '
index e07ac6c6dce93f50ff1900fc42e151f5370935a4..1ed0aa967ece5a38cf49bc43178e4fba187d4cd4 100755 (executable)
@@ -103,7 +103,7 @@ test_expect_success 'git ls-files --others with various exclude options.' '
        test_cmp expect output
 '
 
-test_expect_success !SANITIZE_LEAK 'restore gitignore' '
+test_expect_success 'restore gitignore' '
        git checkout --ignore-skip-worktree-bits $allignores &&
        rm .git/index
 '
@@ -126,7 +126,7 @@ cat > expect << EOF
 #      three/
 EOF
 
-test_expect_success !SANITIZE_LEAK 'git status honors core.excludesfile' \
+test_expect_success 'git status honors core.excludesfile' \
        'test_cmp expect output'
 
 test_expect_success 'trailing slash in exclude allows directory match(1)' '
index 2682b1f43a666564a6f74bd20deca547f721ac34..190e2f6eed758229579a85118106e80d05c8d7d8 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='git ls-files --deduplicate test'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'setup' '
diff --git a/t/t3013-ls-files-format.sh b/t/t3013-ls-files-format.sh
new file mode 100755 (executable)
index 0000000..efb7450
--- /dev/null
@@ -0,0 +1,95 @@
+#!/bin/sh
+
+test_description='git ls-files --format test'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+for flag in -s -o -k -t --resolve-undo --deduplicate --eol
+do
+       test_expect_success "usage: --format is incompatible with $flag" '
+               test_expect_code 129 git ls-files --format="%(objectname)" $flag
+       '
+done
+
+test_expect_success 'setup' '
+       printf "LINEONE\nLINETWO\nLINETHREE\n" >o1.txt &&
+       printf "LINEONE\r\nLINETWO\r\nLINETHREE\r\n" >o2.txt &&
+       printf "LINEONE\r\nLINETWO\nLINETHREE\n" >o3.txt &&
+       git add o?.txt &&
+       oid=$(git hash-object o1.txt) &&
+       git update-index --add --cacheinfo 120000 $oid o4.txt &&
+       git update-index --add --cacheinfo 160000 $oid o5.txt &&
+       git update-index --add --cacheinfo 100755 $oid o6.txt &&
+       git commit -m base
+'
+
+test_expect_success 'git ls-files --format objectmode v.s. -s' '
+       git ls-files -s >files &&
+       cut -d" " -f1 files >expect &&
+       git ls-files --format="%(objectmode)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git ls-files --format objectname v.s. -s' '
+       git ls-files -s >files &&
+       cut -d" " -f2 files >expect &&
+       git ls-files --format="%(objectname)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git ls-files --format v.s. --eol' '
+       git ls-files --eol >tmp &&
+       sed -e "s/      / /g" -e "s/  */ /g" tmp >expect 2>err &&
+       test_must_be_empty err &&
+       git ls-files --format="i/%(eolinfo:index) w/%(eolinfo:worktree) attr/%(eolattr) %(path)" >actual 2>err &&
+       test_must_be_empty err &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git ls-files --format path v.s. -s' '
+       git ls-files -s >files &&
+       cut -f2 files >expect &&
+       git ls-files --format="%(path)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git ls-files --format with -m' '
+       echo change >o1.txt &&
+       cat >expect <<-\EOF &&
+       o1.txt
+       o4.txt
+       o5.txt
+       o6.txt
+       EOF
+       git ls-files --format="%(path)" -m >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git ls-files --format with -d' '
+       echo o7 >o7.txt &&
+       git add o7.txt &&
+       rm o7.txt &&
+       cat >expect <<-\EOF &&
+       o4.txt
+       o5.txt
+       o6.txt
+       o7.txt
+       EOF
+       git ls-files --format="%(path)" -d >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git ls-files --format v.s -s' '
+       git ls-files --stage >expect &&
+       git ls-files --format="%(objectmode) %(objectname) %(stage)%x09%(path)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git ls-files --format with --debug' '
+       git ls-files --debug >expect &&
+       git ls-files --format="%(path)" --debug >actual &&
+       test_cmp expect actual
+'
+
+test_done
index 64a9915761a82a6ea8d640dabfe3811db6450a5a..22ffe5bcb9908d914585dbc90e59194023bd9ff5 100755 (executable)
@@ -51,7 +51,7 @@ test_expect_success 'creating many notes with git-notes' '
        done
 '
 
-test_expect_success !SANITIZE_LEAK 'many notes created correctly with git-notes' '
+test_expect_success 'many notes created correctly with git-notes' '
        git log >output.raw &&
        grep "^    " output.raw >output &&
        i=$num_notes &&
index 1aa366a410e9a3e2ad4c2fa84431198fbb553a5f..ae316502c4531b7cdadfddff12e2f95ea7c9797c 100755 (executable)
@@ -4,6 +4,7 @@ test_description='Examples from the git-notes man page
 
 Make sure the manual is not full of lies.'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'setup' '
index b354fb39de839aba1506693ee4a0cd7d4967d656..3b7df9bed5ad3e740090e2dc9b3fecb40479a65f 100755 (executable)
@@ -766,6 +766,19 @@ test_expect_success 'detect bogus diffFilter output' '
        force_color test_must_fail git add -p <y
 '
 
+test_expect_success 'handle very large filtered diff' '
+       git reset --hard &&
+       # The specific number here is not important, but it must
+       # be large enough that the output of "git diff --color"
+       # fills up the pipe buffer. 10,000 results in ~200k of
+       # colored output.
+       test_seq 10000 >test &&
+       test_config interactive.diffFilter cat &&
+       printf y >y &&
+       force_color git add -p >output 2>&1 <y &&
+       git diff-files --exit-code -- test
+'
+
 test_expect_success 'diff.algorithm is passed to `git diff-files`' '
        git reset --hard &&
 
index 0276edbe3d389b70cace45906626276cce3db44b..4c661d4d54a779f8bc056c62e5d9e864401420c7 100755 (executable)
@@ -1,6 +1,8 @@
 #!/bin/sh
 
 test_description='Test ref-filter and pretty APIs for commit and tag messages using CRLF'
+
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 LIB_CRLF_BRANCHES=""
index ed461f481e2af5e3d73ac628eeee1fb52218d90b..5bc28ad9f042a0476d94d9e90e5b58073cc17f99 100755 (executable)
@@ -5,6 +5,7 @@ test_description='Return value of diffs'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'setup' '
index 858a5522f96b63b971daf85f20e5d66aaebc48e6..c1ac09ecc7140a3dcfcdf906bb4533ba131881de 100755 (executable)
@@ -33,7 +33,7 @@ test_expect_success 'GIT_EXTERNAL_DIFF environment' '
 
 '
 
-test_expect_success !SANITIZE_LEAK 'GIT_EXTERNAL_DIFF environment should apply only to diff' '
+test_expect_success 'GIT_EXTERNAL_DIFF environment should apply only to diff' '
        GIT_EXTERNAL_DIFF=echo git log -p -1 HEAD >out &&
        grep "^diff --git a/file b/file" out
 
@@ -74,7 +74,7 @@ test_expect_success 'diff.external' '
        test_cmp expect actual
 '
 
-test_expect_success !SANITIZE_LEAK 'diff.external should apply only to diff' '
+test_expect_success 'diff.external should apply only to diff' '
        test_config diff.external echo &&
        git log -p -1 HEAD >out &&
        grep "^diff --git a/file b/file" out
index 4838a1df8b4369dc5024cdd7929d851b76482805..725278ad19c720468113659dbcc63aa013ac7de0 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='diff function context'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 dir="$TEST_DIRECTORY/t4051"
index 04b8a1542a8ec3ad2ffc28964f21940d034c2ed6..9a7505cbb8bf900510e8be4fc1a636124deec366 100755 (executable)
@@ -5,6 +5,7 @@ test_description='combined diff show only paths that are different to all parent
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 # verify that diffc.expect matches output of
index 35f94957fceb2b02df9846a256f925833c6b4ba0..9e7cac68b1ce3735237661e14e6c2c8c84cf4665 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='remerge-diff handling'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 # This test is ort-specific
index da3e64f8110d54d7243c017a9a614baa7098142f..8ff364076673747adaaa74aec3f0a966caf033e5 100755 (executable)
@@ -7,6 +7,7 @@ test_description='git apply should not get confused with type changes.
 
 '
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'setup repository and commits' '
index 6e66352558212e9a40404ef892a0944ab3d09db3..f0aaa1fa02a56447d91e0815f71fed0a92989148 100755 (executable)
@@ -2112,9 +2112,9 @@ test_expect_success REFFILES 'log diagnoses bogus HEAD hash' '
        test_i18ngrep broken stderr
 '
 
-test_expect_success 'log diagnoses bogus HEAD symref' '
+test_expect_success REFFILES 'log diagnoses bogus HEAD symref' '
        git init empty &&
-       git --git-dir empty/.git symbolic-ref HEAD refs/heads/invalid.lock &&
+       echo "ref: refs/heads/invalid.lock" > empty/.git/HEAD &&
        test_must_fail git -C empty log 2>stderr &&
        test_i18ngrep broken stderr &&
        test_must_fail git -C empty log --default totally-bogus 2>stderr &&
index 0b2d21ec5510158cc9bb5b861a94bdfc6eccea12..cd1cab3e54b9170d5751279bd68e02dbc3cbcad2 100755 (executable)
@@ -963,4 +963,63 @@ test_expect_success SYMLINKS 'symlinks not respected in-tree' '
        test_cmp expect actual
 '
 
+test_expect_success 'prepare for cat-file --mailmap' '
+       rm -f .mailmap &&
+       git commit --allow-empty -m foo --author="Orig <orig@example.com>"
+'
+
+test_expect_success '--no-use-mailmap disables mailmap in cat-file' '
+       test_when_finished "rm .mailmap" &&
+       cat >.mailmap <<-EOF &&
+       A U Thor <author@example.com> Orig <orig@example.com>
+       EOF
+       cat >expect <<-EOF &&
+       author Orig <orig@example.com>
+       EOF
+       git cat-file --no-use-mailmap commit HEAD >log &&
+       sed -n "/^author /s/\([^>]*>\).*/\1/p" log >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--use-mailmap enables mailmap in cat-file' '
+       test_when_finished "rm .mailmap" &&
+       cat >.mailmap <<-EOF &&
+       A U Thor <author@example.com> Orig <orig@example.com>
+       EOF
+       cat >expect <<-EOF &&
+       author A U Thor <author@example.com>
+       EOF
+       git cat-file --use-mailmap commit HEAD >log &&
+       sed -n "/^author /s/\([^>]*>\).*/\1/p" log >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--no-mailmap disables mailmap in cat-file for annotated tag objects' '
+       test_when_finished "rm .mailmap" &&
+       cat >.mailmap <<-EOF &&
+       Orig <orig@example.com> C O Mitter <committer@example.com>
+       EOF
+       cat >expect <<-EOF &&
+       tagger C O Mitter <committer@example.com>
+       EOF
+       git tag -a -m "annotated tag" v1 &&
+       git cat-file --no-mailmap -p v1 >log &&
+       sed -n "/^tagger /s/\([^>]*>\).*/\1/p" log >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--mailmap enables mailmap in cat-file for annotated tag objects' '
+       test_when_finished "rm .mailmap" &&
+       cat >.mailmap <<-EOF &&
+       Orig <orig@example.com> C O Mitter <committer@example.com>
+       EOF
+       cat >expect <<-EOF &&
+       tagger Orig <orig@example.com>
+       EOF
+       git tag -a -m "annotated tag" v2 &&
+       git cat-file --mailmap -p v2 >log &&
+       sed -n "/^tagger /s/\([^>]*>\).*/\1/p" log >actual &&
+       test_cmp expect actual
+'
+
 test_done
index f091259a55eba49fd632694b08495a4b8e8b0429..a243e3c5176b4ab21defc3fc419d16cbbad618a3 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='git merge-tree --write-tree'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 # This test is ort-specific
index 8bacd96275b0ac881b9202ecaff83394ca562dd2..c80ea9e8b71ee8707c8c1cc4020fb83a1b90029f 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='pack-object compression configuration'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success setup '
index be0b5641ffc39c65a3f790ed8edda2e227201540..1b0cd82359dcb902cbe62a1dbd5b85d8a767185b 100755 (executable)
@@ -812,4 +812,31 @@ test_expect_success 'set up and verify repo with generation data overflow chunk'
 
 graph_git_behavior 'generation data overflow chunk repo' repo left right
 
+test_expect_success 'overflow during generation version upgrade' '
+       git init overflow-v2-upgrade &&
+       (
+               cd overflow-v2-upgrade &&
+
+               # This commit will have a date at two seconds past the Epoch,
+               # and a (v1) generation number of 1, since it is a root commit.
+               #
+               # The offset will then be computed as 1-2, which will underflow
+               # to 2^31, which is greater than the v2 offset small limit of
+               # 2^31-1.
+               #
+               # This is sufficient to need a large offset table for the v2
+               # generation numbers.
+               test_commit --date "@2 +0000" base &&
+               git repack -d &&
+
+               # Test that upgrading from generation v1 to v2 correctly
+               # produces the overflow table.
+               git -c commitGraph.generationVersion=1 commit-graph write &&
+               git -c commitGraph.generationVersion=2 commit-graph write \
+                       --changed-paths &&
+
+               git rev-list --all
+       )
+'
+
 test_done
index f785cb061736170cc57708ef0aacf59f606280ec..8c8af99b844be1ad571f55364891db10fc172d98 100755 (executable)
@@ -5,6 +5,7 @@
 
 test_description='git unpack-objects with large objects'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 prepare_dest () {
@@ -70,9 +71,15 @@ test_expect_success 'unpack big object in stream (core.fsyncmethod=batch)' '
        GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
        GIT_TEST_FSYNC=true \
                git -C dest.git $BATCH_CONFIGURATION unpack-objects <pack-$PACK.pack &&
-       check_fsync_events trace2.txt <<-\EOF &&
+       if grep "core.fsyncMethod = batch is unsupported" trace2.txt
+       then
+               flush_count=7
+       else
+               flush_count=1
+       fi &&
+       check_fsync_events trace2.txt <<-EOF &&
        "key":"fsync/writeout-only","value":"6"
-       "key":"fsync/hardware-flush","value":"1"
+       "key":"fsync/hardware-flush","value":"$flush_count"
        EOF
 
        test_dir_is_empty dest.git/objects/pack &&
@@ -87,7 +94,7 @@ test_expect_success 'do not unpack existing large objects' '
 
        # The destination came up with the exact same pack...
        DEST_PACK=$(echo dest.git/objects/pack/pack-*.pack) &&
-       test_cmp pack-$PACK.pack $DEST_PACK &&
+       cmp pack-$PACK.pack $DEST_PACK &&
 
        # ...and wrote no loose objects
        test_stdout_line_count = 0 find dest.git/objects -type f ! -name "pack-*"
index 915af2de95e162a9581ca23a6efb229737e665a6..46ebdfbeebaf522281e8e4aa4c68e9fb33ca8513 100755 (executable)
@@ -7,6 +7,7 @@ test_description='Test the post-merge hook.'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success setup '
index ee6d2dde9f35677fb9c83a228839bb7ad405a4fa..d18f2823d86e8b94c5331b1087f962469fc91f2c 100755 (executable)
@@ -407,6 +407,7 @@ test_expect_success 'in_vain not triggered before first ACK' '
 '
 
 test_expect_success 'in_vain resetted upon ACK' '
+       test_when_finished rm -f log trace2 &&
        rm -rf myserver myclient &&
        git init myserver &&
 
@@ -432,7 +433,8 @@ test_expect_success 'in_vain resetted upon ACK' '
        # first. The 256th commit is common between the client and the server,
        # and should reset in_vain. This allows negotiation to continue until
        # the client reports that first_anotherbranch_commit is common.
-       git -C myclient fetch --progress origin main 2>log &&
+       GIT_TRACE2_EVENT="$(pwd)/trace2" git -C myclient fetch --progress origin main 2>log &&
+       grep \"key\":\"total_rounds\",\"value\":\"6\" trace2 &&
        test_i18ngrep "Total 3 " log
 '
 
index 195fc64dd44ae74c1546698e111f1a19c07dbb04..5ebbaa489689dce28d1b47e16b73e19e25183c5f 100755 (executable)
@@ -5,6 +5,7 @@ test_description='test automatic tag following'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 # End state of the repository:
index b0b795aca97f8614dcae4a4a52f9b811386d7deb..ac4099ca8931930989eece26fa735e2f6c00bbc3 100755 (executable)
@@ -352,4 +352,21 @@ test_expect_success \
        grep "Cannot demote unterminatedheader" act
 '
 
+test_expect_success 'badFilemode is not a strict error' '
+       git init --bare badmode.git &&
+       tree=$(
+               cd badmode.git &&
+               blob=$(echo blob | git hash-object -w --stdin | hex2oct) &&
+               printf "123456 foo\0${blob}" |
+               git hash-object -t tree --stdin -w --literally
+       ) &&
+
+       rm -rf dst.git &&
+       git init --bare dst.git &&
+       git -C dst.git config transfer.fsckObjects true &&
+
+       git -C badmode.git push ../dst.git $tree:refs/tags/tree 2>err &&
+       grep "$tree: badFilemode" err
+'
+
 test_done
index f3356f9ea8cc1d893f756bf9af9806d210904124..3211002d466867fb2abb99eeff897029a7fdfa71 100755 (executable)
@@ -200,7 +200,10 @@ test_expect_success 'push with negotiation' '
        test_commit -C testrepo unrelated_commit &&
        git -C testrepo config receive.hideRefs refs/remotes/origin/first_commit &&
        test_when_finished "rm event" &&
-       GIT_TRACE2_EVENT="$(pwd)/event" git -c protocol.version=2 -c push.negotiate=1 push testrepo refs/heads/main:refs/remotes/origin/main &&
+       GIT_TRACE2_EVENT="$(pwd)/event" \
+               git -c protocol.version=2 -c push.negotiate=1 \
+               push testrepo refs/heads/main:refs/remotes/origin/main &&
+       grep \"key\":\"total_rounds\",\"value\":\"1\" event &&
        grep_wrote 2 event # 1 commit, 1 tree
 '
 
@@ -224,7 +227,10 @@ test_expect_success 'push with negotiation does not attempt to fetch submodules'
        git push testrepo $the_first_commit:refs/remotes/origin/first_commit &&
        test_commit -C testrepo unrelated_commit &&
        git -C testrepo config receive.hideRefs refs/remotes/origin/first_commit &&
-       git -c submodule.recurse=true -c protocol.version=2 -c push.negotiate=1 push testrepo refs/heads/main:refs/remotes/origin/main 2>err &&
+       GIT_TRACE2_EVENT="$(pwd)/event"  git -c submodule.recurse=true \
+               -c protocol.version=2 -c push.negotiate=1 \
+               push testrepo refs/heads/main:refs/remotes/origin/main 2>err &&
+       grep \"key\":\"total_rounds\",\"value\":\"1\" event &&
        ! grep "Fetching submodule" err
 '
 
index 245532df8818ea53c0b1f83ddc8909f872904d7b..6a38294a47671dccdc81849e1385a8c6e0310488 100755 (executable)
@@ -181,8 +181,8 @@ test_expect_success 'no-op half-auth fetch does not require a password' '
        # This is not possible with protocol v2, since both objects and refs
        # are obtained from the "git-upload-pack" path. A solution to this is
        # to teach the server and client to be able to inline ls-refs requests
-       # as an Extra Parameter (see pack-protocol.txt), so that "info/refs"
-       # can serve refs, just like it does in protocol v0.
+       # as an Extra Parameter (see "git help gitformat-pack-protocol"), so that
+       # "info/refs" can serve refs, just like it does in protocol v0.
        GIT_TEST_PROTOCOL_VERSION=0 git --git-dir=half-auth fetch &&
        expect_askpass none
 '
index cf3be0584f40d13ba187cd8c04efc358910714a7..2e57de9c12a39a63b870079c17b014ad24fb93c3 100755 (executable)
@@ -743,7 +743,11 @@ test_expect_success 'batch missing blob request during checkout' '
 
        # Ensure that there is only one negotiation by checking that there is
        # only "done" line sent. ("done" marks the end of negotiation.)
-       GIT_TRACE_PACKET="$(pwd)/trace" git -C client checkout HEAD^ &&
+       GIT_TRACE_PACKET="$(pwd)/trace" \
+               GIT_TRACE2_EVENT="$(pwd)/trace2_event" \
+               git -C client -c trace2.eventNesting=5 checkout HEAD^ &&
+       grep \"key\":\"total_rounds\",\"value\":\"1\" trace2_event >trace_lines &&
+       test_line_count = 1 trace_lines &&
        grep "fetch> done" trace >done_lines &&
        test_line_count = 1 done_lines
 '
index 4a3778d04a82df6322048e34864578860b96f859..9aeacc2f6a5267cfdf8a05f2c924f4ecd04fe319 100755 (executable)
@@ -49,6 +49,13 @@ test_expect_success 'do partial clone 1' '
        test "$(git -C pc1 config --local remote.origin.partialclonefilter)" = "blob:none"
 '
 
+test_expect_success 'rev-list --missing=allow-promisor on partial clone' '
+       git -C pc1 rev-list --objects --missing=allow-promisor HEAD >actual &&
+       git -C pc1 rev-list --objects --missing=print HEAD >expect.raw &&
+       grep -v "^?" expect.raw >expect &&
+       test_cmp expect actual
+'
+
 test_expect_success 'verify that .promisor file contains refs fetched' '
        ls pc1/.git/objects/pack/pack-*.promisor >promisorlist &&
        test_line_count = 1 promisorlist &&
index 9d6cd7d98649c0fe0f40c474d5ed88528ac9ba8c..df74f80061c564b7f69961f0f3d665b1afca460f 100755 (executable)
@@ -229,14 +229,16 @@ test_expect_success 'setup repos for fetching with ref-in-want tests' '
 '
 
 test_expect_success 'fetching with exact OID' '
-       test_when_finished "rm -f log" &&
+       test_when_finished "rm -f log trace2" &&
 
        rm -rf local &&
        cp -r "$LOCAL_PRISTINE" local &&
        oid=$(git -C "$REPO" rev-parse d) &&
-       GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin \
+       GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE2_EVENT="$(pwd)/trace2" \
+               git -C local fetch origin \
                "$oid":refs/heads/actual &&
 
+       grep \"key\":\"total_rounds\",\"value\":\"2\" trace2 &&
        git -C "$REPO" rev-parse "d" >expected &&
        git -C local rev-parse refs/heads/actual >actual &&
        test_cmp expected actual &&
index cf0195e8263c6fdc25977f421252c41238f03419..4a9a4436e219c856095784324e4b097b3402ba29 100755 (executable)
@@ -17,7 +17,7 @@ test_expect_success 'setup unexpected non-blob entry' '
        broken_tree="$(git hash-object -w --literally -t tree broken-tree)"
 '
 
-test_expect_success !SANITIZE_LEAK 'TODO (should fail!): traverse unexpected non-blob entry (lone)' '
+test_expect_success 'TODO (should fail!): traverse unexpected non-blob entry (lone)' '
        sed "s/Z$//" >expect <<-EOF &&
        $broken_tree Z
        $tree foo
@@ -121,7 +121,7 @@ test_expect_success 'setup unexpected non-blob tag' '
        tag=$(git hash-object -w --literally -t tag broken-tag)
 '
 
-test_expect_success !SANITIZE_LEAK 'TODO (should fail!): traverse unexpected non-blob tag (lone)' '
+test_expect_success 'TODO (should fail!): traverse unexpected non-blob tag (lone)' '
        git rev-list --objects $tag
 '
 
index b4aef32b713ca00f1d8ad4a098f02ca9dc9be126..d59111dedec8020a138296928649eaff4de45694 100755 (executable)
@@ -48,4 +48,26 @@ check_du HEAD
 check_du --objects HEAD
 check_du --objects HEAD^..HEAD
 
+# As mentioned above, don't use hardcode sizes as actual size, but use the
+# output from git cat-file.
+test_expect_success 'rev-list --disk-usage=human' '
+       git rev-list --objects HEAD --disk-usage=human >actual &&
+       disk_usage_slow --objects HEAD >actual_size &&
+       grep "$(cat actual_size) bytes" actual
+'
+
+test_expect_success 'rev-list --disk-usage=human with bitmaps' '
+       git rev-list --objects HEAD --use-bitmap-index --disk-usage=human >actual &&
+       disk_usage_slow --objects HEAD >actual_size &&
+       grep "$(cat actual_size) bytes" actual
+'
+
+test_expect_success 'rev-list use --disk-usage unproperly' '
+       test_must_fail git rev-list --objects HEAD --disk-usage=typo 2>err &&
+       cat >expect <<-\EOF &&
+       fatal: invalid value for '\''--disk-usage=<format>'\'': '\''typo'\'', the only allowed format is '\''human'\''
+       EOF
+       test_cmp err expect
+'
+
 test_done
index 3a32b1a45cf8e4bf2a7b42eeec98923262b3e470..772238e582c6f11b81c12a519f81bbe48cecf635 100755 (executable)
@@ -210,7 +210,7 @@ test_expect_success 'updated working tree file should prevent the merge' '
        echo >>M one line addition &&
        cat M >M.saved &&
        git update-index M &&
-       test_expect_code 128 git pull --no-rebase . yellow &&
+       test_expect_code 2 git pull --no-rebase . yellow &&
        test_cmp M M.saved &&
        rm -f M.saved
 '
index b8735c6db4d7c9cc556231a5b0dab091f2f2eb61..36215518b6eb1a746ded0f9b475a7d3029b807a6 100755 (executable)
@@ -4,6 +4,7 @@ test_description='Test merge without common ancestors'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 # This scenario is based on a real-world repository of Shawn Pearce.
index 7435fce71e004095c3a9fe181b038ae77a800192..29e2b25ce5de25f6f97aa6ac967332e7603eb91e 100755 (executable)
@@ -11,6 +11,7 @@ if core.symlinks is false.'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'setup' '
index 0753fc95f45efb642543f9f23191d3430d4d6cde..e8a28717cece3248c2d9996e797dcfdb9af128eb 100755 (executable)
@@ -5,7 +5,6 @@ test_description='ask merge-recursive to merge binary files'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
-TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success setup '
index 7763c1ba98080d5d1d68e1009fb70f6c80cf479a..8a1ba6d23a7dc4a7ca7a56988f3d945d9af38619 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='merge fast-forward and up to date'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success setup '
index 6ae2489286c278f978c3a87f1015f16fe2bb005f..b6182723aae158acb4e56ab9018c9b30f4f86cd6 100755 (executable)
@@ -4,6 +4,7 @@ test_description='merge: handle file mode'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'set up mode change in one branch' '
index affea255fe92ca134a553d2a69362a8f8f7a8ab1..b4f4a313f486a583ca62268f2106948cca8d7266 100755 (executable)
@@ -11,6 +11,7 @@ test_description='merge conflict in crlf repo
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success setup '
index b6e424a427b5566b2edf48b3d0a29ad6a927f67e..a61f20c22fe62031da12af45bd8ca427782043bf 100755 (executable)
@@ -114,6 +114,39 @@ test_expect_success 'resolve, non-trivial' '
        test_path_is_missing .git/MERGE_HEAD
 '
 
+test_expect_success 'resolve, trivial, related file removed' '
+       git reset --hard &&
+       git checkout B^0 &&
+
+       git rm a &&
+       test_path_is_missing a &&
+
+       test_must_fail git merge -s resolve C^0 &&
+
+       test_path_is_missing a &&
+       test_path_is_missing .git/MERGE_HEAD
+'
+
+test_expect_success 'resolve, non-trivial, related file removed' '
+       git reset --hard &&
+       git checkout B^0 &&
+
+       git rm a &&
+       test_path_is_missing a &&
+
+       # We also ask for recursive in order to turn off the "allow_trivial"
+       # setting in builtin/merge.c, and ensure that resolve really does
+       # correctly fail the merge (I guess this also tests that recursive
+       # correctly fails the merge, but the main thing we are attempting
+       # to test here is resolve and are just using the side effect of
+       # adding recursive to ensure that resolve is actually tested rather
+       # than the trivial merge codepath)
+       test_must_fail git merge -s resolve -s recursive D^0 &&
+
+       test_path_is_missing a &&
+       test_path_is_missing .git/MERGE_HEAD
+'
+
 test_expect_success 'recursive' '
        git reset --hard &&
        git checkout B^0 &&
@@ -242,4 +275,36 @@ test_expect_success 'subtree' '
        test_path_is_missing .git/MERGE_HEAD
 '
 
+test_expect_success 'avoid failure due to stat-dirty files' '
+       git reset --hard &&
+       git checkout B^0 &&
+
+       # Make "a" be stat-dirty
+       test-tool chmtime =+1 a &&
+
+       # stat-dirty file should not prevent stash creation in builtin/merge.c
+       git merge -s resolve -s recursive D^0
+'
+
+test_expect_success 'with multiple strategies, recursive or ort failure do not early abort' '
+       git reset --hard &&
+       git checkout B^0 &&
+
+       test_seq 0 10 >a &&
+       git add a &&
+       git rev-parse :a >expect &&
+
+       sane_unset GIT_TEST_MERGE_ALGORITHM &&
+       test_must_fail git merge -s recursive -s ort -s octopus C^0 >output 2>&1 &&
+
+       grep "Trying merge strategy recursive..." output &&
+       grep "Trying merge strategy ort..." output &&
+       grep "Trying merge strategy octopus..." output &&
+       grep "No merge strategy handled the merge." output &&
+
+       # Changes to "a" should remain staged
+       git rev-parse :a >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 459b431a60d83c9f1e7c052b9c41491854f9997d..93cd2869b12897b5246a3a977f0cdaf1b2911663 100755 (executable)
@@ -4,6 +4,7 @@ test_description='Merge-recursive rename/delete conflict message'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'rename/delete' '
index 3824756a02ec31c228e2eeda92fc13a33b1e4b75..3fe14cd73e895fde51b4f711865553e7a142a2ea 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='merge-recursive backend test'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 #         A      <- create some files
index c253bf759ab12629ad4bba872af8f9bd51f78e1b..d5f3e0fed65b2167bd6b2ddb6a4b7ae4144f1604 100755 (executable)
@@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' '
         echo "file-c" > file-c &&
         git add file-c &&
         git commit -m "sub-c") &&
-       git commit -a -m "c" &&
+       git commit -a -m "c")
+'
 
+test_expect_success 'merging should conflict for non fast-forward' '
+       test_when_finished "git -C merge-search reset --hard" &&
+       (cd merge-search &&
+        git checkout -b test-nonforward-a b &&
+         if test "$GIT_TEST_MERGE_ALGORITHM" = ort
+         then
+               test_must_fail git merge c >actual &&
+               sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" &&
+               grep "$sub_expect" actual
+         else
+               test_must_fail git merge c 2> actual
+         fi)
+'
+
+test_expect_success 'finish setup for merge-search' '
+       (cd merge-search &&
        git checkout -b d a &&
        (cd sub &&
         git checkout -b sub-d sub-b &&
@@ -129,14 +146,16 @@ test_expect_success 'merge with one side as a fast-forward of the other' '
         test_cmp expect actual)
 '
 
-test_expect_success 'merging should conflict for non fast-forward' '
+test_expect_success 'merging should conflict for non fast-forward (resolution exists)' '
        (cd merge-search &&
-        git checkout -b test-nonforward b &&
+        git checkout -b test-nonforward-b b &&
         (cd sub &&
          git rev-parse --short sub-d > ../expect) &&
          if test "$GIT_TEST_MERGE_ALGORITHM" = ort
          then
-               test_must_fail git merge c >actual
+               test_must_fail git merge c >actual &&
+               sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" &&
+               grep "$sub_expect" actual
          else
                test_must_fail git merge c 2> actual
          fi &&
@@ -161,7 +180,9 @@ test_expect_success 'merging should fail for ambiguous common parent' '
         ) &&
         if test "$GIT_TEST_MERGE_ALGORITHM" = ort
         then
-               test_must_fail git merge c >actual
+               test_must_fail git merge c >actual &&
+               sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" &&
+               grep "$sub_expect" actual
         else
                test_must_fail git merge c 2> actual
         fi &&
@@ -205,7 +226,12 @@ test_expect_success 'merging should fail for changes that are backwards' '
        git commit -a -m "f" &&
 
        git checkout -b test-backward e &&
-       test_must_fail git merge f)
+       test_must_fail git merge f >actual &&
+       if test "$GIT_TEST_MERGE_ALGORITHM" = ort
+    then
+               sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" &&
+               grep "$sub_expect" actual
+       fi)
 '
 
 
@@ -476,4 +502,44 @@ test_expect_failure 'directory/submodule conflict; merge --abort works afterward
        )
 '
 
+# Setup:
+#   - Submodule has 2 commits: a and b
+#   - Superproject branch 'a' adds and commits submodule pointing to 'commit a'
+#   - Superproject branch 'b' adds and commits submodule pointing to 'commit b'
+# If these two branches are now merged, there is no merge base
+test_expect_success 'setup for null merge base' '
+       mkdir no-merge-base &&
+       (cd no-merge-base &&
+       git init &&
+       mkdir sub &&
+       (cd sub &&
+        git init &&
+        echo "file-a" > file-a &&
+        git add file-a &&
+        git commit -m "commit a") &&
+       git commit --allow-empty -m init &&
+       git branch init &&
+       git checkout -b a init &&
+       git add sub &&
+       git commit -m "a" &&
+       git switch main &&
+       (cd sub &&
+        echo "file-b" > file-b &&
+        git add file-b &&
+        git commit -m "commit b"))
+'
+
+test_expect_success 'merging should fail with no merge base' '
+       (cd no-merge-base &&
+       git checkout -b b init &&
+       git add sub &&
+       git commit -m "b" &&
+       test_must_fail git merge a >actual &&
+       if test "$GIT_TEST_MERGE_ALGORITHM" = ort
+    then
+               sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short HEAD^1)" &&
+               grep "$sub_expect" actual
+       fi)
+'
+
 test_done
index 5bfb027099a6d22849739075de82f3c0af7ca513..52cf0c87690be83e758b97c12b0ab00fa77613a2 100755 (executable)
@@ -47,6 +47,7 @@ test_expect_success 'untracked files overwritten by merge (fast and non-fast for
                export GIT_MERGE_VERBOSITY &&
                test_must_fail git merge branch 2>out2
        ) &&
+       echo "Merge with strategy ${GIT_TEST_MERGE_ALGORITHM:-ort} failed." >>expect &&
        test_cmp out2 expect &&
        git reset --hard HEAD^
 '
index d6cc69e0f2cbd576ff6ff81720de63f1e9cf86bf..f908a4d1abc5dc6608b4473937a614267013f3a2 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='git show'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success setup '
index 0f4344c55e6421d605ab7364bc2fedfe9165b02b..aaeb4a533440df495d99e3b123f1640afe4374e7 100755 (executable)
@@ -5,6 +5,7 @@ test_description='basic work tree status reporting'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success setup '
index 73709dbeee287932b3d925aad78f5c2d3f216a52..caf372a3d42ac362c96bbbdf340f27688a565130 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='git-status with core.ignorecase=true'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'status with hash collisions' '
index f5050b75b298e9af770a170a2d0e5ad3cf042ddd..8929ef481f926ce8eee1414b852c3e14538afa9c 100755 (executable)
@@ -986,4 +986,9 @@ test_expect_success '"status" after file replacement should be clean with UC=fal
        status_is_clean
 '
 
+test_expect_success 'empty repo (no index) and core.untrackedCache' '
+       git init emptyrepo &&
+       git -C emptyrepo -c core.untrackedCache=true write-tree
+'
+
 test_done
index 3d62e10b53fe16fd2eae0d0fa0363d8839f97b95..eb881be95b615f53867dbe8f56c6b0f9cc7a9d3c 100755 (executable)
@@ -5,6 +5,7 @@
 
 test_description='Tests for "git reset" with "--merge" and "--keep" options'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success setup '
index ce421ad5ac4b3a17d0e347a7d86a386e2b14547b..78f25c1c7ead9820ed647711e4749d7e9d444c52 100755 (executable)
@@ -5,6 +5,7 @@
 
 test_description='Tests to check that "reset" options follow a known table'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 
index 8e32f190077474274dc5046df5a64f837ee696f3..ebeca12a71115f60d10fa3ffa4725b1c7080f6f0 100755 (executable)
@@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' '
        test_tick &&
        git commit -m fourth &&
 
-       test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 &&
+       test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output &&
        git ls-files -s submodule >actual &&
        (
                cd submodule &&
@@ -112,7 +112,12 @@ test_expect_success 'rebasing submodule that should conflict' '
                echo "160000 $(git rev-parse HEAD^^) 2  submodule" &&
                echo "160000 $(git rev-parse HEAD) 3    submodule"
        ) >expect &&
-       test_cmp expect actual
+       test_cmp expect actual &&
+       if test "$GIT_TEST_MERGE_ALGORITHM" = ort
+    then
+               sub_expect="go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)" &&
+               grep "$sub_expect" actual_output
+       fi
 '
 
 test_done
index ad1eb64ba0db16d4f1452961c0a226fdf2e5cce0..aa004b70a8d1f1ab093b962a232524eaf4e851a1 100755 (executable)
@@ -5,6 +5,7 @@ test_description='pre-commit and pre-merge-commit hooks'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'root commit' '
diff --git a/t/t7607-merge-state.sh b/t/t7607-merge-state.sh
new file mode 100755 (executable)
index 0000000..89a62ac
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+test_description="Test that merge state is as expected after failed merge"
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+. ./test-lib.sh
+
+test_expect_success 'Ensure we restore original state if no merge strategy handles it' '
+       test_commit --no-tag "Initial" base base &&
+
+       for b in branch1 branch2 branch3
+       do
+               git checkout -b $b main &&
+               test_commit --no-tag "Change on $b" base $b || return 1
+       done &&
+
+       git checkout branch1 &&
+       # This is a merge that octopus cannot handle.  Note, that it does not
+       # just hit conflicts, it completely fails and says that it cannot
+       # handle this type of merge.
+       test_expect_code 2 git merge branch2 branch3 >output 2>&1 &&
+       grep "fatal: merge program failed" output &&
+       grep "Should not be doing an octopus" output &&
+
+       # Make sure we did not leave stray changes around when no appropriate
+       # merge strategy was found
+       git diff --exit-code --name-status &&
+       test_path_is_missing .git/MERGE_HEAD
+'
+
+test_done
index 330d6d603d77236788ee932cdcc288731a7aa388..8b1c3bd39f2249417099ea2f24218268925909af 100755 (executable)
@@ -4,6 +4,7 @@ test_description='git mergetool
 
 Testing basic merge tools options'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'mergetool --tool=vimdiff creates the expected layout' '
index 7c5b847f58424dc62c330674971d2d849f77ac4e..fea41b3c3606df1fc6d8111cdd74522532ca8b0b 100755 (executable)
@@ -8,7 +8,6 @@ test_description='git svn basic tests'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
-TEST_FAILS_SANITIZE_LEAK=true
 . ./lib-git-svn.sh
 
 prepare_utf8_locale
index 527ba3d29322a14d609ea57e3740672f144f3182..0fc289ae0f02c064586b5ff9f1cab0ff0976814f 100755 (executable)
@@ -2,7 +2,6 @@
 
 test_description='git svn authorship'
 
-TEST_FAILS_SANITIZE_LEAK=true
 . ./lib-git-svn.sh
 
 test_expect_success 'setup svn repository' '
index e2aa8ed88a96ec0b50fb33f31e2e6d232895067c..b3ce033a0d3dec9c27b3a5e9114e07f18f20f04d 100755 (executable)
@@ -4,7 +4,6 @@
 
 test_description='git svn dcommit --interactive series'
 
-TEST_FAILS_SANITIZE_LEAK=true
 . ./lib-git-svn.sh
 
 test_expect_success 'initialize repo' '
index 102c133112c7149258d123b95acec807006890b7..4aa5d90d328aca4adcac5f0d3a2ee4946a393812 100755 (executable)
@@ -4,17 +4,12 @@
 #
 
 test_description='perl interface (Git.pm)'
-. ./test-lib.sh
 
-if ! test_have_prereq PERL; then
-       skip_all='skipping perl interface tests, perl not available'
-       test_done
-fi
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-perl.sh
 
-perl -MTest::More -e 0 2>/dev/null || {
-       skip_all="Perl Test::More unavailable, skipping test"
-       test_done
-}
+skip_all_if_no_Test_More
 
 # set up test repository
 
@@ -50,11 +45,9 @@ test_expect_success \
      git config --add test.pathmulti bar
      '
 
-# The external test will outputs its own plan
-test_external_has_tap=1
-
-test_external_without_stderr \
-    'Perl API' \
-    perl "$TEST_DIRECTORY"/t9700/test.pl
+test_expect_success 'use t9700/test.pl to test Git.pm' '
+       "$PERL_PATH" "$TEST_DIRECTORY"/t9700/test.pl 2>stderr &&
+       test_must_be_empty stderr
+'
 
 test_done
index de7152f82713bf797aecd3f53bce6ef006524b7b..19f56e5680f678c22fc80a63907c804448c4b445 100755 (executable)
@@ -5,6 +5,7 @@ test_description='git web--browse basic tests
 
 This test checks that git web--browse can handle various valid URLs.'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_web_browse () {
index 8c44856eaec0d8fed84f5a4959edce275c8b79eb..c6479f24eb5ac291a29664903b3b6393d04748c6 100644 (file)
@@ -633,7 +633,7 @@ test_hook () {
 # - Explicitly using test_have_prereq.
 #
 # - Implicitly by specifying the prerequisite tag in the calls to
-#   test_expect_{success,failure} and test_external{,_without_stderr}.
+#   test_expect_{success,failure}
 #
 # The single parameter is the prerequisite tag (a simple word, in all
 # capital letters by convention).
@@ -835,93 +835,6 @@ test_expect_success () {
        test_finish_
 }
 
-# test_external runs external test scripts that provide continuous
-# test output about their progress, and succeeds/fails on
-# zero/non-zero exit code.  It outputs the test output on stdout even
-# in non-verbose mode, and announces the external script with "# run
-# <n>: ..." before running it.  When providing relative paths, keep in
-# mind that all scripts run in "trash directory".
-# Usage: test_external description command arguments...
-# Example: test_external 'Perl API' perl ../path/to/test.pl
-test_external () {
-       test "$#" = 4 && { test_prereq=$1; shift; } || test_prereq=
-       test "$#" = 3 ||
-       BUG "not 3 or 4 parameters to test_external"
-       descr="$1"
-       shift
-       test_verify_prereq
-       export test_prereq
-       if ! test_skip "$descr" "$@"
-       then
-               # Announce the script to reduce confusion about the
-               # test output that follows.
-               say_color "" "# run $test_count: $descr ($*)"
-               # Export TEST_DIRECTORY, TRASH_DIRECTORY and GIT_TEST_LONG
-               # to be able to use them in script
-               export TEST_DIRECTORY TRASH_DIRECTORY GIT_TEST_LONG
-               # Run command; redirect its stderr to &4 as in
-               # test_run_, but keep its stdout on our stdout even in
-               # non-verbose mode.
-               "$@" 2>&4
-               if test "$?" = 0
-               then
-                       if test $test_external_has_tap -eq 0; then
-                               test_ok_ "$descr"
-                       else
-                               say_color "" "# test_external test $descr was ok"
-                               test_success=$(($test_success + 1))
-                       fi
-               else
-                       if test $test_external_has_tap -eq 0; then
-                               test_failure_ "$descr" "$@"
-                       else
-                               say_color error "# test_external test $descr failed: $@"
-                               test_failure=$(($test_failure + 1))
-                       fi
-               fi
-       fi
-}
-
-# Like test_external, but in addition tests that the command generated
-# no output on stderr.
-test_external_without_stderr () {
-       # The temporary file has no (and must have no) security
-       # implications.
-       tmp=${TMPDIR:-/tmp}
-       stderr="$tmp/git-external-stderr.$$.tmp"
-       test_external "$@" 4> "$stderr"
-       test -f "$stderr" || error "Internal error: $stderr disappeared."
-       descr="no stderr: $1"
-       shift
-       say >&3 "# expecting no stderr from previous command"
-       if test ! -s "$stderr"
-       then
-               rm "$stderr"
-
-               if test $test_external_has_tap -eq 0; then
-                       test_ok_ "$descr"
-               else
-                       say_color "" "# test_external_without_stderr test $descr was ok"
-                       test_success=$(($test_success + 1))
-               fi
-       else
-               if test "$verbose" = t
-               then
-                       output=$(echo; echo "# Stderr is:"; cat "$stderr")
-               else
-                       output=
-               fi
-               # rm first in case test_failure exits.
-               rm "$stderr"
-               if test $test_external_has_tap -eq 0; then
-                       test_failure_ "$descr" "$@" "$output"
-               else
-                       say_color error "# test_external_without_stderr test $descr failed: $@: $output"
-                       test_failure=$(($test_failure + 1))
-               fi
-       fi
-}
-
 # debugging-friendly alternatives to "test [-f|-d|-e]"
 # The commands test the existence or non-existence of $1
 test_path_is_file () {
index 7726d1da88a52f60408bc3d8223923533f23e627..377cc1c1203d6fdfc7418f4ce189bb313cfb0b75 100644 (file)
@@ -238,6 +238,9 @@ parse_option () {
                        ;;
                esac
                ;;
+       --invert-exit-code)
+               invert_exit_code=t
+               ;;
        *)
                echo "error: unknown test option '$opt'" >&2; exit 1 ;;
        esac
@@ -302,6 +305,11 @@ TEST_NUMBER="${TEST_NAME%%-*}"
 TEST_NUMBER="${TEST_NUMBER#t}"
 TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results"
 TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME$TEST_STRESS_JOB_SFX"
+TEST_RESULTS_SAN_FILE_PFX=trace
+TEST_RESULTS_SAN_DIR_SFX=leak
+TEST_RESULTS_SAN_FILE=
+TEST_RESULTS_SAN_DIR="$TEST_RESULTS_DIR/$TEST_NAME.$TEST_RESULTS_SAN_DIR_SFX"
+TEST_RESULTS_SAN_DIR_NR_LEAKS_STARTUP=
 TRASH_DIRECTORY="trash directory.$TEST_NAME$TEST_STRESS_JOB_SFX"
 test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY"
 case "$TRASH_DIRECTORY" in
@@ -309,6 +317,16 @@ case "$TRASH_DIRECTORY" in
  *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;;
 esac
 
+# Utility functions using $TEST_RESULTS_* variables
+nr_san_dir_leaks_ () {
+       # stderr piped to /dev/null because the directory may have
+       # been "rmdir"'d already.
+       find "$TEST_RESULTS_SAN_DIR" \
+               -type f \
+               -name "$TEST_RESULTS_SAN_FILE_PFX.*" 2>/dev/null |
+       wc -l
+}
+
 # If --stress was passed, run this test repeatedly in several parallel loops.
 if test "$GIT_TEST_STRESS_STARTED" = "done"
 then
@@ -557,14 +575,19 @@ then
                : nothing
        }
 else
+       _USE_GLIBC_TUNABLES=
+       if _GLIBC_VERSION=$(getconf GNU_LIBC_VERSION 2>/dev/null) &&
+          _GLIBC_VERSION=${_GLIBC_VERSION#"glibc "} &&
+          expr 2.34 \<= "$_GLIBC_VERSION" >/dev/null
+       then
+               _USE_GLIBC_TUNABLES=YesPlease
+       fi
        setup_malloc_check () {
                local g
                local t
                MALLOC_CHECK_=3 MALLOC_PERTURB_=165
                export MALLOC_CHECK_ MALLOC_PERTURB_
-               if _GLIBC_VERSION=$(getconf GNU_LIBC_VERSION 2>/dev/null) &&
-                  _GLIBC_VERSION=${_GLIBC_VERSION#"glibc "} &&
-                  expr 2.34 \<= "$_GLIBC_VERSION" >/dev/null
+               if test -n "$_USE_GLIBC_TUNABLES"
                then
                        g=
                        LD_PRELOAD="libc_malloc_debug.so.0"
@@ -788,15 +811,31 @@ test_ok_ () {
        finalize_test_case_output ok "$@"
 }
 
+_invert_exit_code_failure_end_blurb () {
+       say_color warn "# faked up failures as TODO & now exiting with 0 due to --invert-exit-code"
+}
+
 test_failure_ () {
        failure_label=$1
        test_failure=$(($test_failure + 1))
-       say_color error "not ok $test_count - $1"
+       local pfx=""
+       if test -n "$invert_exit_code" # && test -n "$HARNESS_ACTIVE"
+       then
+               pfx="# TODO induced breakage (--invert-exit-code):"
+       fi
+       say_color error "not ok $test_count - ${pfx:+$pfx }$1"
        shift
        printf '%s\n' "$*" | sed -e 's/^/#      /'
        if test -n "$immediate"
        then
                say_color error "1..$test_count"
+               if test -n "$invert_exit_code"
+               then
+                       finalize_test_output
+                       _invert_exit_code_failure_end_blurb
+                       GIT_EXIT_OK=t
+                       exit 0
+               fi
                _error_exit
        fi
        finalize_test_case_output failure "$failure_label" "$@"
@@ -804,14 +843,14 @@ test_failure_ () {
 
 test_known_broken_ok_ () {
        test_fixed=$(($test_fixed+1))
-       say_color error "ok $test_count - $@ # TODO known breakage vanished"
-       finalize_test_case_output fixed "$@"
+       say_color error "ok $test_count - $1 # TODO known breakage vanished"
+       finalize_test_case_output fixed "$1"
 }
 
 test_known_broken_failure_ () {
        test_broken=$(($test_broken+1))
-       say_color warn "not ok $test_count - $@ # TODO known breakage"
-       finalize_test_case_output broken "$@"
+       say_color warn "not ok $test_count - $1 # TODO known breakage"
+       finalize_test_case_output broken "$1"
 }
 
 test_debug () {
@@ -1168,9 +1207,67 @@ test_atexit_handler () {
        teardown_malloc_check
 }
 
-test_done () {
-       GIT_EXIT_OK=t
+sanitize_leak_log_message_ () {
+       local new="$1" &&
+       local old="$2" &&
+       local file="$3" &&
+
+       printf "With SANITIZE=leak at exit we have %d leak logs, but started with %d
+
+This means that we have a blindspot where git is leaking but we're
+losing the exit code somewhere, or not propagating it appropriately
+upwards!
+
+See the logs at \"%s.*\";
+those logs are reproduced below." \
+              "$new" "$old" "$file"
+}
+
+check_test_results_san_file_ () {
+       if test -z "$TEST_RESULTS_SAN_FILE"
+       then
+               return
+       fi &&
+       local old="$TEST_RESULTS_SAN_DIR_NR_LEAKS_STARTUP" &&
+       local new="$(nr_san_dir_leaks_)" &&
+
+       if test $new -le $old
+       then
+               return
+       fi &&
+       local out="$(sanitize_leak_log_message_ "$new" "$old" "$TEST_RESULTS_SAN_FILE")" &&
+       say_color error "$out" &&
+       if test "$old" != 0
+       then
+               echo &&
+               say_color error "The logs include output from past runs to avoid" &&
+               say_color error "that remove 'test-results' between runs."
+       fi &&
+       say_color error "$(cat "$TEST_RESULTS_SAN_FILE".*)" &&
 
+       if test -n "$passes_sanitize_leak" && test "$test_failure" = 0
+       then
+               say "As TEST_PASSES_SANITIZE_LEAK=true and our logs show we're leaking, exit non-zero!" &&
+               invert_exit_code=t
+       elif test -n "$passes_sanitize_leak"
+       then
+               say "As TEST_PASSES_SANITIZE_LEAK=true and our logs show we're leaking, and we're failing for other reasons too..." &&
+               invert_exit_code=
+       elif test -n "$sanitize_leak_check" && test "$test_failure" = 0
+       then
+               say "As TEST_PASSES_SANITIZE_LEAK=true isn't set the above leak is 'ok' with GIT_TEST_PASSING_SANITIZE_LEAK=check" &&
+               invert_exit_code=
+       elif test -n "$sanitize_leak_check"
+       then
+               say "As TEST_PASSES_SANITIZE_LEAK=true isn't set the above leak is 'ok' with GIT_TEST_PASSING_SANITIZE_LEAK=check" &&
+               invert_exit_code=t
+       else
+               say "With GIT_TEST_SANITIZE_LEAK_LOG=true our logs revealed a memory leak, exit non-zero!" &&
+               invert_exit_code=t
+       fi
+}
+
+test_done () {
        # Run the atexit commands _before_ the trash directory is
        # removed, so the commands can access pidfiles and socket files.
        test_atexit_handler
@@ -1210,28 +1307,32 @@ test_done () {
        fi
        case "$test_failure" in
        0)
-               if test $test_external_has_tap -eq 0
+               if test $test_remaining -gt 0
                then
-                       if test $test_remaining -gt 0
-                       then
-                               say_color pass "# passed all $msg"
-                       fi
-
-                       # Maybe print SKIP message
-                       test -z "$skip_all" || skip_all="# SKIP $skip_all"
-                       case "$test_count" in
-                       0)
-                               say "1..$test_count${skip_all:+ $skip_all}"
-                               ;;
-                       *)
-                               test -z "$skip_all" ||
-                               say_color warn "$skip_all"
-                               say "1..$test_count"
-                               ;;
-                       esac
+                       say_color pass "# passed all $msg"
                fi
 
-               if test -z "$debug" && test -n "$remove_trash"
+               # Maybe print SKIP message
+               test -z "$skip_all" || skip_all="# SKIP $skip_all"
+               case "$test_count" in
+               0)
+                       say "1..$test_count${skip_all:+ $skip_all}"
+                       ;;
+               *)
+                       test -z "$skip_all" ||
+                       say_color warn "$skip_all"
+                       say "1..$test_count"
+                       ;;
+               esac
+
+               if test -n "$stress" && test -n "$invert_exit_code"
+               then
+                       # We're about to move our "$TRASH_DIRECTORY"
+                       # to "$TRASH_DIRECTORY.stress-failed" if
+                       # --stress is combined with
+                       # --invert-exit-code.
+                       say "with --stress and --invert-exit-code we're not removing '$TRASH_DIRECTORY'"
+               elif test -z "$debug" && test -n "$remove_trash"
                then
                        test -d "$TRASH_DIRECTORY" ||
                        error "Tests passed but trash directory already removed before test cleanup; aborting"
@@ -1244,17 +1345,35 @@ test_done () {
                        } ||
                        error "Tests passed but test cleanup failed; aborting"
                fi
+
+               check_test_results_san_file_ "$test_failure"
+
+               if test -z "$skip_all" && test -n "$invert_exit_code"
+               then
+                       say_color warn "# faking up non-zero exit with --invert-exit-code"
+                       GIT_EXIT_OK=t
+                       exit 1
+               fi
+
                test_at_end_hook_
 
+               GIT_EXIT_OK=t
                exit 0 ;;
 
        *)
-               if test $test_external_has_tap -eq 0
+               say_color error "# failed $test_failure among $msg"
+               say "1..$test_count"
+
+               check_test_results_san_file_ "$test_failure"
+
+               if test -n "$invert_exit_code"
                then
-                       say_color error "# failed $test_failure among $msg"
-                       say "1..$test_count"
+                       _invert_exit_code_failure_end_blurb
+                       GIT_EXIT_OK=t
+                       exit 0
                fi
 
+               GIT_EXIT_OK=t
                exit 1 ;;
 
        esac
@@ -1387,14 +1506,12 @@ fi
 GITPERLLIB="$GIT_BUILD_DIR"/perl/build/lib
 export GITPERLLIB
 test -d "$GIT_BUILD_DIR"/templates/blt || {
-       error "You haven't built things yet, have you?"
+       BAIL_OUT "You haven't built things yet, have you?"
 }
 
 if ! test -x "$GIT_BUILD_DIR"/t/helper/test-tool$X
 then
-       echo >&2 'You need to build test-tool:'
-       echo >&2 'Run "make t/helper/test-tool" in the source (toplevel) directory'
-       exit 1
+       BAIL_OUT 'You need to build test-tool; Run "make t/helper/test-tool" in the source (toplevel) directory'
 fi
 
 # Are we running this test at all?
@@ -1408,24 +1525,70 @@ then
        test_done
 fi
 
-# skip non-whitelisted tests when compiled with SANITIZE=leak
+BAIL_OUT_ENV_NEEDS_SANITIZE_LEAK () {
+       BAIL_OUT "$1 has no effect except when compiled with SANITIZE=leak"
+}
+
 if test -n "$SANITIZE_LEAK"
 then
-       if test_bool_env GIT_TEST_PASSING_SANITIZE_LEAK false
+       # Normalize with test_bool_env
+       passes_sanitize_leak=
+
+       # We need to see TEST_PASSES_SANITIZE_LEAK in "git
+       # env--helper" (via test_bool_env)
+       export TEST_PASSES_SANITIZE_LEAK
+       if test_bool_env TEST_PASSES_SANITIZE_LEAK false
+       then
+               passes_sanitize_leak=t
+       fi
+
+       if test "$GIT_TEST_PASSING_SANITIZE_LEAK" = "check"
        then
-               # We need to see it in "git env--helper" (via
-               # test_bool_env)
-               export TEST_PASSES_SANITIZE_LEAK
+               sanitize_leak_check=t
+               if test -n "$invert_exit_code"
+               then
+                       BAIL_OUT "cannot use --invert-exit-code under GIT_TEST_PASSING_SANITIZE_LEAK=check"
+               fi
 
-               if ! test_bool_env TEST_PASSES_SANITIZE_LEAK false
+               if test -z "$passes_sanitize_leak"
                then
-                       skip_all="skipping $this_test under GIT_TEST_PASSING_SANITIZE_LEAK=true"
-                       test_done
+                       say "in GIT_TEST_PASSING_SANITIZE_LEAK=check mode, setting --invert-exit-code for TEST_PASSES_SANITIZE_LEAK != true"
+                       invert_exit_code=t
                fi
+       elif test -z "$passes_sanitize_leak" &&
+            test_bool_env GIT_TEST_PASSING_SANITIZE_LEAK false
+       then
+               skip_all="skipping $this_test under GIT_TEST_PASSING_SANITIZE_LEAK=true"
+               test_done
        fi
-elif test_bool_env GIT_TEST_PASSING_SANITIZE_LEAK false
+
+       if test_bool_env GIT_TEST_SANITIZE_LEAK_LOG false
+       then
+               if ! mkdir -p "$TEST_RESULTS_SAN_DIR"
+               then
+                       BAIL_OUT "cannot create $TEST_RESULTS_SAN_DIR"
+               fi &&
+               TEST_RESULTS_SAN_FILE="$TEST_RESULTS_SAN_DIR/$TEST_RESULTS_SAN_FILE_PFX"
+
+               # In case "test-results" is left over from a previous
+               # run: Only report if new leaks show up.
+               TEST_RESULTS_SAN_DIR_NR_LEAKS_STARTUP=$(nr_san_dir_leaks_)
+
+               # Don't litter *.leak dirs if there was nothing to report
+               test_atexit "rmdir \"$TEST_RESULTS_SAN_DIR\" 2>/dev/null || :"
+
+               prepend_var LSAN_OPTIONS : dedup_token_length=9999
+               prepend_var LSAN_OPTIONS : log_exe_name=1
+               prepend_var LSAN_OPTIONS : log_path=\"$TEST_RESULTS_SAN_FILE\"
+               export LSAN_OPTIONS
+       fi
+elif test "$GIT_TEST_PASSING_SANITIZE_LEAK" = "check" ||
+     test_bool_env GIT_TEST_PASSING_SANITIZE_LEAK false
+then
+       BAIL_OUT_ENV_NEEDS_SANITIZE_LEAK "GIT_TEST_PASSING_SANITIZE_LEAK=true"
+elif test_bool_env GIT_TEST_SANITIZE_LEAK_LOG false
 then
-       BAIL_OUT "GIT_TEST_PASSING_SANITIZE_LEAK=true has no effect except when compiled with SANITIZE=leak"
+       BAIL_OUT_ENV_NEEDS_SANITIZE_LEAK "GIT_TEST_SANITIZE_LEAK_LOG=true"
 fi
 
 # Last-minute variable setup
@@ -1448,9 +1611,7 @@ remove_trash_directory () {
 
 # Test repository
 remove_trash_directory "$TRASH_DIRECTORY" || {
-       GIT_EXIT_OK=t
-       echo >&5 "FATAL: Cannot prepare test area"
-       exit 1
+       BAIL_OUT 'cannot prepare test area'
 }
 
 remove_trash=t
@@ -1466,7 +1627,7 @@ fi
 
 # Use -P to resolve symlinks in our working directory so that the cwd
 # in subprocesses like git equals our $PWD (for pathname comparisons).
-cd -P "$TRASH_DIRECTORY" || exit 1
+cd -P "$TRASH_DIRECTORY" || BAIL_OUT "cannot cd -P to \"$TRASH_DIRECTORY\""
 
 start_test_output "$0"
 
index 506234b4b8138894d6516fc80e9fc42753f78f00..74f4d710e8f0f28410fae49a6b9c31bf1045e0ca 100644 (file)
@@ -47,17 +47,20 @@ static int decode_tree_entry(struct tree_desc *desc, const char *buf, unsigned l
 
        /* Initialize the descriptor entry */
        desc->entry.path = path;
-       desc->entry.mode = canon_mode(mode);
+       desc->entry.mode = (desc->flags & TREE_DESC_RAW_MODES) ? mode : canon_mode(mode);
        desc->entry.pathlen = len - 1;
        oidread(&desc->entry.oid, (const unsigned char *)path + len);
 
        return 0;
 }
 
-static int init_tree_desc_internal(struct tree_desc *desc, const void *buffer, unsigned long size, struct strbuf *err)
+static int init_tree_desc_internal(struct tree_desc *desc, const void *buffer,
+                                  unsigned long size, struct strbuf *err,
+                                  enum tree_desc_flags flags)
 {
        desc->buffer = buffer;
        desc->size = size;
+       desc->flags = flags;
        if (size)
                return decode_tree_entry(desc, buffer, size, err);
        return 0;
@@ -66,15 +69,16 @@ static int init_tree_desc_internal(struct tree_desc *desc, const void *buffer, u
 void init_tree_desc(struct tree_desc *desc, const void *buffer, unsigned long size)
 {
        struct strbuf err = STRBUF_INIT;
-       if (init_tree_desc_internal(desc, buffer, size, &err))
+       if (init_tree_desc_internal(desc, buffer, size, &err, 0))
                die("%s", err.buf);
        strbuf_release(&err);
 }
 
-int init_tree_desc_gently(struct tree_desc *desc, const void *buffer, unsigned long size)
+int init_tree_desc_gently(struct tree_desc *desc, const void *buffer, unsigned long size,
+                         enum tree_desc_flags flags)
 {
        struct strbuf err = STRBUF_INIT;
-       int result = init_tree_desc_internal(desc, buffer, size, &err);
+       int result = init_tree_desc_internal(desc, buffer, size, &err, flags);
        if (result)
                error("%s", err.buf);
        strbuf_release(&err);
index a5058469e9b3a83fdd58a04f2af72690742d08bd..6305d531503f25cd0b2632914d9aa7ddea55ff8e 100644 (file)
@@ -34,6 +34,11 @@ struct tree_desc {
 
        /* counts the number of bytes left in the `buffer`. */
        unsigned int size;
+
+       /* option flags passed via init_tree_desc_gently() */
+       enum tree_desc_flags {
+               TREE_DESC_RAW_MODES = (1 << 0),
+       } flags;
 };
 
 /**
@@ -79,7 +84,8 @@ int update_tree_entry_gently(struct tree_desc *);
  */
 void init_tree_desc(struct tree_desc *desc, const void *buf, unsigned long size);
 
-int init_tree_desc_gently(struct tree_desc *desc, const void *buf, unsigned long size);
+int init_tree_desc_gently(struct tree_desc *desc, const void *buf, unsigned long size,
+                         enum tree_desc_flags flags);
 
 /*
  * Visit the next entry in a tree. Returns 1 when there are more entries
index 8a454e03bff796452b6ffad3327dde6c592847f3..90b92114be8558b63371531aecfa246829317840 100644 (file)
@@ -1069,6 +1069,67 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info,
        return ce;
 }
 
+/*
+ * Determine whether the path specified by 'p' should be unpacked as a new
+ * sparse directory in a sparse index. A new sparse directory 'A/':
+ * - must be outside the sparse cone.
+ * - must not already be in the index (i.e., no index entry with name 'A/'
+ *   exists).
+ * - must not have any child entries in the index (i.e., no index entry
+ *   'A/<something>' exists).
+ * If 'p' meets the above requirements, return 1; otherwise, return 0.
+ */
+static int entry_is_new_sparse_dir(const struct traverse_info *info,
+                                  const struct name_entry *p)
+{
+       int res, pos;
+       struct strbuf dirpath = STRBUF_INIT;
+       struct unpack_trees_options *o = info->data;
+
+       if (!S_ISDIR(p->mode))
+               return 0;
+
+       /*
+        * If the path is inside the sparse cone, it can't be a sparse directory.
+        */
+       strbuf_add(&dirpath, info->traverse_path, info->pathlen);
+       strbuf_add(&dirpath, p->path, p->pathlen);
+       strbuf_addch(&dirpath, '/');
+       if (path_in_cone_mode_sparse_checkout(dirpath.buf, o->src_index)) {
+               res = 0;
+               goto cleanup;
+       }
+
+       pos = index_name_pos_sparse(o->src_index, dirpath.buf, dirpath.len);
+       if (pos >= 0) {
+               /* Path is already in the index, not a new sparse dir */
+               res = 0;
+               goto cleanup;
+       }
+
+       /* Where would this sparse dir be inserted into the index? */
+       pos = -pos - 1;
+       if (pos >= o->src_index->cache_nr) {
+               /*
+                * Sparse dir would be inserted at the end of the index, so we
+                * know it has no child entries.
+                */
+               res = 1;
+               goto cleanup;
+       }
+
+       /*
+        * If the dir has child entries in the index, the first would be at the
+        * position the sparse directory would be inserted. If the entry at this
+        * position is inside the dir, not a new sparse dir.
+        */
+       res = strncmp(o->src_index->cache[pos]->name, dirpath.buf, dirpath.len);
+
+cleanup:
+       strbuf_release(&dirpath);
+       return res;
+}
+
 /*
  * Note that traverse_by_cache_tree() duplicates some logic in this function
  * without actually calling it. If you change the logic here you may need to
@@ -1078,21 +1139,44 @@ static int unpack_single_entry(int n, unsigned long mask,
                               unsigned long dirmask,
                               struct cache_entry **src,
                               const struct name_entry *names,
-                              const struct traverse_info *info)
+                              const struct traverse_info *info,
+                              int *is_new_sparse_dir)
 {
        int i;
        struct unpack_trees_options *o = info->data;
        unsigned long conflicts = info->df_conflicts | dirmask;
+       const struct name_entry *p = names;
 
-       if (mask == dirmask && !src[0])
-               return 0;
+       *is_new_sparse_dir = 0;
+       if (mask == dirmask && !src[0]) {
+               /*
+                * If we're not in a sparse index, we can't unpack a directory
+                * without recursing into it, so we return.
+                */
+               if (!o->src_index->sparse_index)
+                       return 0;
+
+               /* Find first entry with a real name (we could use "mask" too) */
+               while (!p->mode)
+                       p++;
+
+               /*
+                * If the directory is completely missing from the index but
+                * would otherwise be a sparse directory, we should unpack it.
+                * If not, we'll return and continue recursively traversing the
+                * tree.
+                */
+               *is_new_sparse_dir = entry_is_new_sparse_dir(info, p);
+               if (!*is_new_sparse_dir)
+                       return 0;
+       }
 
        /*
-        * When we have a sparse directory entry for src[0],
-        * then this isn't necessarily a directory-file conflict.
+        * When we are unpacking a sparse directory, then this isn't necessarily
+        * a directory-file conflict.
         */
-       if (mask == dirmask && src[0] &&
-           S_ISSPARSEDIR(src[0]->ce_mode))
+       if (mask == dirmask &&
+           (*is_new_sparse_dir || (src[0] && S_ISSPARSEDIR(src[0]->ce_mode))))
                conflicts = 0;
 
        /*
@@ -1352,7 +1436,7 @@ static int unpack_sparse_callback(int n, unsigned long mask, unsigned long dirma
 {
        struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
        struct unpack_trees_options *o = info->data;
-       int ret;
+       int ret, is_new_sparse_dir;
 
        assert(o->merge);
 
@@ -1376,7 +1460,7 @@ static int unpack_sparse_callback(int n, unsigned long mask, unsigned long dirma
         * "index" tree (i.e., names[0]) and adjust 'names', 'n', 'mask', and
         * 'dirmask' accordingly.
         */
-       ret = unpack_single_entry(n - 1, mask >> 1, dirmask >> 1, src, names + 1, info);
+       ret = unpack_single_entry(n - 1, mask >> 1, dirmask >> 1, src, names + 1, info, &is_new_sparse_dir);
 
        if (src[0])
                discard_cache_entry(src[0]);
@@ -1394,6 +1478,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
        struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
        struct unpack_trees_options *o = info->data;
        const struct name_entry *p = names;
+       int is_new_sparse_dir;
 
        /* Find first entry with a real name (we could use "mask" too) */
        while (!p->mode)
@@ -1440,7 +1525,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
                }
        }
 
-       if (unpack_single_entry(n, mask, dirmask, src, names, info) < 0)
+       if (unpack_single_entry(n, mask, dirmask, src, names, info, &is_new_sparse_dir))
                return -1;
 
        if (o->merge && src[0]) {
@@ -1478,6 +1563,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
                }
 
                if (!is_sparse_directory_entry(src[0], names, info) &&
+                   !is_new_sparse_dir &&
                    traverse_trees_recursive(n, dirmask, mask & ~dirmask,
                                                    names, info) < 0) {
                        return -1;
index 09f48317b0237ab957c5ec475f20ffacc3268505..b217a1f469e8d9b8aaf92a27aba186be83dcfa62 100644 (file)
@@ -455,6 +455,7 @@ static void create_pack_file(struct upload_pack_data *pack_data,
        return;
 
  fail:
+       free(output_state);
        send_client_data(3, abort_msg, sizeof(abort_msg),
                         pack_data->use_sideband);
        die("git upload-pack: %s", abort_msg);
index cfe79bd081ff75837b626f1db7e3960e6dc4e5e4..299d6489a6b0a148b57fa6c8a11f9245e1dfd0dd 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -161,28 +161,6 @@ void xsetenv(const char *name, const char *value, int overwrite)
                die_errno(_("could not setenv '%s'"), name ? name : "(null)");
 }
 
-/*
- * Limit size of IO chunks, because huge chunks only cause pain.  OS X
- * 64-bit is buggy, returning EINVAL if len >= INT_MAX; and even in
- * the absence of bugs, large chunks can result in bad latencies when
- * you decide to kill the process.
- *
- * We pick 8 MiB as our default, but if the platform defines SSIZE_MAX
- * that is smaller than that, clip it to SSIZE_MAX, as a call to
- * read(2) or write(2) larger than that is allowed to fail.  As the last
- * resort, we allow a port to pass via CFLAGS e.g. "-DMAX_IO_SIZE=value"
- * to override this, if the definition of SSIZE_MAX given by the platform
- * is broken.
- */
-#ifndef MAX_IO_SIZE
-# define MAX_IO_SIZE_DEFAULT (8*1024*1024)
-# if defined(SSIZE_MAX) && (SSIZE_MAX < MAX_IO_SIZE_DEFAULT)
-#  define MAX_IO_SIZE SSIZE_MAX
-# else
-#  define MAX_IO_SIZE MAX_IO_SIZE_DEFAULT
-# endif
-#endif
-
 /**
  * xopen() is the same as open(), but it die()s if the open() fails.
  */
index 72e25a9ffa56fbeebefbd2e9904b3957d0a8610d..bb56b23f34c96ceec169d39b829e89cd512d7e39 100644 (file)
@@ -120,6 +120,7 @@ typedef struct s_bdiffparam {
 
 
 #define xdl_malloc(x) xmalloc(x)
+#define xdl_calloc(n, sz) xcalloc(n, sz)
 #define xdl_free(ptr) free(ptr)
 #define xdl_realloc(ptr,x) xrealloc(ptr,x)
 
index 758410c11ac286adc77c6f992e51822def202a40..53e803e6bcb249c38b0d09d7811fa207513ada81 100644 (file)
@@ -337,7 +337,7 @@ int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
         * One is to store the forward path and one to store the backward path.
         */
        ndiags = xe->xdf1.nreff + xe->xdf2.nreff + 3;
-       if (!(kvd = (long *) xdl_malloc((2 * ndiags + 2) * sizeof(long)))) {
+       if (!XDL_ALLOC_ARRAY(kvd, 2 * ndiags + 2)) {
 
                xdl_free_env(xe);
                return -1;
index 01decffc332629dd9dcfd79c904187b7cc6d0943..df909004c1057c011af3a97aba8ca4ecc722b8e0 100644 (file)
@@ -251,7 +251,7 @@ static int find_lcs(xpparam_t const *xpp, xdfenv_t *env,
                    int line1, int count1, int line2, int count2)
 {
        int b_ptr;
-       int sz, ret = -1;
+       int ret = -1;
        struct histindex index;
 
        memset(&index, 0, sizeof(index));
@@ -265,23 +265,16 @@ static int find_lcs(xpparam_t const *xpp, xdfenv_t *env,
        index.rcha.head = NULL;
 
        index.table_bits = xdl_hashbits(count1);
-       sz = index.records_size = 1 << index.table_bits;
-       sz *= sizeof(struct record *);
-       if (!(index.records = (struct record **) xdl_malloc(sz)))
+       index.records_size = 1 << index.table_bits;
+       if (!XDL_CALLOC_ARRAY(index.records, index.records_size))
                goto cleanup;
-       memset(index.records, 0, sz);
 
-       sz = index.line_map_size = count1;
-       sz *= sizeof(struct record *);
-       if (!(index.line_map = (struct record **) xdl_malloc(sz)))
+       index.line_map_size = count1;
+       if (!XDL_CALLOC_ARRAY(index.line_map, index.line_map_size))
                goto cleanup;
-       memset(index.line_map, 0, sz);
 
-       sz = index.line_map_size;
-       sz *= sizeof(unsigned int);
-       if (!(index.next_ptrs = (unsigned int *) xdl_malloc(sz)))
+       if (!XDL_CALLOC_ARRAY(index.next_ptrs, index.line_map_size))
                goto cleanup;
-       memset(index.next_ptrs, 0, sz);
 
        /* lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx() */
        if (xdl_cha_init(&index.rcha, sizeof(struct record), count1 / 4 + 1) < 0)
index ae4636c2477cc640eae84578805d9722d5e28d1b..8487bb396faa5c6ac984e8295b737b4fbab3f92c 100644 (file)
@@ -49,5 +49,23 @@ do { \
                ((unsigned long) __p[2]) << 16 | ((unsigned long) __p[3]) << 24; \
 } while (0)
 
+/* Allocate an array of nr elements, returns NULL on failure */
+#define XDL_ALLOC_ARRAY(p, nr)                         \
+       ((p) = SIZE_MAX / sizeof(*(p)) >= (size_t)(nr)  \
+               ? xdl_malloc((nr) * sizeof(*(p)))       \
+               : NULL)
+
+/* Allocate an array of nr zeroed out elements, returns NULL on failure */
+#define XDL_CALLOC_ARRAY(p, nr)        ((p) = xdl_calloc(nr, sizeof(*(p))))
+
+/*
+ * Ensure array p can accommodate at least nr elements, growing the
+ * array and updating alloc (which is the number of allocated
+ * elements) as necessary. Frees p and returns -1 on failure, returns
+ * 0 on success
+ */
+#define XDL_ALLOC_GROW(p, nr, alloc)   \
+       (-!((nr) <= (alloc) ||          \
+           ((p) = xdl_alloc_grow_helper((p), (nr), &(alloc), sizeof(*(p))))))
 
 #endif /* #if !defined(XMACROS_H) */
index 1a21c6a74b368cb094e20c708a43071c72558d7e..fe39c2978cb784231ae27808c9f38c6d9b7f1803 100644 (file)
@@ -151,11 +151,8 @@ static int fill_hashmap(mmfile_t *file1, mmfile_t *file2,
 
        /* We know exactly how large we want the hash map */
        result->alloc = count1 * 2;
-       result->entries = (struct entry *)
-               xdl_malloc(result->alloc * sizeof(struct entry));
-       if (!result->entries)
+       if (!XDL_CALLOC_ARRAY(result->entries, result->alloc))
                return -1;
-       memset(result->entries, 0, result->alloc * sizeof(struct entry));
 
        /* First, fill with entries from the first file */
        while (count1--)
@@ -200,7 +197,7 @@ static int binary_search(struct entry **sequence, int longest,
  */
 static int find_longest_common_sequence(struct hashmap *map, struct entry **res)
 {
-       struct entry **sequence = xdl_malloc(map->nr * sizeof(struct entry *));
+       struct entry **sequence;
        int longest = 0, i;
        struct entry *entry;
 
@@ -211,7 +208,7 @@ static int find_longest_common_sequence(struct hashmap *map, struct entry **res)
         */
        int anchor_i = -1;
 
-       if (!sequence)
+       if (!XDL_ALLOC_ARRAY(sequence, map->nr))
                return -1;
 
        for (entry = map->first; entry; entry = entry->next) {
index 105752758f2f3870448f1ec1cf0070c3a29b36e6..c84549f6c5089ea08c7bc1daad3ef57dd3fceb77 100644 (file)
@@ -78,15 +78,14 @@ static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
 
                return -1;
        }
-       if (!(cf->rchash = (xdlclass_t **) xdl_malloc(cf->hsize * sizeof(xdlclass_t *)))) {
+       if (!XDL_CALLOC_ARRAY(cf->rchash, cf->hsize)) {
 
                xdl_cha_free(&cf->ncha);
                return -1;
        }
-       memset(cf->rchash, 0, cf->hsize * sizeof(xdlclass_t *));
 
        cf->alloc = size;
-       if (!(cf->rcrecs = (xdlclass_t **) xdl_malloc(cf->alloc * sizeof(xdlclass_t *)))) {
+       if (!XDL_ALLOC_ARRAY(cf->rcrecs, cf->alloc)) {
 
                xdl_free(cf->rchash);
                xdl_cha_free(&cf->ncha);
@@ -112,7 +111,6 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
        long hi;
        char const *line;
        xdlclass_t *rcrec;
-       xdlclass_t **rcrecs;
 
        line = rec->ptr;
        hi = (long) XDL_HASHLONG(rec->ha, cf->hbits);
@@ -128,14 +126,8 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
                        return -1;
                }
                rcrec->idx = cf->count++;
-               if (cf->count > cf->alloc) {
-                       cf->alloc *= 2;
-                       if (!(rcrecs = (xdlclass_t **) xdl_realloc(cf->rcrecs, cf->alloc * sizeof(xdlclass_t *)))) {
-
+               if (XDL_ALLOC_GROW(cf->rcrecs, cf->count, cf->alloc))
                                return -1;
-                       }
-                       cf->rcrecs = rcrecs;
-               }
                cf->rcrecs[rcrec->idx] = rcrec;
                rcrec->line = line;
                rcrec->size = rec->size;
@@ -164,7 +156,7 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
        unsigned long hav;
        char const *blk, *cur, *top, *prev;
        xrecord_t *crec;
-       xrecord_t **recs, **rrecs;
+       xrecord_t **recs;
        xrecord_t **rhash;
        unsigned long *ha;
        char *rchg;
@@ -178,26 +170,21 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 
        if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
                goto abort;
-       if (!(recs = (xrecord_t **) xdl_malloc(narec * sizeof(xrecord_t *))))
+       if (!XDL_ALLOC_ARRAY(recs, narec))
                goto abort;
 
        hbits = xdl_hashbits((unsigned int) narec);
        hsize = 1 << hbits;
-       if (!(rhash = (xrecord_t **) xdl_malloc(hsize * sizeof(xrecord_t *))))
+       if (!XDL_CALLOC_ARRAY(rhash, hsize))
                goto abort;
-       memset(rhash, 0, hsize * sizeof(xrecord_t *));
 
        nrec = 0;
        if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
                for (top = blk + bsize; cur < top; ) {
                        prev = cur;
                        hav = xdl_hash_record(&cur, top, xpp->flags);
-                       if (nrec >= narec) {
-                               narec *= 2;
-                               if (!(rrecs = (xrecord_t **) xdl_realloc(recs, narec * sizeof(xrecord_t *))))
-                                       goto abort;
-                               recs = rrecs;
-                       }
+                       if (XDL_ALLOC_GROW(recs, nrec + 1, narec))
+                               goto abort;
                        if (!(crec = xdl_cha_alloc(&xdf->rcha)))
                                goto abort;
                        crec->ptr = prev;
@@ -209,15 +196,14 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
                }
        }
 
-       if (!(rchg = (char *) xdl_malloc((nrec + 2) * sizeof(char))))
+       if (!XDL_CALLOC_ARRAY(rchg, nrec + 2))
                goto abort;
-       memset(rchg, 0, (nrec + 2) * sizeof(char));
 
        if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
            (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) {
-               if (!(rindex = xdl_malloc((nrec + 1) * sizeof(*rindex))))
+               if (!XDL_ALLOC_ARRAY(rindex, nrec + 1))
                        goto abort;
-               if (!(ha = xdl_malloc((nrec + 1) * sizeof(*ha))))
+               if (!XDL_ALLOC_ARRAY(ha, nrec + 1))
                        goto abort;
        }
 
@@ -383,11 +369,8 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd
        xdlclass_t *rcrec;
        char *dis, *dis1, *dis2;
 
-       if (!(dis = (char *) xdl_malloc(xdf1->nrec + xdf2->nrec + 2))) {
-
+       if (!XDL_CALLOC_ARRAY(dis, xdf1->nrec + xdf2->nrec + 2))
                return -1;
-       }
-       memset(dis, 0, xdf1->nrec + xdf2->nrec + 2);
        dis1 = dis;
        dis2 = dis1 + xdf1->nrec + 1;
 
index 115b2b1640b4504d1b7eb1bc4dc1428b109f6380..9e36f24875d20711b61d243994f324d00a1b211e 100644 (file)
@@ -432,3 +432,20 @@ int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
 
        return 0;
 }
+
+void* xdl_alloc_grow_helper(void *p, long nr, long *alloc, size_t size)
+{
+       void *tmp = NULL;
+       size_t n = ((LONG_MAX - 16) / 2 >= *alloc) ? 2 * *alloc + 16 : LONG_MAX;
+       if (nr > n)
+               n = nr;
+       if (SIZE_MAX / size >= n)
+               tmp = xdl_realloc(p, n * size);
+       if (tmp) {
+               *alloc = n;
+       } else {
+               xdl_free(p);
+               *alloc = 0;
+       }
+       return tmp;
+}
index fba7bae03c7855ca90aff3f238321581a91a6676..fd0bba94e8b4d2442ba59d0a4327d2d53e10210a 100644 (file)
@@ -42,6 +42,7 @@ int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
 int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
                       int line1, int count1, int line2, int count2);
 
-
+/* Do not call this function, use XDL_ALLOC_GROW instead */
+void* xdl_alloc_grow_helper(void* p, long nr, long* alloc, size_t size);
 
 #endif /* #if !defined(XUTILS_H) */