]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'mt/use-passed-repo-more-in-funcs'
authorJunio C Hamano <gitster@pobox.com>
Fri, 14 Feb 2020 20:54:22 +0000 (12:54 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 14 Feb 2020 20:54:22 +0000 (12:54 -0800)
Some codepaths were given a repository instance as a parameter to
work in the repository, but passed the_repository instance to its
callees, which has been cleaned up (somewhat).

* mt/use-passed-repo-more-in-funcs:
  sha1-file: allow check_object_signature() to handle any repo
  sha1-file: pass git_hash_algo to hash_object_file()
  sha1-file: pass git_hash_algo to write_object_file_prepare()
  streaming: allow open_istream() to handle any repo
  pack-check: use given repo's hash_algo at verify_packfile()
  cache-tree: use given repo's hash_algo at verify_one()
  diff: make diff_populate_filespec() honor its repo argument

205 files changed:
.editorconfig
.mailmap
.tsan-suppressions
Documentation/RelNotes/2.26.0.txt [new file with mode: 0644]
Documentation/config/advice.txt
Documentation/config/core.txt
Documentation/config/gpg.txt
Documentation/config/http.txt
Documentation/config/pack.txt
Documentation/config/protocol.txt
Documentation/config/push.txt
Documentation/config/user.txt
Documentation/diff-options.txt
Documentation/fetch-options.txt
Documentation/git-commit-tree.txt
Documentation/git-commit.txt
Documentation/git-filter-branch.txt
Documentation/git-grep.txt
Documentation/git-sparse-checkout.txt
Documentation/git-submodule.txt
Documentation/git-update-index.txt
Documentation/git.txt
Documentation/gitcore-tutorial.txt
Documentation/githooks.txt
Documentation/pretty-formats.txt
Documentation/technical/bundle-format.txt [new file with mode: 0644]
Documentation/technical/pack-format.txt
GIT-VERSION-GEN
Makefile
RelNotes
add-interactive.c
add-interactive.h
add-patch.c
advice.c
advice.h
builtin/add.c
builtin/checkout.c
builtin/clone.c
builtin/commit.c
builtin/fast-export.c
builtin/fetch.c
builtin/grep.c
builtin/merge.c
builtin/pack-objects.c
builtin/pull.c
builtin/rebase.c
builtin/reflog.c
builtin/sparse-checkout.c
builtin/stash.c
cache.h
ci/run-build-and-tests.sh
ci/test-documentation.sh
commit.c
commit.h
compat/obstack.h
compat/regex/regex.h
compat/terminal.c
compat/terminal.h
compat/vcbuild/scripts/clink.pl
connected.c
connected.h
contrib/buildsystems/engine.pl
contrib/completion/git-completion.bash
contrib/credential/netrc/.gitignore [new file with mode: 0644]
contrib/credential/netrc/Makefile
contrib/credential/netrc/git-credential-netrc.perl [moved from contrib/credential/netrc/git-credential-netrc with 99% similarity]
convert.c
csum-file.h
diff.c
dir.c
ewah/bitmap.c
ewah/ewok.h
fsmonitor.c
git-legacy-stash.sh
git-p4.py
git-submodule.sh
gpg-interface.c
gpg-interface.h
graph.c
grep.c
grep.h
merge-recursive.c
notes.c
object-store.h
object.c
pack-bitmap.c
pack-bitmap.h
packfile.c
packfile.h
parse-options.c
pretty.c
protocol.c
rebase-interactive.c
rebase-interactive.h
refs/files-backend.c
remote-curl.c
replace-object.c
replace-object.h
run-command.c
sequencer.c
sequencer.h
setup.c
sha1-file.c
sha1-name.c
string-list.h
submodule-config.c
submodule-config.h
submodule.c
t/README
t/check-non-portable-shell.pl
t/helper/test-dump-fsmonitor.c
t/helper/test-parse-pathspec-file.c [new file with mode: 0644]
t/helper/test-tool.c
t/helper/test-tool.h
t/lib-git-p4.sh
t/t0000-basic.sh
t/t0003-attributes.sh
t/t0020-crlf.sh
t/t0040-parse-options.sh
t/t0067-parse_pathspec_file.sh [new file with mode: 0755]
t/t1091-sparse-checkout-builtin.sh
t/t1306-xdg-files.sh
t/t1307-config-blob.sh
t/t1400-update-ref.sh
t/t1409-avoid-packing-refs.sh
t/t1501-work-tree.sh
t/t1506-rev-parse-diagnosis.sh
t/t1507-rev-parse-upstream.sh
t/t2018-checkout-branch.sh
t/t2024-checkout-dwim.sh
t/t2026-checkout-pathspec-file.sh
t/t2070-restore.sh
t/t2072-restore-pathspec-file.sh
t/t2405-worktree-submodule.sh [new file with mode: 0755]
t/t3030-merge-recursive.sh
t/t3310-notes-merge-manual-resolve.sh
t/t3404-rebase-interactive.sh
t/t3415-rebase-autosquash.sh
t/t3419-rebase-patch-id.sh
t/t3504-cherry-pick-rerere.sh
t/t3507-cherry-pick-conflict.sh
t/t3600-rm.sh
t/t3700-add.sh
t/t3701-add-interactive.sh
t/t3704-add-pathspec-file.sh
t/t4018-diff-funcname.sh
t/t4054-diff-bogus-tree.sh
t/t4066-diff-emit-delay.sh
t/t4124-apply-ws-rule.sh
t/t4134-apply-submodule.sh
t/t4200-rerere.sh
t/t4202-log.sh
t/t4204-patch-id.sh
t/t4215-log-skewed-merges.sh
t/t4300-merge-tree.sh
t/t5318-commit-graph.sh
t/t5319-multi-pack-index.sh
t/t5324-split-commit-graph.sh
t/t5400-send-pack.sh
t/t5500-fetch-pack.sh
t/t5504-fetch-receive-strict.sh
t/t5510-fetch.sh
t/t5512-ls-remote.sh
t/t5515-fetch-merge-logic.sh
t/t5516-fetch-push.sh
t/t5530-upload-pack-error.sh
t/t5537-fetch-shallow.sh
t/t5539-fetch-http-shallow.sh
t/t5540-http-push-webdav.sh
t/t5541-http-push-smart.sh
t/t5551-http-fetch-smart.sh
t/t5552-skipping-fetch-negotiator.sh
t/t5562-http-backend-content-length.sh
t/t5573-pull-verify-signatures.sh
t/t5601-clone.sh
t/t5604-clone-reference.sh
t/t5616-partial-clone.sh
t/t5700-protocol-v1.sh
t/t5702-protocol-v2.sh
t/t6025-merge-symlinks.sh
t/t7030-verify-tag.sh
t/t7107-reset-pathspec-file.sh
t/t7300-clean.sh
t/t7400-submodule-basic.sh
t/t7406-submodule-update.sh
t/t7410-submodule-checkout-to.sh [deleted file]
t/t7500-commit-template-squash-signoff.sh
t/t7510-signed-commit.sh
t/t7519-status-fsmonitor.sh
t/t7519/fsmonitor-all
t/t7519/fsmonitor-all-v2 [new file with mode: 0755]
t/t7519/fsmonitor-watchman
t/t7519/fsmonitor-watchman-v2 [new file with mode: 0755]
t/t7526-commit-pathspec-file.sh
t/t7612-merge-verify-signatures.sh
t/t7800-difftool.sh
t/t7814-grep-recurse-submodules.sh
t/t9902-completion.sh
templates/hooks--fsmonitor-watchman.sample
templates/hooks--pre-commit.sample
templates/hooks--update.sample
transport-helper.c
transport.c
unpack-trees.c
xdiff-interface.c

index 42cdc4bbfb05934bb9c3ed2fe0e0d45212c32d7a..f9d819623d832113014dd5d5366e8ee44ac9666a 100644 (file)
@@ -4,7 +4,7 @@ insert_final_newline = true
 
 # The settings for C (*.c and *.h) files are mirrored in .clang-format.  Keep
 # them in sync.
-[*.{c,h,sh,perl,pl,pm}]
+[*.{c,h,sh,perl,pl,pm,txt}]
 indent_style = tab
 tab_width = 8
 
index 7c9441837a157b155c22989875554be2ac557a9d..73fd92e192bd348dd64965fd33feb9d61b11b957 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -59,6 +59,7 @@ David S. Miller <davem@davemloft.net>
 David Turner <novalis@novalis.org> <dturner@twopensource.com>
 David Turner <novalis@novalis.org> <dturner@twosigma.com>
 Derrick Stolee <dstolee@microsoft.com> <stolee@gmail.com>
+Derrick Stolee <dstolee@microsoft.com> Derrick Stolee via GitGitGadget <gitgitgadget@gmail.com>
 Deskin Miller <deskinm@umich.edu>
 Đoàn Trần Công Danh <congdanhqx@gmail.com> Doan Tran Cong Danh
 Dirk Süsserott <newsletter@dirk.my1.cc>
@@ -109,6 +110,7 @@ Jim Meyering <jim@meyering.net> <meyering@redhat.com>
 Joachim Berdal Haga <cjhaga@fys.uio.no>
 Joachim Jablon <joachim.jablon@people-doc.com> <ewjoachim@gmail.com>
 Johannes Schindelin <Johannes.Schindelin@gmx.de> <johannes.schindelin@gmx.de>
+Johannes Schindelin <Johannes.Schindelin@gmx.de> Johannes Schindelin via GitGitGadget <gitgitgadget@gmail.com>
 Johannes Sixt <j6t@kdbg.org> <J.Sixt@eudaptics.com>
 Johannes Sixt <j6t@kdbg.org> <j.sixt@viscovery.net>
 Johannes Sixt <j6t@kdbg.org> <johannes.sixt@telecom.at>
@@ -287,6 +289,7 @@ William Pursell <bill.pursell@gmail.com>
 YONETANI Tomokazu <y0n3t4n1@gmail.com> <qhwt+git@les.ath.cx>
 YONETANI Tomokazu <y0n3t4n1@gmail.com> <y0netan1@dragonflybsd.org>
 YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
+Yi-Jyun Pan <pan93412@gmail.com>
 # the two anonymous contributors are different persons:
 anonymous <linux@horizon.com>
 anonymous <linux@horizon.net>
index 8c85014a0a936892f6832c68e3db646b6f9d2ea2..5ba86d68459e61f87dae1332c7f2402860b4280c 100644 (file)
@@ -8,3 +8,9 @@
 # in practice it (hopefully!) doesn't matter.
 race:^want_color$
 race:^transfer_debug$
+
+# A boolean value, which tells whether the replace_map has been initialized or
+# not, is read racily with an update. As this variable is written to only once,
+# and it's OK if the value change right after reading it, this shouldn't be a
+# problem.
+race:^lookup_replace_object$
diff --git a/Documentation/RelNotes/2.26.0.txt b/Documentation/RelNotes/2.26.0.txt
new file mode 100644 (file)
index 0000000..ae66cce
--- /dev/null
@@ -0,0 +1,175 @@
+Git 2.26 Release Notes
+======================
+
+Updates since v2.25
+-------------------
+
+UI, Workflows & Features
+
+ * Sample credential helper for using .netrc has been updated to work
+   out of the box.
+
+ * gpg.minTrustLevel configuration variable has been introduced to
+   tell various signature verification codepaths the required minimum
+   trust level.
+
+ * The command line completion (in contrib/) learned to complete
+   subcommands and arguments to "git worktree".
+
+ * Disambiguation logic to tell revisions and pathspec apart has been
+   tweaked so that backslash-escaped glob special characters do not
+   count in the "wildcards are pathspec" rule.
+
+ * One effect of specifying where the GIT_DIR is (either with the
+   environment variable, or with the "git --git-dir=<where> cmd"
+   option) is to disable the repository discovery.  This has been
+   placed a bit more stress in the documentation, as new users often
+   get confused.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * Tell .editorconfig that in this project, *.txt files are indented
+   with tabs.
+
+ * The test-lint machinery knew to check "VAR=VAL shell_function"
+   construct, but did not check "VAR= shell_funciton", which has been
+   corrected.
+
+ * Replace "git config --bool" calls with "git config --type=bool" in
+   sample templates.
+
+ * The effort to move "git-add--interactive" to C continues.
+
+ * Improve error message generation for "git submodule add".
+
+ * Preparation of test scripts for the day when the object names will
+   use SHA-256 continues.
+
+ * Warn programmers about pretend_object_file() that allows the code
+   to tentatively use in-core objects.
+
+
+Fixes since v2.25
+-----------------
+
+ * "git commit" gives output similar to "git status" when there is
+   nothing to commit, but without honoring the advise.statusHints
+   configuration variable, which has been corrected.
+   (merge 5c4f55f1f6 hw/commit-advise-while-rejecting later to maint).
+
+ * has_object_file() said "no" given an object registered to the
+   system via pretend_object_file(), making it inconsistent with
+   read_object_file(), causing lazy fetch to attempt fetching an
+   empty tree from promisor remotes.
+   (merge 9c8a294a1a jt/sha1-file-remove-oi-skip-cached later to maint).
+
+ * Complete an update to tutorial that encourages "git switch" over
+   "git checkout" that was done only half-way.
+   (merge 1a7e454dd6 hw/tutorial-favor-switch-over-checkout later to maint).
+
+ * C pedantry ;-) fix.
+   (merge 63ab08fb99 bc/run-command-nullness-after-free-fix later to maint).
+
+ * The code that tries to skip over the entries for the paths in a
+   single directory using the cache-tree was not careful enough
+   against corrupt index file.
+   (merge 573117dfa5 es/unpack-trees-oob-fix later to maint).
+
+ * Reduce unnecessary round-trip when running "ls-remote" over the
+   stateless RPC mechanism.
+   (merge 4d8cab95cc jk/no-flush-upon-disconnecting-slrpc-transport later to maint).
+
+ * "git restore --staged" did not correctly update the cache-tree
+   structure, resulting in bogus trees to be written afterwards, which
+   has been corrected.
+   (merge e701bab3e9 nd/switch-and-restore later to maint).
+
+ * The code recently added to move to the entry beyond the ones in the
+   same directory in the index in the sparse-cone mode did not count
+   the number of entries to skip over incorrectly, which has been
+   corrected.
+   (merge 7210ca4ee5 ds/sparse-cone later to maint).
+
+ * Rendering by "git log --graph" of ancestry lines leading to a merge
+   commit were made suboptimal to waste vertical space a bit with a
+   recent update, which has been corrected.
+   (merge c958d3bd0a ds/graph-horizontal-edges later to maint).
+
+ * Work around test breakages caused by custom regex engine used in
+   libasan, when address sanitizer is used with more recent versions
+   of gcc and clang.
+   (merge f65d07fffa jk/asan-build-fix later to maint).
+
+ * Minor bugfixes to "git add -i" that has recently been rewritten in C.
+   (merge 849e43cc18 js/builtin-add-i-cmds later to maint).
+
+ * "git fetch --refmap=" option has got a better documentation.
+   (merge b40a50264a ds/refmap-doc later to maint).
+
+ * "git checkout X" did not correctly fail when X is not a local
+   branch but could name more than one remote-tracking branches
+   (i.e. to be dwimmed as the starting point to create a corresponding
+   local branch), which has been corrected.
+   (merge fa74180d08 am/checkout-file-and-ref-ref-ambiguity later to maint).
+
+ * Corner case bugs in "git clean" that stems from a (necessarily for
+   performance reasons) awkward calling convention in the directory
+   enumeration API has been corrected.
+   (merge 0cbb60574e en/fill-directory-fixes-more later to maint).
+
+ * A fetch that is told to recursively fetch updates in submodules
+   inevitably produces reams of output, and it becomes hard to spot
+   error messages.  The command has been taught to enumerate
+   submodules that had errors at the end of the operation.
+   (merge 0222540827 es/fetch-show-failed-submodules-atend later to maint).
+
+ * The "--recurse-submodules" option of various subcommands did not
+   work well when run in an alternate worktree, which has been
+   corrected.
+   (merge a9472afb63 pb/recurse-submodule-in-worktree-fix later to maint).
+
+ * Futureproofing a test not to depend on the current implementation
+   detail.
+   (merge b54128bb0b jt/t5616-robustify later to maint).
+
+ * Running "git rm" on a submodule failed unnecessarily when
+   .gitmodules is only cache-dirty, which has been corrected.
+   (merge 7edee32985 dt/submodule-rm-with-stale-cache later to maint).
+
+ * C pedantry ;-) fix.
+   (merge cf82bff73f jk/clang-sanitizer-fixes later to maint).
+
+ * "git grep --no-index" should not get affected by the contents of
+   the .gitmodules file but when "--recurse-submodules" is given or
+   the "submodule.recurse" variable is set, it did.  Now these
+   settings are ignored in the "--no-index" mode.
+   (merge c56c48dd07 pb/do-not-recurse-grep-no-index later to maint).
+
+ * Technical details of the bundle format has been documented.
+   (merge 7378ec90e1 ms/doc-bundle-format later to maint).
+
+ * Unhelpful warning messages during documentation build have been squelched.
+   (merge 30183894ea js/ci-squelch-doc-warning later to maint).
+
+ * Other code cleanup, docfix, build fix, etc.
+   (merge 26f924d50e en/simplify-check-updates-in-unpack-trees later to maint).
+   (merge 065027ee1a en/string-list-can-be-custom-sorted later to maint).
+   (merge d0d0a357a1 am/update-pathspec-f-f-tests later to maint).
+   (merge f94f7bd00d am/test-pathspec-f-f-error-cases later to maint).
+   (merge e4837b4406 jk/test-fixes later to maint).
+   (merge a4ffbbbb99 rt/submodule-i18n later to maint).
+   (merge 856249c62a bc/actualmente later to maint).
+   (merge c513a958b6 ss/t6025-modernize later to maint).
+   (merge 69e104d70e bc/author-committer-doc later to maint).
+   (merge 7a2dc95cbc bc/misconception-doc later to maint).
+   (merge b441717256 dl/test-must-fail-fixes later to maint).
+   (merge d031049da3 mt/sparse-checkout-doc-update later to maint).
+   (merge 145136a95a jc/skip-prefix later to maint).
+   (merge eb31044ff7 jb/multi-pack-index-docfix later to maint).
+   (merge 04e5b3f0b4 km/submodule-doc-use-sm-path later to maint).
+   (merge e469afe158 ma/filter-branch-doc-caret later to maint).
+   (merge 395518cf7a jb/parse-options-message-fix later to maint).
+   (merge 303b3c1c46 es/submodule-fetch-message-fix later to maint).
+   (merge 9299f84921 ma/diff-doc-clarify-regexp-example later to maint).
+   (merge 2b0f19fa7a js/convert-typofix later to maint).
index 4be93f8ad9c677e45c1dab652e28dab40bd36691..bdd37c3eaa3203f504fdc07eeac358ad58887965 100644 (file)
@@ -110,4 +110,10 @@ advice.*::
        submoduleAlternateErrorStrategyDie::
                Advice shown when a submodule.alternateErrorStrategy option
                configured to "die" causes a fatal error.
+       addIgnoredFile::
+               Advice shown if a user attempts to add an ignored file to
+               the index.
+       addEmptyPathspec::
+               Advice shown if a user runs the add command without providing
+               the pathspec parameter.
 --
index 9e440b160d9b3f0e1647f35996accc76e97984c7..74619a9c03bb17d0f6c007fc3f9b8c052b957c65 100644 (file)
@@ -68,6 +68,17 @@ core.fsmonitor::
        avoiding unnecessary processing of files that have not changed.
        See the "fsmonitor-watchman" section of linkgit:githooks[5].
 
+core.fsmonitorHookVersion::
+       Sets the version of hook that is to be used when calling fsmonitor.
+       There are currently versions 1 and 2. When this is not set,
+       version 2 will be tried first and if it fails then version 1
+       will be tried. Version 1 uses a timestamp as input to determine
+       which files have changes since that time but some monitors
+       like watchman have race conditions when used with a timestamp.
+       Version 2 uses an opaque string so that the monitor can return
+       something that can be used to determine what files have changed
+       without race conditions.
+
 core.trustctime::
        If false, the ctime differences between the index and the
        working tree are ignored; useful when the inode change time
index cce2c8924598c240fcebfadea8be5b4b28ce378e..d94025cb3684d8e20f2875716cc16dfad47cf6ff 100644 (file)
@@ -18,3 +18,18 @@ gpg.<format>.program::
        chose. (see `gpg.program` and `gpg.format`) `gpg.program` can still
        be used as a legacy synonym for `gpg.openpgp.program`. The default
        value for `gpg.x509.program` is "gpgsm".
+
+gpg.minTrustLevel::
+       Specifies a minimum trust level for signature verification.  If
+       this option is unset, then signature verification for merge
+       operations require a key with at least `marginal` trust.  Other
+       operations that perform signature verification require a key
+       with at least `undefined` trust.  Setting this option overrides
+       the required trust-level for all operations.  Supported values,
+       in increasing order of significance:
++
+* `undefined`
+* `never`
+* `marginal`
+* `fully`
+* `ultimate`
index 5a32f5b0a5a9c312bb414d0405cab0f97e826a01..e806033aab86ae7efd52a26a1efd2067178c28dc 100644 (file)
@@ -71,7 +71,7 @@ http.saveCookies::
 http.version::
        Use the specified HTTP protocol version when communicating with a server.
        If you want to force the default. The available and default version depend
-       on libcurl. Actually the possible values of
+       on libcurl. Currently the possible values of
        this option are:
 
        - HTTP/2
@@ -84,7 +84,7 @@ http.sslVersion::
        particular configuration of the crypto library in use. Internally
        this sets the 'CURLOPT_SSL_VERSION' option; see the libcurl
        documentation for more details on the format of this option and
-       for the ssl version supported. Actually the possible values of
+       for the ssl version supported. Currently the possible values of
        this option are:
 
        - sslv2
@@ -199,6 +199,14 @@ http.postBuffer::
        Transfer-Encoding: chunked is used to avoid creating a
        massive pack file locally.  Default is 1 MiB, which is
        sufficient for most requests.
++
+Note that raising this limit is only effective for disabling chunked
+transfer encoding and therefore should be used only where the remote
+server or a proxy only supports HTTP/1.0 or is noncompliant with the
+HTTP standard.  Raising this is not, in general, an effective solution
+for most push problems, but can increase memory consumption
+significantly since the entire buffer is allocated even for small
+pushes.
 
 http.lowSpeedLimit, http.lowSpeedTime::
        If the HTTP transfer speed is less than 'http.lowSpeedLimit'
index 1d66f0c992c38237dfa7def97d9b62c9803c7fc8..0dac5805816ff6e9c133097be5cd9ae14e67da41 100644 (file)
@@ -27,6 +27,13 @@ Note that changing the compression level will not automatically recompress
 all existing objects. You can force recompression by passing the -F option
 to linkgit:git-repack[1].
 
+pack.allowPackReuse::
+       When true, and when reachability bitmaps are enabled,
+       pack-objects will try to send parts of the bitmapped packfile
+       verbatim. This can reduce memory and CPU usage to serve fetches,
+       but might result in sending a slightly larger pack. Defaults to
+       true.
+
 pack.island::
        An extended regular expression configuring a set of delta
        islands. See "DELTA ISLANDS" in linkgit:git-pack-objects[1]
index bfccc074913eda410ce5a5e9257889097141317a..756591d77b080ccfe5be5a26c899b65273cf4866 100644 (file)
@@ -45,11 +45,10 @@ The protocol names currently used by git are:
 --
 
 protocol.version::
-       Experimental. If set, clients will attempt to communicate with a
-       server using the specified protocol version.  If unset, no
-       attempt will be made by the client to communicate using a
-       particular protocol version, this results in protocol version 0
-       being used.
+       If set, clients will attempt to communicate with a server
+       using the specified protocol version.  If the server does
+       not support it, communication falls back to version 0.
+       If unset, the default is `2`.
        Supported versions:
 +
 --
index 0a0e000569d8ad6f3da22ff400b7c358eec109d1..54871f8213c4292b7e11901831f09a4f18c018fb 100644 (file)
@@ -1,6 +1,7 @@
 push.default::
        Defines the action `git push` should take if no refspec is
-       explicitly given.  Different values are well-suited for
+       given (whether from the command-line, config, or elsewhere).
+       Different values are well-suited for
        specific workflows; for instance, in a purely central workflow
        (i.e. the fetch source is equal to the push destination),
        `upstream` is probably what you want.  Possible values are:
@@ -8,7 +9,7 @@ push.default::
 --
 
 * `nothing` - do not push anything (error out) unless a refspec is
-  explicitly given. This is primarily meant for people who want to
+  given. This is primarily meant for people who want to
   avoid mistakes by always being explicit.
 
 * `current` - push the current branch to update a branch with the same
index 0557cbbceb8159148e8d0e7ab4771a8f53a67c0a..59aec7c3aed32aac0a8d7e015c0602705e4ccc6d 100644 (file)
@@ -13,7 +13,12 @@ committer.email::
        Also, all of these can be overridden by the `GIT_AUTHOR_NAME`,
        `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_NAME`,
        `GIT_COMMITTER_EMAIL` and `EMAIL` environment variables.
-       See linkgit:git-commit-tree[1] for more information.
++
+Note that the `name` forms of these variables conventionally refer to
+some form of a personal name.  See linkgit:git-commit[1] and the
+environment variables section of linkgit:git[1] for more information on
+these settings and the `credential.username` option if you're looking
+for authentication credentials instead.
 
 user.useConfigOnly::
        Instruct Git to avoid trying to guess defaults for `user.email`
index 09faee3b44db2e78ba909d30c62ff105255c7832..bb31f0c42b3f8a7b26f6eb01e7e1ad7c0b0fa008 100644 (file)
@@ -567,13 +567,13 @@ To illustrate the difference between `-S<regex> --pickaxe-regex` and
 file:
 +
 ----
-+    return !regexec(regexp, two->ptr, 1, &regmatch, 0);
++    return frotz(nitfol, two->ptr, 1, 0);
 ...
--    hit = !regexec(regexp, mf2.ptr, 1, &regmatch, 0);
+-    hit = frotz(nitfol, mf2.ptr, 1, 0);
 ----
 +
-While `git log -G"regexec\(regexp"` will show this commit, `git log
--S"regexec\(regexp" --pickaxe-regex` will not (because the number of
+While `git log -G"frotz\(nitfol"` will show this commit, `git log
+-S"frotz\(nitfol" --pickaxe-regex` will not (because the number of
 occurrences of that string did not change).
 +
 Unless `--text` is supplied patches of binary files without a textconv
index a2f78624a27e68dfe5a97adcac9cd67c109d1f52..a115a1ae0e973dc0375e82cbc1de5eff182aa9a1 100644 (file)
@@ -139,7 +139,10 @@ ifndef::git-pull[]
        specified refspec (can be given more than once) to map the
        refs to remote-tracking branches, instead of the values of
        `remote.*.fetch` configuration variables for the remote
-       repository.  See section on "Configured Remote-tracking
+       repository.  Providing an empty `<refspec>` to the
+       `--refmap` option causes Git to ignore the configured
+       refspecs and rely entirely on the refspecs supplied as
+       command-line arguments. See section on "Configured Remote-tracking
        Branches" for details.
 
 -t::
index 4b90b9c12a4a87fe563d367ab8252750021969d7..ec15ee8d6fad83d5dfe7d019cbb5984303552d9e 100644 (file)
@@ -69,7 +69,6 @@ OPTIONS
        Do not GPG-sign commit, to countermand a `--gpg-sign` option
        given earlier on the command line.
 
-
 Commit Information
 ------------------
 
@@ -79,26 +78,6 @@ A commit encapsulates:
 - author name, email and date
 - committer name and email and the commit time.
 
-While parent object ids are provided on the command line, author and
-committer information is taken from the following environment variables,
-if set:
-
-       GIT_AUTHOR_NAME
-       GIT_AUTHOR_EMAIL
-       GIT_AUTHOR_DATE
-       GIT_COMMITTER_NAME
-       GIT_COMMITTER_EMAIL
-       GIT_COMMITTER_DATE
-
-(nb "<", ">" and "\n"s are stripped)
-
-In case (some of) these environment variables are not set, the information
-is taken from the configuration items user.name and user.email, or, if not
-present, the environment variable EMAIL, or, if that is not set,
-system user name and the hostname used for outgoing mail (taken
-from `/etc/mailname` and falling back to the fully qualified hostname when
-that file does not exist).
-
 A commit comment is read from stdin. If a changelog
 entry is not provided via "<" redirection, 'git commit-tree' will just wait
 for one to be entered and terminated with ^D.
@@ -117,6 +96,7 @@ FILES
 SEE ALSO
 --------
 linkgit:git-write-tree[1]
+linkgit:git-commit[1]
 
 GIT
 ---
index ced5a9beab159de86148b94bc1825247ead79390..13f653989f32f1231030d8e89a70cb6a96c9955f 100644 (file)
@@ -367,9 +367,6 @@ changes to tracked files.
 +
 For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
 
-:git-commit: 1
-include::date-formats.txt[]
-
 EXAMPLES
 --------
 When recording your own work, the contents of modified files in
@@ -463,6 +460,43 @@ alter the order the changes are committed, because the merge
 should be recorded as a single commit.  In fact, the command
 refuses to run when given pathnames (but see `-i` option).
 
+COMMIT INFORMATION
+------------------
+
+Author and committer information is taken from the following environment
+variables, if set:
+
+       GIT_AUTHOR_NAME
+       GIT_AUTHOR_EMAIL
+       GIT_AUTHOR_DATE
+       GIT_COMMITTER_NAME
+       GIT_COMMITTER_EMAIL
+       GIT_COMMITTER_DATE
+
+(nb "<", ">" and "\n"s are stripped)
+
+The author and committer names are by convention some form of a personal name
+(that is, the name by which other humans refer to you), although Git does not
+enforce or require any particular form. Arbitrary Unicode may be used, subject
+to the constraints listed above. This name has no effect on authentication; for
+that, see the `credential.username` variable in linkgit:git-config[1].
+
+In case (some of) these environment variables are not set, the information
+is taken from the configuration items `user.name` and `user.email`, or, if not
+present, the environment variable EMAIL, or, if that is not set,
+system user name and the hostname used for outgoing mail (taken
+from `/etc/mailname` and falling back to the fully qualified hostname when
+that file does not exist).
+
+The `author.name` and `committer.name` and their corresponding email options
+override `user.name` and `user.email` if set and are overridden themselves by
+the environment variables.
+
+The typical usage is to set just the `user.name` and `user.email` variables;
+the other options are provided for more complex use cases.
+
+:git-commit: 1
+include::date-formats.txt[]
 
 DISCUSSION
 ----------
index a530fef7e5c08bf4bf01942ddbf1bcf10daa0b57..40ba4aa3e65b4c33886ba4334e5d3c84e979853e 100644 (file)
@@ -467,9 +467,9 @@ impossible for a backward-compatible implementation to ever be fast:
 
 * In editing files, git-filter-branch by design checks out each and
   every commit as it existed in the original repo.  If your repo has
-  10\^5 files and 10\^5 commits, but each commit only modifies 5
-  files, then git-filter-branch will make you do 10\^10 modifications,
-  despite only having (at most) 5*10^5 unique blobs.
+  `10^5` files and `10^5` commits, but each commit only modifies five
+  files, then git-filter-branch will make you do `10^10` modifications,
+  despite only having (at most) `5*10^5` unique blobs.
 
 * If you try and cheat and try to make git-filter-branch only work on
   files modified in a commit, then two things happen
index c89fb569e35855e79a43ee3cde784df5b7cb269c..ddb6acc0257ef25c415de9e63e4fb9bacd1728ea 100644 (file)
@@ -59,8 +59,8 @@ grep.extendedRegexp::
        other than 'default'.
 
 grep.threads::
-       Number of grep worker threads to use.  If unset (or set to 0),
-       8 threads are used by default (for now).
+       Number of grep worker threads to use. If unset (or set to 0), Git will
+       use as many threads as the number of logical cores available.
 
 grep.fullName::
        If set to true, enable `--full-name` option by default.
@@ -96,7 +96,8 @@ OPTIONS
        Recursively search in each submodule that has been initialized and
        checked out in the repository.  When used in combination with the
        <tree> option the prefix of all submodule output will be the name of
-       the parent project's <tree> object.
+       the parent project's <tree> object. This option has no effect
+       if `--no-index` is given.
 
 -a::
 --text::
@@ -347,6 +348,17 @@ EXAMPLES
 `git grep solution -- :^Documentation`::
        Looks for `solution`, excluding files in `Documentation`.
 
+NOTES ON THREADS
+----------------
+
+The `--threads` option (and the grep.threads configuration) will be ignored when
+`--open-files-in-pager` is used, forcing a single-threaded execution.
+
+When grepping the object store (with `--cached` or giving tree objects), running
+with multiple threads might perform slower than single threaded if `--textconv`
+is given and there're too many text conversions. So if you experience low
+performance in this case, it might be desirable to use `--threads=1`.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index 974ade22387f23905938e2a898f730168f7dd76b..a24d90529bd62a99dc46b945b0136e672a1a0374 100644 (file)
@@ -41,6 +41,10 @@ COMMANDS
 To avoid interfering with other worktrees, it first enables the
 `extensions.worktreeConfig` setting and makes sure to set the
 `core.sparseCheckout` setting in the worktree-specific config file.
++
+When `--cone` is provided, the `core.sparseCheckoutCone` setting is
+also set, allowing for better performance with a limited set of
+patterns (see 'CONE PATTERN SET' below).
 
 'set'::
        Write a set of patterns to the sparse-checkout file, as given as
@@ -50,6 +54,14 @@ To avoid interfering with other worktrees, it first enables the
 +
 When the `--stdin` option is provided, the patterns are read from
 standard in as a newline-delimited list instead of from the arguments.
++
+When `core.sparseCheckoutCone` is enabled, the input list is considered a
+list of directories instead of sparse-checkout patterns. The command writes
+patterns to the sparse-checkout file to include all files contained in those
+directories (recursively) as well as files that are siblings of ancestor
+directories. The input format matches the output of `git ls-tree --name-only`.
+This includes interpreting pathnames that begin with a double quote (") as
+C-style quoted strings.
 
 'disable'::
        Disable the `core.sparseCheckout` config setting, and restore the
@@ -106,7 +118,7 @@ The full pattern set allows for arbitrary pattern matches and complicated
 inclusion/exclusion rules. These can result in O(N*M) pattern matches when
 updating the index, where N is the number of patterns and M is the number
 of paths in the index. To combat this performance issue, a more restricted
-pattern set is allowed when `core.spareCheckoutCone` is enabled.
+pattern set is allowed when `core.sparseCheckoutCone` is enabled.
 
 The accepted patterns in the cone pattern set are:
 
@@ -128,9 +140,12 @@ the following patterns:
 ----------------
 
 This says "include everything in root, but nothing two levels below root."
-If we then add the folder `A/B/C` as a recursive pattern, the folders `A` and
-`A/B` are added as parent patterns. The resulting sparse-checkout file is
-now
+
+When in cone mode, the `git sparse-checkout set` subcommand takes a list of
+directories instead of a list of sparse-checkout patterns. In this mode,
+the command `git sparse-checkout set A/B/C` sets the directory `A/B/C` as
+a recursive pattern, the directories `A` and `A/B` are added as parent
+patterns. The resulting sparse-checkout file is now
 
 ----------------
 /*
index 5232407f68331618e5c509b91aef30ce6c6dbecd..218942acd11ffff39230a71617d119d323795299 100644 (file)
@@ -229,7 +229,7 @@ As an example, the command below will show the path and currently
 checked out commit for each submodule:
 +
 --------------
-git submodule foreach 'echo $path `git rev-parse HEAD`'
+git submodule foreach 'echo $sm_path `git rev-parse HEAD`'
 --------------
 
 sync [--recursive] [--] [<path>...]::
index c7a6271daf61507459fe3bb1a0bf494def4f994f..1489cb09a09997a64a102b58a8bf2b890640d6df 100644 (file)
@@ -549,6 +549,22 @@ The untracked cache extension can be enabled by the
 `core.untrackedCache` configuration variable (see
 linkgit:git-config[1]).
 
+NOTES
+-----
+
+Users often try to use the assume-unchanged and skip-worktree bits
+to tell Git to ignore changes to files that are tracked.  This does not
+work as expected, since Git may still check working tree files against
+the index when performing certain operations.  In general, Git does not
+provide a way to ignore changes to tracked files, so alternate solutions
+are recommended.
+
+For example, if the file you want to change is some sort of config file,
+the repository can include a sample config file that can then be copied
+into the ignored name and modified.  The repository can even include a
+script to treat the sample file as a template, modifying and copying it
+automatically.
+
 SEE ALSO
 --------
 linkgit:git-config[1],
index b1597ac002f15492b861b332519cf8b5ea2d9228..b0672bd8065fe0cb61ca9e4563b8932a501fb3d9 100644 (file)
@@ -110,9 +110,23 @@ foo.bar= ...`) sets `foo.bar` to the empty string which `git config
        Do not pipe Git output into a pager.
 
 --git-dir=<path>::
-       Set the path to the repository. This can also be controlled by
-       setting the `GIT_DIR` environment variable. It can be an absolute
-       path or relative path to current working directory.
+       Set the path to the repository (".git" directory). This can also be
+       controlled by setting the `GIT_DIR` environment variable. It can be
+       an absolute path or relative path to current working directory.
++
+Specifying the location of the ".git" directory using this
+option (or `GIT_DIR` environment variable) turns off the
+repository discovery that tries to find a directory with
+".git" subdirectory (which is how the repository and the
+top-level of the working tree are discovered), and tells Git
+that you are at the top level of the working tree.  If you
+are not at the top-level directory of the working tree, you
+should tell Git where the top-level of the working tree is,
+with the `--work-tree=<path>` option (or `GIT_WORK_TREE`
+environment variable)
++
+If you just want to run git as if it was started in `<path>` then use
+`git -C <path>`.
 
 --work-tree=<path>::
        Set the path to the working tree. It can be an absolute path
@@ -482,13 +496,36 @@ double-quotes and respecting backslash escapes. E.g., the value
 Git Commits
 ~~~~~~~~~~~
 `GIT_AUTHOR_NAME`::
+       The human-readable name used in the author identity when creating commit or
+       tag objects, or when writing reflogs. Overrides the `user.name` and
+       `author.name` configuration settings.
+
 `GIT_AUTHOR_EMAIL`::
+       The email address used in the author identity when creating commit or
+       tag objects, or when writing reflogs. Overrides the `user.email` and
+       `author.email` configuration settings.
+
 `GIT_AUTHOR_DATE`::
+       The date used for the author identity when creating commit or tag objects, or
+       when writing reflogs. See linkgit:git-commit[1] for valid formats.
+
 `GIT_COMMITTER_NAME`::
+       The human-readable name used in the committer identity when creating commit or
+       tag objects, or when writing reflogs. Overrides the `user.name` and
+       `committer.name` configuration settings.
+
 `GIT_COMMITTER_EMAIL`::
+       The email address used in the author identity when creating commit or
+       tag objects, or when writing reflogs. Overrides the `user.email` and
+       `committer.email` configuration settings.
+
 `GIT_COMMITTER_DATE`::
-'EMAIL'::
-       see linkgit:git-commit-tree[1]
+       The date used for the committer identity when creating commit or tag objects, or
+       when writing reflogs. See linkgit:git-commit[1] for valid formats.
+
+`EMAIL`::
+       The email address used in the author and committer identities if no other
+       relevant environment variable or configuration setting has been set.
 
 Git Diffs
 ~~~~~~~~~
index f880d21dfb520c4a7bed90ccd0f2cc9d3961ecf7..c0b95256cc8c8d640d284f77b23d63c8785601d1 100644 (file)
@@ -751,7 +751,7 @@ to it.
 ================================================
 If you make the decision to start your new branch at some
 other point in the history than the current `HEAD`, you can do so by
-just telling 'git checkout' what the base of the checkout would be.
+just telling 'git switch' what the base of the checkout would be.
 In other words, if you have an earlier tag or branch, you'd just do
 
 ------------
index 50365f2914e04fe26969f00d3745e171375779db..3dccab53758717d6e7822d5cdfefd002864e9276 100644 (file)
@@ -490,9 +490,16 @@ fsmonitor-watchman
 ~~~~~~~~~~~~~~~~~~
 
 This hook is invoked when the configuration option `core.fsmonitor` is
-set to `.git/hooks/fsmonitor-watchman`.  It takes two arguments, a version
-(currently 1) and the time in elapsed nanoseconds since midnight,
-January 1, 1970.
+set to `.git/hooks/fsmonitor-watchman` or `.git/hooks/fsmonitor-watchmanv2`
+depending on the version of the hook to use.
+
+Version 1 takes two arguments, a version (1) and the time in elapsed
+nanoseconds since midnight, January 1, 1970.
+
+Version 2 takes two arguments, a version (2) and a token that is used
+for identifying changes since the token. For watchman this would be
+a clock id. This version must output to stdout the new token followed
+by a NUL before the list of files.
 
 The hook should output to stdout the list of all files in the working
 directory that may have changed since the requested time.  The logic
index 1a7212ce5ab8147a810c10395bb4b558958cf6ac..a4b6f49186319ae094d3a7182ab9f7e0a8925ef9 100644 (file)
@@ -226,6 +226,7 @@ endif::git-rev-list[]
 '%GF':: show the fingerprint of the key used to sign a signed commit
 '%GP':: show the fingerprint of the primary key whose subkey was used
        to sign a signed commit
+'%GT':: show the trust level for the key used to sign a signed commit
 '%gD':: reflog selector, e.g., `refs/stash@{1}` or `refs/stash@{2
        minutes ago}`; the format follows the rules described for the
        `-g` option. The portion before the `@` is the refname as
diff --git a/Documentation/technical/bundle-format.txt b/Documentation/technical/bundle-format.txt
new file mode 100644 (file)
index 0000000..0e82815
--- /dev/null
@@ -0,0 +1,48 @@
+= Git bundle v2 format
+
+The Git bundle format is a format that represents both refs and Git objects.
+
+== Format
+
+We will use ABNF notation to define the Git bundle format. See
+protocol-common.txt for the details.
+
+----
+bundle    = signature *prerequisite *reference LF pack
+signature = "# v2 git bundle" LF
+
+prerequisite = "-" obj-id SP comment LF
+comment      = *CHAR
+reference    = obj-id SP refname LF
+
+pack         = ... ; packfile
+----
+
+== Semantics
+
+A Git bundle consists of three parts.
+
+* "Prerequisites" lists the objects that are NOT included in the bundle and the
+  reader of the bundle MUST already have, in order to use the data in the
+  bundle. The objects stored in the bundle may refer to prerequisite objects and
+  anything reachable from them (e.g. a tree object in the bundle can reference
+  a blob that is reachable from a prerequisite) and/or expressed as a delta
+  against prerequisite objects.
+
+* "References" record the tips of the history graph, iow, what the reader of the
+  bundle CAN "git fetch" from it.
+
+* "Pack" is the pack data stream "git fetch" would send, if you fetch from a
+  repository that has the references recorded in the "References" above into a
+  repository that has references pointing at the objects listed in
+  "Prerequisites" above.
+
+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 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.
index cab5bdd2ff0f887cb991e2dc9ba3cccec34f8a0a..d3a142c6520258033f95b7e427be41f930883f99 100644 (file)
@@ -315,10 +315,11 @@ CHUNK DATA:
            Stores two 4-byte values for every object.
            1: The pack-int-id for the pack storing this object.
            2: The offset within the pack.
-               If all offsets are less than 2^31, then the large offset chunk
+               If all offsets are less than 2^32, then the large offset chunk
                will not exist and offsets are stored as in IDX v1.
                If there is at least one offset value larger than 2^32-1, then
-               the large offset chunk must exist. If the large offset chunk
+               the large offset chunk must exist, and offsets larger than
+               2^31-1 must be stored in it instead. If the large offset chunk
                exists and the 31st bit is on, then removing that bit reveals
                the row in the large offsets containing the 8-byte offset of
                this object.
index 2b7f62b8bfa042f10c5a0a76bdf33e002e7ff586..616d5a6404e4f1a6550b2aca89488889dbe6d34f 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.25.0
+DEF_VER=v2.25.GIT
 
 LF='
 '
index 09f98b777cae1dc9226f13c182011adacfd8b2fc..6134104ae65b713b665a40933b2df1a8c58e2269 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -721,6 +721,7 @@ TEST_BUILTINS_OBJS += test-mktemp.o
 TEST_BUILTINS_OBJS += test-oidmap.o
 TEST_BUILTINS_OBJS += test-online-cpus.o
 TEST_BUILTINS_OBJS += test-parse-options.o
+TEST_BUILTINS_OBJS += test-parse-pathspec-file.o
 TEST_BUILTINS_OBJS += test-path-utils.o
 TEST_BUILTINS_OBJS += test-pkt-line.o
 TEST_BUILTINS_OBJS += test-prio-queue.o
@@ -1220,6 +1221,9 @@ endif
 ifneq ($(filter leak,$(SANITIZERS)),)
 BASIC_CFLAGS += -DSUPPRESS_ANNOTATED_LEAKS
 endif
+ifneq ($(filter address,$(SANITIZERS)),)
+NO_REGEX = NeededForASAN
+endif
 endif
 
 ifndef sysconfdir
index 091dd024b349d6bc908371eddb7c594059c4fd70..370269d8dfc14c9dda695f8bdbc84febe64771a3 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.25.0.txt
\ No newline at end of file
+Documentation/RelNotes/2.26.0.txt
\ No newline at end of file
index 6a5048c83e4d6f08ae3ecfdb6beab125cde10a05..4a9bf85cac033b0cf22665918fccee561678eb3d 100644 (file)
@@ -52,6 +52,24 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
                diff_get_color(s->use_color, DIFF_FILE_OLD));
        init_color(r, s, "new", s->file_new_color,
                diff_get_color(s->use_color, DIFF_FILE_NEW));
+
+       FREE_AND_NULL(s->interactive_diff_filter);
+       git_config_get_string("interactive.difffilter",
+                             &s->interactive_diff_filter);
+
+       FREE_AND_NULL(s->interactive_diff_algorithm);
+       git_config_get_string("diff.algorithm",
+                             &s->interactive_diff_algorithm);
+
+       git_config_get_bool("interactive.singlekey", &s->use_single_key);
+}
+
+void clear_add_i_state(struct add_i_state *s)
+{
+       FREE_AND_NULL(s->interactive_diff_filter);
+       FREE_AND_NULL(s->interactive_diff_algorithm);
+       memset(s, 0, sizeof(*s));
+       s->use_color = -1;
 }
 
 /*
@@ -326,7 +344,10 @@ static ssize_t list_and_choose(struct add_i_state *s,
                                if (endp == p + sep)
                                        to = from + 1;
                                else if (*endp == '-') {
-                                       to = strtoul(++endp, &endp, 10);
+                                       if (isdigit(*(++endp)))
+                                               to = strtoul(endp, &endp, 10);
+                                       else
+                                               to = items->items.nr;
                                        /* extra characters after the range? */
                                        if (endp != p + sep)
                                                from = -1;
@@ -913,7 +934,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
 
        opts->prompt = N_("Patch update");
        count = list_and_choose(s, files, opts);
-       if (count >= 0) {
+       if (count > 0) {
                struct argv_array args = ARGV_ARRAY_INIT;
                struct pathspec ps_selected = { 0 };
 
@@ -924,7 +945,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
                parse_pathspec(&ps_selected,
                               PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
                               PATHSPEC_LITERAL_PATH, "", args.argv);
-               res = run_add_p(s->r, &ps_selected);
+               res = run_add_p(s->r, ADD_P_ADD, NULL, &ps_selected);
                argv_array_clear(&args);
                clear_pathspec(&ps_selected);
        }
@@ -954,7 +975,7 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,
        opts->flags = IMMEDIATE;
        count = list_and_choose(s, files, opts);
        opts->flags = 0;
-       if (count >= 0) {
+       if (count > 0) {
                struct argv_array args = ARGV_ARRAY_INIT;
 
                argv_array_pushl(&args, "git", "diff", "-p", "--cached",
@@ -1149,6 +1170,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
        strbuf_release(&print_file_item_data.worktree);
        strbuf_release(&header);
        prefix_item_list_clear(&commands);
+       clear_add_i_state(&s);
 
        return res;
 }
index 062dc3646c2fd41df3b767d8fa64c8e454e846c0..693f125e8e4bc64ac0ccc8ac3b8dea812a397330 100644 (file)
@@ -15,13 +15,27 @@ struct add_i_state {
        char context_color[COLOR_MAXLEN];
        char file_old_color[COLOR_MAXLEN];
        char file_new_color[COLOR_MAXLEN];
+
+       int use_single_key;
+       char *interactive_diff_filter, *interactive_diff_algorithm;
 };
 
 void init_add_i_state(struct add_i_state *s, struct repository *r);
+void clear_add_i_state(struct add_i_state *s);
 
 struct repository;
 struct pathspec;
 int run_add_i(struct repository *r, const struct pathspec *ps);
-int run_add_p(struct repository *r, const struct pathspec *ps);
+
+enum add_p_mode {
+       ADD_P_ADD,
+       ADD_P_STASH,
+       ADD_P_RESET,
+       ADD_P_CHECKOUT,
+       ADD_P_WORKTREE,
+};
+
+int run_add_p(struct repository *r, enum add_p_mode mode,
+             const char *revision, const struct pathspec *ps);
 
 #endif
index 2c46fe5b3332bf844007ea0d17dee250f0ba5756..d8dafa8168dc8389468d8c2c3cd6221220826605 100644 (file)
 #include "pathspec.h"
 #include "color.h"
 #include "diff.h"
+#include "compat/terminal.h"
 
 enum prompt_mode_type {
-       PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK
+       PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK,
+       PROMPT_MODE_MAX, /* must be last */
 };
 
-static const char *prompt_mode[] = {
-       N_("Stage mode change [y,n,a,q,d%s,?]? "),
-       N_("Stage deletion [y,n,a,q,d%s,?]? "),
-       N_("Stage this hunk [y,n,a,q,d%s,?]? ")
+struct patch_mode {
+       /*
+        * The magic constant 4 is chosen such that all patch modes
+        * provide enough space for three command-line arguments followed by a
+        * trailing `NULL`.
+        */
+       const char *diff_cmd[4], *apply_args[4], *apply_check_args[4];
+       unsigned is_reverse:1, index_only:1, apply_for_checkout:1;
+       const char *prompt_mode[PROMPT_MODE_MAX];
+       const char *edit_hunk_hint, *help_patch_text;
+};
+
+static struct patch_mode patch_mode_add = {
+       .diff_cmd = { "diff-files", NULL },
+       .apply_args = { "--cached", NULL },
+       .apply_check_args = { "--cached", NULL },
+       .prompt_mode = {
+               N_("Stage mode change [y,n,q,a,d%s,?]? "),
+               N_("Stage deletion [y,n,q,a,d%s,?]? "),
+               N_("Stage this hunk [y,n,q,a,d%s,?]? ")
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for staging."),
+       .help_patch_text =
+               N_("y - stage this hunk\n"
+                  "n - do not stage this hunk\n"
+                  "q - quit; do not stage this hunk or any of the remaining "
+                       "ones\n"
+                  "a - stage this hunk and all later hunks in the file\n"
+                  "d - do not stage this hunk or any of the later hunks in "
+                       "the file\n")
+};
+
+static struct patch_mode patch_mode_stash = {
+       .diff_cmd = { "diff-index", "HEAD", NULL },
+       .apply_args = { "--cached", NULL },
+       .apply_check_args = { "--cached", NULL },
+       .prompt_mode = {
+               N_("Stash mode change [y,n,q,a,d%s,?]? "),
+               N_("Stash deletion [y,n,q,a,d%s,?]? "),
+               N_("Stash this hunk [y,n,q,a,d%s,?]? "),
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for stashing."),
+       .help_patch_text =
+               N_("y - stash this hunk\n"
+                  "n - do not stash this hunk\n"
+                  "q - quit; do not stash this hunk or any of the remaining "
+                       "ones\n"
+                  "a - stash this hunk and all later hunks in the file\n"
+                  "d - do not stash this hunk or any of the later hunks in "
+                       "the file\n"),
+};
+
+static struct patch_mode patch_mode_reset_head = {
+       .diff_cmd = { "diff-index", "--cached", NULL },
+       .apply_args = { "-R", "--cached", NULL },
+       .apply_check_args = { "-R", "--cached", NULL },
+       .is_reverse = 1,
+       .index_only = 1,
+       .prompt_mode = {
+               N_("Unstage mode change [y,n,q,a,d%s,?]? "),
+               N_("Unstage deletion [y,n,q,a,d%s,?]? "),
+               N_("Unstage this hunk [y,n,q,a,d%s,?]? "),
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for unstaging."),
+       .help_patch_text =
+               N_("y - unstage this hunk\n"
+                  "n - do not unstage this hunk\n"
+                  "q - quit; do not unstage this hunk or any of the remaining "
+                       "ones\n"
+                  "a - unstage this hunk and all later hunks in the file\n"
+                  "d - do not unstage this hunk or any of the later hunks in "
+                       "the file\n"),
+};
+
+static struct patch_mode patch_mode_reset_nothead = {
+       .diff_cmd = { "diff-index", "-R", "--cached", NULL },
+       .apply_args = { "--cached", NULL },
+       .apply_check_args = { "--cached", NULL },
+       .index_only = 1,
+       .prompt_mode = {
+               N_("Apply mode change to index [y,n,q,a,d%s,?]? "),
+               N_("Apply deletion to index [y,n,q,a,d%s,?]? "),
+               N_("Apply this hunk to index [y,n,q,a,d%s,?]? "),
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for applying."),
+       .help_patch_text =
+               N_("y - apply this hunk to index\n"
+                  "n - do not apply this hunk to index\n"
+                  "q - quit; do not apply this hunk or any of the remaining "
+                       "ones\n"
+                  "a - apply this hunk and all later hunks in the file\n"
+                  "d - do not apply this hunk or any of the later hunks in "
+                       "the file\n"),
+};
+
+static struct patch_mode patch_mode_checkout_index = {
+       .diff_cmd = { "diff-files", NULL },
+       .apply_args = { "-R", NULL },
+       .apply_check_args = { "-R", NULL },
+       .is_reverse = 1,
+       .prompt_mode = {
+               N_("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for discarding."),
+       .help_patch_text =
+               N_("y - discard this hunk from worktree\n"
+                  "n - do not discard this hunk from worktree\n"
+                  "q - quit; do not discard this hunk or any of the remaining "
+                       "ones\n"
+                  "a - discard this hunk and all later hunks in the file\n"
+                  "d - do not discard this hunk or any of the later hunks in "
+                       "the file\n"),
+};
+
+static struct patch_mode patch_mode_checkout_head = {
+       .diff_cmd = { "diff-index", NULL },
+       .apply_for_checkout = 1,
+       .apply_check_args = { "-R", NULL },
+       .is_reverse = 1,
+       .prompt_mode = {
+               N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for discarding."),
+       .help_patch_text =
+               N_("y - discard this hunk from index and worktree\n"
+                  "n - do not discard this hunk from index and worktree\n"
+                  "q - quit; do not discard this hunk or any of the remaining "
+                       "ones\n"
+                  "a - discard this hunk and all later hunks in the file\n"
+                  "d - do not discard this hunk or any of the later hunks in "
+                       "the file\n"),
+};
+
+static struct patch_mode patch_mode_checkout_nothead = {
+       .diff_cmd = { "diff-index", "-R", NULL },
+       .apply_for_checkout = 1,
+       .apply_check_args = { NULL },
+       .prompt_mode = {
+               N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for applying."),
+       .help_patch_text =
+               N_("y - apply this hunk to index and worktree\n"
+                  "n - do not apply this hunk to index and worktree\n"
+                  "q - quit; do not apply this hunk or any of the remaining "
+                       "ones\n"
+                  "a - apply this hunk and all later hunks in the file\n"
+                  "d - do not apply this hunk or any of the later hunks in "
+                       "the file\n"),
+};
+
+static struct patch_mode patch_mode_worktree_head = {
+       .diff_cmd = { "diff-index", NULL },
+       .apply_args = { "-R", NULL },
+       .apply_check_args = { "-R", NULL },
+       .is_reverse = 1,
+       .prompt_mode = {
+               N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for discarding."),
+       .help_patch_text =
+               N_("y - discard this hunk from worktree\n"
+                  "n - do not discard this hunk from worktree\n"
+                  "q - quit; do not discard this hunk or any of the remaining "
+                       "ones\n"
+                  "a - discard this hunk and all later hunks in the file\n"
+                  "d - do not discard this hunk or any of the later hunks in "
+                       "the file\n"),
+};
+
+static struct patch_mode patch_mode_worktree_nothead = {
+       .diff_cmd = { "diff-index", "-R", NULL },
+       .apply_args = { NULL },
+       .apply_check_args = { NULL },
+       .prompt_mode = {
+               N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for applying."),
+       .help_patch_text =
+               N_("y - apply this hunk to worktree\n"
+                  "n - do not apply this hunk to worktree\n"
+                  "q - quit; do not apply this hunk or any of the remaining "
+                       "ones\n"
+                  "a - apply this hunk and all later hunks in the file\n"
+                  "d - do not apply this hunk or any of the later hunks in "
+                       "the file\n"),
 };
 
 struct hunk_header {
@@ -47,6 +250,10 @@ struct add_p_state {
                unsigned deleted:1, mode_change:1,binary:1;
        } *file_diff;
        size_t file_diff_nr;
+
+       /* patch mode */
+       struct patch_mode *mode;
+       const char *revision;
 };
 
 static void err(struct add_p_state *s, const char *fmt, ...)
@@ -154,6 +361,7 @@ static int is_octal(const char *p, size_t len)
 static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
 {
        struct argv_array args = ARGV_ARRAY_INIT;
+       const char *diff_algorithm = s->s.interactive_diff_algorithm;
        struct strbuf *plain = &s->plain, *colored = NULL;
        struct child_process cp = CHILD_PROCESS_INIT;
        char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0';
@@ -162,9 +370,20 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
        struct hunk *hunk = NULL;
        int res;
 
+       argv_array_pushv(&args, s->mode->diff_cmd);
+       if (diff_algorithm)
+               argv_array_pushf(&args, "--diff-algorithm=%s", diff_algorithm);
+       if (s->revision) {
+               struct object_id oid;
+               argv_array_push(&args,
+                               /* could be on an unborn branch */
+                               !strcmp("HEAD", s->revision) &&
+                               get_oid("HEAD", &oid) ?
+                               empty_tree_oid_hex() : s->revision);
+       }
+       color_arg_index = args.argc;
        /* Use `--no-color` explicitly, just in case `diff.color = always`. */
-       argv_array_pushl(&args, "diff-files", "-p", "--no-color", "--", NULL);
-       color_arg_index = args.argc - 2;
+       argv_array_pushl(&args, "--no-color", "-p", "--", NULL);
        for (i = 0; i < ps->nr; i++)
                argv_array_push(&args, ps->items[i].original);
 
@@ -183,6 +402,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
 
        if (want_color_fd(1, -1)) {
                struct child_process colored_cp = CHILD_PROCESS_INIT;
+               const char *diff_filter = s->s.interactive_diff_filter;
 
                setup_child_process(s, &colored_cp, NULL);
                xsnprintf((char *)args.argv[color_arg_index], 8, "--color");
@@ -192,6 +412,24 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
                argv_array_clear(&args);
                if (res)
                        return error(_("could not parse colored diff"));
+
+               if (diff_filter) {
+                       struct child_process filter_cp = CHILD_PROCESS_INIT;
+
+                       setup_child_process(s, &filter_cp,
+                                           diff_filter, NULL);
+                       filter_cp.git_cmd = 0;
+                       filter_cp.use_shell = 1;
+                       strbuf_reset(&s->buf);
+                       if (pipe_command(&filter_cp,
+                                        colored->buf, colored->len,
+                                        &s->buf, colored->len,
+                                        NULL, 0) < 0)
+                               return error(_("failed to run '%s'"),
+                                            diff_filter);
+                       strbuf_swap(colored, &s->buf);
+               }
+
                strbuf_complete_line(colored);
                colored_p = colored->buf;
                colored_pend = colored_p + colored->len;
@@ -316,6 +554,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
                                                   colored_pend - colored_p);
                        if (colored_eol)
                                colored_p = colored_eol + 1;
+                       else if (p != pend)
+                               /* colored shorter than non-colored? */
+                               goto mismatched_output;
                        else
                                colored_p = colored_pend;
 
@@ -340,6 +581,15 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
                 */
                hunk->splittable_into++;
 
+       /* non-colored shorter than colored? */
+       if (colored_p != colored_pend) {
+mismatched_output:
+               error(_("mismatched output from interactive.diffFilter"));
+               advise(_("Your filter must maintain a one-to-one correspondence\n"
+                        "between its input and output lines."));
+               return -1;
+       }
+
        return 0;
 }
 
@@ -382,7 +632,10 @@ static void render_hunk(struct add_p_state *s, struct hunk *hunk,
                                - header->colored_extra_start;
                }
 
-               new_offset += delta;
+               if (s->mode->is_reverse)
+                       old_offset -= delta;
+               else
+                       new_offset += delta;
 
                strbuf_addf(out, "@@ -%lu,%lu +%lu,%lu @@",
                            old_offset, header->old_count,
@@ -805,11 +1058,10 @@ static int edit_hunk_manually(struct add_p_state *s, struct hunk *hunk)
                                "(context).\n"
                                "To remove '%c' lines, delete them.\n"
                                "Lines starting with %c will be removed.\n"),
-                             '-', '+', comment_line_char);
-       strbuf_commented_addf(&s->buf,
-                             _("If the patch applies cleanly, the edited hunk "
-                               "will immediately be\n"
-                               "marked for staging.\n"));
+                             s->mode->is_reverse ? '+' : '-',
+                             s->mode->is_reverse ? '-' : '+',
+                             comment_line_char);
+       strbuf_commented_addf(&s->buf, "%s", _(s->mode->edit_hunk_hint));
        /*
         * TRANSLATORS: 'it' refers to the patch mentioned in the previous
         * messages.
@@ -890,21 +1142,35 @@ static int run_apply_check(struct add_p_state *s,
        reassemble_patch(s, file_diff, 1, &s->buf);
 
        setup_child_process(s, &cp,
-                           "apply", "--cached", "--check", NULL);
+                           "apply", "--check", NULL);
+       argv_array_pushv(&cp.args, s->mode->apply_check_args);
        if (pipe_command(&cp, s->buf.buf, s->buf.len, NULL, 0, NULL, 0))
                return error(_("'git apply --cached' failed"));
 
        return 0;
 }
 
+static int read_single_character(struct add_p_state *s)
+{
+       if (s->s.use_single_key) {
+               int res = read_key_without_echo(&s->answer);
+               printf("%s\n", res == EOF ? "" : s->answer.buf);
+               return res;
+       }
+
+       if (strbuf_getline(&s->answer, stdin) == EOF)
+               return EOF;
+       strbuf_trim_trailing_newline(&s->answer);
+       return 0;
+}
+
 static int prompt_yesno(struct add_p_state *s, const char *prompt)
 {
        for (;;) {
                color_fprintf(stdout, s->s.prompt_color, "%s", _(prompt));
                fflush(stdout);
-               if (strbuf_getline(&s->answer, stdin) == EOF)
+               if (read_single_character(s) == EOF)
                        return -1;
-               strbuf_trim_trailing_newline(&s->answer);
                switch (tolower(s->answer.buf[0])) {
                case 'n': return 0;
                case 'y': return 1;
@@ -957,6 +1223,57 @@ static int edit_hunk_loop(struct add_p_state *s,
        }
 }
 
+static int apply_for_checkout(struct add_p_state *s, struct strbuf *diff,
+                             int is_reverse)
+{
+       const char *reverse = is_reverse ? "-R" : NULL;
+       struct child_process check_index = CHILD_PROCESS_INIT;
+       struct child_process check_worktree = CHILD_PROCESS_INIT;
+       struct child_process apply_index = CHILD_PROCESS_INIT;
+       struct child_process apply_worktree = CHILD_PROCESS_INIT;
+       int applies_index, applies_worktree;
+
+       setup_child_process(s, &check_index,
+                           "apply", "--cached", "--check", reverse, NULL);
+       applies_index = !pipe_command(&check_index, diff->buf, diff->len,
+                                     NULL, 0, NULL, 0);
+
+       setup_child_process(s, &check_worktree,
+                           "apply", "--check", reverse, NULL);
+       applies_worktree = !pipe_command(&check_worktree, diff->buf, diff->len,
+                                        NULL, 0, NULL, 0);
+
+       if (applies_worktree && applies_index) {
+               setup_child_process(s, &apply_index,
+                                   "apply", "--cached", reverse, NULL);
+               pipe_command(&apply_index, diff->buf, diff->len,
+                            NULL, 0, NULL, 0);
+
+               setup_child_process(s, &apply_worktree,
+                                   "apply", reverse, NULL);
+               pipe_command(&apply_worktree, diff->buf, diff->len,
+                            NULL, 0, NULL, 0);
+
+               return 1;
+       }
+
+       if (!applies_index) {
+               err(s, _("The selected hunks do not apply to the index!"));
+               if (prompt_yesno(s, _("Apply them to the worktree "
+                                         "anyway? ")) > 0) {
+                       setup_child_process(s, &apply_worktree,
+                                           "apply", reverse, NULL);
+                       return pipe_command(&apply_worktree, diff->buf,
+                                           diff->len, NULL, 0, NULL, 0);
+               }
+               err(s, _("Nothing was applied.\n"));
+       } else
+               /* As a last resort, show the diff to the user */
+               fwrite(diff->buf, diff->len, 1, stderr);
+
+       return 0;
+}
+
 #define SUMMARY_HEADER_WIDTH 20
 #define SUMMARY_LINE_WIDTH 80
 static void summarize_hunk(struct add_p_state *s, struct hunk *hunk,
@@ -1005,13 +1322,6 @@ static size_t display_hunks(struct add_p_state *s,
        return end_index;
 }
 
-static const char help_patch_text[] =
-N_("y - stage this hunk\n"
-   "n - do not stage this hunk\n"
-   "q - quit; do not stage this hunk or any of the remaining ones\n"
-   "a - stage this and all the remaining hunks\n"
-   "d - do not stage this hunk nor any of the remaining hunks\n");
-
 static const char help_patch_remainder[] =
 N_("j - leave this hunk undecided, see next undecided hunk\n"
    "J - leave this hunk undecided, see next hunk\n"
@@ -1097,11 +1407,11 @@ static int patch_update_file(struct add_p_state *s,
                              (uintmax_t)hunk_index + 1,
                              (uintmax_t)file_diff->hunk_nr);
                color_fprintf(stdout, s->s.prompt_color,
-                             _(prompt_mode[prompt_mode_type]), s->buf.buf);
+                             _(s->mode->prompt_mode[prompt_mode_type]),
+                             s->buf.buf);
                fflush(stdout);
-               if (strbuf_getline(&s->answer, stdin) == EOF)
+               if (read_single_character(s) == EOF)
                        break;
-               strbuf_trim_trailing_newline(&s->answer);
 
                if (!s->answer.len)
                        continue;
@@ -1254,7 +1564,7 @@ soft_increment:
                        const char *p = _(help_patch_remainder), *eol = p;
 
                        color_fprintf(stdout, s->s.help_color, "%s",
-                                     _(help_patch_text));
+                                     _(s->mode->help_patch_text));
 
                        /*
                         * Show only those lines of the remainder that are
@@ -1288,10 +1598,16 @@ soft_increment:
                reassemble_patch(s, file_diff, 0, &s->buf);
 
                discard_index(s->s.r->index);
-               setup_child_process(s, &cp, "apply", "--cached", NULL);
-               if (pipe_command(&cp, s->buf.buf, s->buf.len,
-                                NULL, 0, NULL, 0))
-                       error(_("'git apply --cached' failed"));
+               if (s->mode->apply_for_checkout)
+                       apply_for_checkout(s, &s->buf,
+                                          s->mode->is_reverse);
+               else {
+                       setup_child_process(s, &cp, "apply", NULL);
+                       argv_array_pushv(&cp.args, s->mode->apply_args);
+                       if (pipe_command(&cp, s->buf.buf, s->buf.len,
+                                        NULL, 0, NULL, 0))
+                               error(_("'git apply' failed"));
+               }
                if (!repo_read_index(s->s.r))
                        repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0,
                                                     1, NULL, NULL, NULL);
@@ -1301,7 +1617,8 @@ soft_increment:
        return quit;
 }
 
-int run_add_p(struct repository *r, const struct pathspec *ps)
+int run_add_p(struct repository *r, enum add_p_mode mode,
+             const char *revision, const struct pathspec *ps)
 {
        struct add_p_state s = {
                { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
@@ -1310,12 +1627,39 @@ int run_add_p(struct repository *r, const struct pathspec *ps)
 
        init_add_i_state(&s.s, r);
 
+       if (mode == ADD_P_STASH)
+               s.mode = &patch_mode_stash;
+       else if (mode == ADD_P_RESET) {
+               if (!revision || !strcmp(revision, "HEAD"))
+                       s.mode = &patch_mode_reset_head;
+               else
+                       s.mode = &patch_mode_reset_nothead;
+       } else if (mode == ADD_P_CHECKOUT) {
+               if (!revision)
+                       s.mode = &patch_mode_checkout_index;
+               else if (!strcmp(revision, "HEAD"))
+                       s.mode = &patch_mode_checkout_head;
+               else
+                       s.mode = &patch_mode_checkout_nothead;
+       } else if (mode == ADD_P_WORKTREE) {
+               if (!revision)
+                       s.mode = &patch_mode_checkout_index;
+               else if (!strcmp(revision, "HEAD"))
+                       s.mode = &patch_mode_worktree_head;
+               else
+                       s.mode = &patch_mode_worktree_nothead;
+       } else
+               s.mode = &patch_mode_add;
+       s.revision = revision;
+
        if (discard_index(r->index) < 0 || repo_read_index(r) < 0 ||
-           repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
-                                        NULL, NULL, NULL) < 0 ||
+           (!s.mode->index_only &&
+            repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
+                                         NULL, NULL, NULL) < 0) ||
            parse_diff(&s, ps) < 0) {
                strbuf_release(&s.plain);
                strbuf_release(&s.colored);
+               clear_add_i_state(&s.s);
                return -1;
        }
 
@@ -1334,5 +1678,6 @@ int run_add_p(struct repository *r, const struct pathspec *ps)
        strbuf_release(&s.buf);
        strbuf_release(&s.plain);
        strbuf_release(&s.colored);
+       clear_add_i_state(&s.s);
        return 0;
 }
index 249c60dcf32e242b57316385fb31d52bec2a282a..97f3f981b4b484a9edfed7cedadc3c01e51ff47e 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -31,6 +31,8 @@ int advice_graft_file_deprecated = 1;
 int advice_checkout_ambiguous_remote_branch_name = 1;
 int advice_nested_tag = 1;
 int advice_submodule_alternate_error_strategy_die = 1;
+int advice_add_ignored_file = 1;
+int advice_add_empty_pathspec = 1;
 
 static int advice_use_color = -1;
 static char advice_colors[][COLOR_MAXLEN] = {
@@ -91,6 +93,8 @@ static struct {
        { "checkoutAmbiguousRemoteBranchName", &advice_checkout_ambiguous_remote_branch_name },
        { "nestedTag", &advice_nested_tag },
        { "submoduleAlternateErrorStrategyDie", &advice_submodule_alternate_error_strategy_die },
+       { "addIgnoredFile", &advice_add_ignored_file },
+       { "addEmptyPathspec", &advice_add_empty_pathspec },
 
        /* make this an alias for backward compatibility */
        { "pushNonFastForward", &advice_push_update_rejected }
index b706780614dd3721b71b79feed9b3e0bdae3964c..0e6e58d9f8f79df4b4f6ae242cf1fb0a7d179d1b 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -31,6 +31,8 @@ extern int advice_graft_file_deprecated;
 extern int advice_checkout_ambiguous_remote_branch_name;
 extern int advice_nested_tag;
 extern int advice_submodule_alternate_error_strategy_die;
+extern int advice_add_ignored_file;
+extern int advice_add_empty_pathspec;
 
 int git_default_advice_config(const char *var, const char *value);
 __attribute__((format (printf, 1, 2)))
index 4c38aff41957a07cc52eee8ac64bbb4afe2969e4..18a0881ecf951cf79be33eee3470edca39dcf395 100644 (file)
@@ -31,6 +31,7 @@ static int take_worktree_changes;
 static int add_renormalize;
 static int pathspec_file_nul;
 static const char *pathspec_from_file;
+static int legacy_stash_p; /* support for the scripted `git stash` */
 
 struct update_callback_data {
        int flags;
@@ -196,12 +197,25 @@ int run_add_interactive(const char *revision, const char *patch_mode,
                                    &use_builtin_add_i);
 
        if (use_builtin_add_i == 1) {
+               enum add_p_mode mode;
+
                if (!patch_mode)
                        return !!run_add_i(the_repository, pathspec);
-               if (strcmp(patch_mode, "--patch"))
-                       die("'%s' not yet supported in the built-in add -p",
-                           patch_mode);
-               return !!run_add_p(the_repository, pathspec);
+
+               if (!strcmp(patch_mode, "--patch"))
+                       mode = ADD_P_ADD;
+               else if (!strcmp(patch_mode, "--patch=stash"))
+                       mode = ADD_P_STASH;
+               else if (!strcmp(patch_mode, "--patch=reset"))
+                       mode = ADD_P_RESET;
+               else if (!strcmp(patch_mode, "--patch=checkout"))
+                       mode = ADD_P_CHECKOUT;
+               else if (!strcmp(patch_mode, "--patch=worktree"))
+                       mode = ADD_P_WORKTREE;
+               else
+                       die("'%s' not supported", patch_mode);
+
+               return !!run_add_p(the_repository, mode, revision, pathspec);
        }
 
        argv_array_push(&argv, "add--interactive");
@@ -327,6 +341,8 @@ static struct option builtin_add_options[] = {
                   N_("override the executable bit of the listed files")),
        OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
                        N_("warn when adding an embedded repository")),
+       OPT_HIDDEN_BOOL(0, "legacy-stash-p", &legacy_stash_p,
+                       N_("backend for `git stash -p`")),
        OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
        OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
        OPT_END(),
@@ -390,7 +406,10 @@ static int add_files(struct dir_struct *dir, int flags)
                fprintf(stderr, _(ignore_error));
                for (i = 0; i < dir->ignored_nr; i++)
                        fprintf(stderr, "%s\n", dir->ignored[i]->name);
-               fprintf(stderr, _("Use -f if you really want to add them.\n"));
+               if (advice_add_ignored_file)
+                       advise(_("Use -f if you really want to add them.\n"
+                               "Turn this message off by running\n"
+                               "\"git config advice.addIgnoredFile false\""));
                exit_status = 1;
        }
 
@@ -428,6 +447,17 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                        die(_("--pathspec-from-file is incompatible with --interactive/--patch"));
                exit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive));
        }
+       if (legacy_stash_p) {
+               struct pathspec pathspec;
+
+               parse_pathspec(&pathspec, 0,
+                       PATHSPEC_PREFER_FULL |
+                       PATHSPEC_SYMLINK_LEADING_PATH |
+                       PATHSPEC_PREFIX_ORIGIN,
+                       prefix, argv);
+
+               return run_add_interactive(NULL, "--patch=stash", &pathspec);
+       }
 
        if (edit_interactive) {
                if (pathspec_from_file)
@@ -480,7 +510,10 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 
        if (require_pathspec && pathspec.nr == 0) {
                fprintf(stderr, _("Nothing specified, nothing added.\n"));
-               fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
+               if (advice_add_empty_pathspec)
+                       advise( _("Maybe you wanted to say 'git add .'?\n"
+                               "Turn this message off by running\n"
+                               "\"git config advice.addEmptyPathspec false\""));
                return 0;
        }
 
index b52c490c8f5404e24c05db289fc22539faf276e3..fc2eb1befcccecdc3bd583941d46a9a6e23c9496 100644 (file)
@@ -524,6 +524,8 @@ static int checkout_paths(const struct checkout_opts *opts,
        /* Now we are committed to check them out */
        if (opts->checkout_worktree)
                errs |= checkout_worktree(opts);
+       else
+               remove_marked_cache_entries(&the_index, 1);
 
        /*
         * Allow updating the index when checking out from the index.
@@ -1115,12 +1117,43 @@ static void setup_new_branch_info_and_source_tree(
        }
 }
 
+static const char *parse_remote_branch(const char *arg,
+                                      struct object_id *rev,
+                                      int could_be_checkout_paths)
+{
+       int num_matches = 0;
+       const char *remote = unique_tracking_name(arg, rev, &num_matches);
+
+       if (remote && could_be_checkout_paths) {
+               die(_("'%s' could be both a local file and a tracking branch.\n"
+                       "Please use -- (and optionally --no-guess) to disambiguate"),
+                   arg);
+       }
+
+       if (!remote && num_matches > 1) {
+           if (advice_checkout_ambiguous_remote_branch_name) {
+                   advise(_("If you meant to check out a remote tracking branch on, e.g. 'origin',\n"
+                            "you can do so by fully qualifying the name with the --track option:\n"
+                            "\n"
+                            "    git checkout --track origin/<name>\n"
+                            "\n"
+                            "If you'd like to always have checkouts of an ambiguous <name> prefer\n"
+                            "one remote, e.g. the 'origin' remote, consider setting\n"
+                            "checkout.defaultRemote=origin in your config."));
+           }
+
+           die(_("'%s' matched multiple (%d) remote tracking branches"),
+               arg, num_matches);
+       }
+
+       return remote;
+}
+
 static int parse_branchname_arg(int argc, const char **argv,
                                int dwim_new_local_branch_ok,
                                struct branch_info *new_branch_info,
                                struct checkout_opts *opts,
-                               struct object_id *rev,
-                               int *dwim_remotes_matched)
+                               struct object_id *rev)
 {
        const char **new_branch = &opts->new_branch;
        int argcount = 0;
@@ -1225,13 +1258,9 @@ static int parse_branchname_arg(int argc, const char **argv,
                        recover_with_dwim = 0;
 
                if (recover_with_dwim) {
-                       const char *remote = unique_tracking_name(arg, rev,
-                                                                 dwim_remotes_matched);
+                       const char *remote = parse_remote_branch(arg, rev,
+                                                                could_be_checkout_paths);
                        if (remote) {
-                               if (could_be_checkout_paths)
-                                       die(_("'%s' could be both a local file and a tracking branch.\n"
-                                             "Please use -- (and optionally --no-guess) to disambiguate"),
-                                           arg);
                                *new_branch = arg;
                                arg = remote;
                                /* DWIMmed to create local branch, case (3).(b) */
@@ -1496,7 +1525,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
                         const char * const usagestr[])
 {
        struct branch_info new_branch_info;
-       int dwim_remotes_matched = 0;
        int parseopt_flags = 0;
 
        memset(&new_branch_info, 0, sizeof(new_branch_info));
@@ -1604,8 +1632,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
                        opts->track == BRANCH_TRACK_UNSPECIFIED &&
                        !opts->new_branch;
                int n = parse_branchname_arg(argc, argv, dwim_ok,
-                                            &new_branch_info, opts, &rev,
-                                            &dwim_remotes_matched);
+                                            &new_branch_info, opts, &rev);
                argv += n;
                argc -= n;
        } else if (!opts->accept_ref && opts->from_treeish) {
@@ -1682,28 +1709,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
        }
 
        UNLEAK(opts);
-       if (opts->patch_mode || opts->pathspec.nr) {
-               int ret = checkout_paths(opts, new_branch_info.name);
-               if (ret && dwim_remotes_matched > 1 &&
-                   advice_checkout_ambiguous_remote_branch_name)
-                       advise(_("'%s' matched more than one remote tracking branch.\n"
-                                "We found %d remotes with a reference that matched. So we fell back\n"
-                                "on trying to resolve the argument as a path, but failed there too!\n"
-                                "\n"
-                                "If you meant to check out a remote tracking branch on, e.g. 'origin',\n"
-                                "you can do so by fully qualifying the name with the --track option:\n"
-                                "\n"
-                                "    git checkout --track origin/<name>\n"
-                                "\n"
-                                "If you'd like to always have checkouts of an ambiguous <name> prefer\n"
-                                "one remote, e.g. the 'origin' remote, consider setting\n"
-                                "checkout.defaultRemote=origin in your config."),
-                              argv[0],
-                              dwim_remotes_matched);
-               return ret;
-       } else {
+       if (opts->patch_mode || opts->pathspec.nr)
+               return checkout_paths(opts, new_branch_info.name);
+       else
                return checkout_branch(opts, &new_branch_info);
-       }
 }
 
 int cmd_checkout(int argc, const char **argv, const char *prefix)
index 0fc89ae2b9c77d420153c8bef6680d3324cdf5ad..4f6150c55c3a447b62de323b5cbe6640a7eb2cf2 100644 (file)
@@ -673,7 +673,7 @@ static void update_remote_refs(const struct ref *refs,
                               const char *msg,
                               struct transport *transport,
                               int check_connectivity,
-                              int check_refs_only)
+                              int check_refs_are_promisor_objects_only)
 {
        const struct ref *rm = mapped_refs;
 
@@ -682,7 +682,8 @@ static void update_remote_refs(const struct ref *refs,
 
                opt.transport = transport;
                opt.progress = transport->progress;
-               opt.check_refs_only = !!check_refs_only;
+               opt.check_refs_are_promisor_objects_only =
+                       !!check_refs_are_promisor_objects_only;
 
                if (check_connected(iterate_ref_map, &rm, &opt))
                        die(_("remote did not send all necessary objects"));
@@ -1128,7 +1129,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        if (option_required_reference.nr || option_optional_reference.nr)
                setup_reference();
 
-       if (option_sparse_checkout && git_sparse_checkout_init(repo))
+       if (option_sparse_checkout && git_sparse_checkout_init(dir))
                return 1;
 
        remote = remote_get(option_origin);
index aa1332308a243802f2fb8bc98d276fd66510949b..c70ad01cc906cff61aae08ad4956f03aed7e96fc 100644 (file)
@@ -367,7 +367,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
                die(_("index file corrupt"));
 
        if (interactive) {
-               char *old_index_env = NULL;
+               char *old_index_env = NULL, *old_repo_index_file;
                hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
 
                refresh_cache_or_die(refresh_flags);
@@ -375,12 +375,16 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
                if (write_locked_index(&the_index, &index_lock, 0))
                        die(_("unable to create temporary index"));
 
+               old_repo_index_file = the_repository->index_file;
+               the_repository->index_file =
+                       (char *)get_lock_file_path(&index_lock);
                old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
-               setenv(INDEX_ENVIRONMENT, get_lock_file_path(&index_lock), 1);
+               setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
 
                if (interactive_add(argc, argv, prefix, patch_interactive) != 0)
                        die(_("interactive add failed"));
 
+               the_repository->index_file = old_repo_index_file;
                if (old_index_env && *old_index_env)
                        setenv(INDEX_ENVIRONMENT, old_index_env, 1);
                else
@@ -964,6 +968,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
         */
        if (!committable && whence != FROM_MERGE && !allow_empty &&
            !(amend && is_a_merge(current_head))) {
+               s->hints = advice_status_hints;
                s->display_comment_prefix = old_display_comment_prefix;
                run_status(stdout, index_file, prefix, 0, s);
                if (amend)
index 25386b34d300e6dcee9c1f43a5de46a8b13673bb..85868162eec9b0080ab30e7b1b8de1560c5ce5f2 100644 (file)
@@ -871,8 +871,7 @@ static void handle_tag(const char *name, struct tag *tag)
                printf("reset %s\nfrom %s\n\n",
                       name, oid_to_hex(&null_oid));
        }
-       if (starts_with(name, "refs/tags/"))
-               name += 10;
+       skip_prefix(name, "refs/tags/", &name);
        printf("tag %s\n", name);
        if (mark_tags) {
                mark_next_object(&tag->object);
index b4c6d921d063734313f6d9c4746bfe7835885555..6fb50320eb6ed354ede802a0c6d1805feb6db4f2 100644 (file)
@@ -906,8 +906,17 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                url = xstrdup("foreign");
 
        if (!connectivity_checked) {
+               struct check_connected_options opt = CHECK_CONNECTED_INIT;
+
+               if (filter_options.choice)
+                       /*
+                        * Since a filter is specified, objects indirectly
+                        * referenced by refs are allowed to be absent.
+                        */
+                       opt.check_refs_are_promisor_objects_only = 1;
+
                rm = ref_map;
-               if (check_connected(iterate_ref_map, &rm, NULL)) {
+               if (check_connected(iterate_ref_map, &rm, &opt)) {
                        rc = error(_("%s did not send all necessary objects\n"), url);
                        goto abort;
                }
index 50ce8d946128c9f54d43d234a2117f3dba6d2299..99e26850907b74374000675aa19bb42cf39c32b2 100644 (file)
@@ -24,6 +24,7 @@
 #include "submodule.h"
 #include "submodule-config.h"
 #include "object-store.h"
+#include "packfile.h"
 
 static char const * const grep_usage[] = {
        N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"),
@@ -32,7 +33,6 @@ static char const * const grep_usage[] = {
 
 static int recurse_submodules;
 
-#define GREP_NUM_THREADS_DEFAULT 8
 static int num_threads;
 
 static pthread_t *threads;
@@ -91,8 +91,11 @@ static pthread_cond_t cond_result;
 
 static int skip_first_line;
 
-static void add_work(struct grep_opt *opt, const struct grep_source *gs)
+static void add_work(struct grep_opt *opt, struct grep_source *gs)
 {
+       if (opt->binary != GREP_BINARY_TEXT)
+               grep_source_load_driver(gs, opt->repo->index);
+
        grep_lock();
 
        while ((todo_end+1) % ARRAY_SIZE(todo) == todo_done) {
@@ -100,9 +103,6 @@ static void add_work(struct grep_opt *opt, const struct grep_source *gs)
        }
 
        todo[todo_end].source = *gs;
-       if (opt->binary != GREP_BINARY_TEXT)
-               grep_source_load_driver(&todo[todo_end].source,
-                                       opt->repo->index);
        todo[todo_end].done = 0;
        strbuf_reset(&todo[todo_end].out);
        todo_end = (todo_end + 1) % ARRAY_SIZE(todo);
@@ -200,12 +200,12 @@ static void start_threads(struct grep_opt *opt)
        int i;
 
        pthread_mutex_init(&grep_mutex, NULL);
-       pthread_mutex_init(&grep_read_mutex, NULL);
        pthread_mutex_init(&grep_attr_mutex, NULL);
        pthread_cond_init(&cond_add, NULL);
        pthread_cond_init(&cond_write, NULL);
        pthread_cond_init(&cond_result, NULL);
        grep_use_locks = 1;
+       enable_obj_read_lock();
 
        for (i = 0; i < ARRAY_SIZE(todo); i++) {
                strbuf_init(&todo[i].out, 0);
@@ -257,12 +257,12 @@ static int wait_all(void)
        free(threads);
 
        pthread_mutex_destroy(&grep_mutex);
-       pthread_mutex_destroy(&grep_read_mutex);
        pthread_mutex_destroy(&grep_attr_mutex);
        pthread_cond_destroy(&cond_add);
        pthread_cond_destroy(&cond_write);
        pthread_cond_destroy(&cond_result);
        grep_use_locks = 0;
+       disable_obj_read_lock();
 
        return hit;
 }
@@ -295,16 +295,6 @@ static int grep_cmd_config(const char *var, const char *value, void *cb)
        return st;
 }
 
-static void *lock_and_read_oid_file(const struct object_id *oid, enum object_type *type, unsigned long *size)
-{
-       void *data;
-
-       grep_read_lock();
-       data = read_object_file(oid, type, size);
-       grep_read_unlock();
-       return data;
-}
-
 static int grep_oid(struct grep_opt *opt, const struct object_id *oid,
                     const char *filename, int tree_name_len,
                     const char *path)
@@ -407,30 +397,28 @@ static int grep_submodule(struct grep_opt *opt,
 {
        struct repository subrepo;
        struct repository *superproject = opt->repo;
-       const struct submodule *sub = submodule_from_path(superproject,
-                                                         &null_oid, path);
+       const struct submodule *sub;
        struct grep_opt subopt;
        int hit;
 
-       /*
-        * NEEDSWORK: submodules functions need to be protected because they
-        * access the object store via config_from_gitmodules(): the latter
-        * uses get_oid() which, for now, relies on the global the_repository
-        * object.
-        */
-       grep_read_lock();
+       sub = submodule_from_path(superproject, &null_oid, path);
 
-       if (!is_submodule_active(superproject, path)) {
-               grep_read_unlock();
+       if (!is_submodule_active(superproject, path))
                return 0;
-       }
 
-       if (repo_submodule_init(&subrepo, superproject, sub)) {
-               grep_read_unlock();
+       if (repo_submodule_init(&subrepo, superproject, sub))
                return 0;
-       }
 
-       repo_read_gitmodules(&subrepo);
+       /*
+        * NEEDSWORK: repo_read_gitmodules() might call
+        * add_to_alternates_memory() via config_from_gitmodules(). This
+        * operation causes a race condition with concurrent object readings
+        * performed by the worker threads. That's why we need obj_read_lock()
+        * here. It should be removed once it's no longer necessary to add the
+        * subrepo's odbs to the in-memory alternates list.
+        */
+       obj_read_lock();
+       repo_read_gitmodules(&subrepo, 0);
 
        /*
         * NEEDSWORK: This adds the submodule's object directory to the list of
@@ -443,7 +431,7 @@ static int grep_submodule(struct grep_opt *opt,
         * object.
         */
        add_to_alternates_memory(subrepo.objects->odb->path);
-       grep_read_unlock();
+       obj_read_unlock();
 
        memcpy(&subopt, opt, sizeof(subopt));
        subopt.repo = &subrepo;
@@ -455,14 +443,12 @@ static int grep_submodule(struct grep_opt *opt,
                unsigned long size;
                struct strbuf base = STRBUF_INIT;
 
+               obj_read_lock();
                object = parse_object_or_die(oid, oid_to_hex(oid));
-
-               grep_read_lock();
+               obj_read_unlock();
                data = read_object_with_reference(&subrepo,
                                                  &object->oid, tree_type,
                                                  &size, NULL);
-               grep_read_unlock();
-
                if (!data)
                        die(_("unable to read tree (%s)"), oid_to_hex(&object->oid));
 
@@ -587,7 +573,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
                        void *data;
                        unsigned long size;
 
-                       data = lock_and_read_oid_file(&entry.oid, &type, &size);
+                       data = read_object_file(&entry.oid, &type, &size);
                        if (!data)
                                die(_("unable to read tree (%s)"),
                                    oid_to_hex(&entry.oid));
@@ -625,12 +611,9 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
                struct strbuf base;
                int hit, len;
 
-               grep_read_lock();
                data = read_object_with_reference(opt->repo,
                                                  &obj->oid, tree_type,
                                                  &size, NULL);
-               grep_read_unlock();
-
                if (!data)
                        die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid));
 
@@ -659,13 +642,18 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
 
        for (i = 0; i < nr; i++) {
                struct object *real_obj;
+
+               obj_read_lock();
                real_obj = deref_tag(opt->repo, list->objects[i].item,
                                     NULL, 0);
+               obj_read_unlock();
 
                /* load the gitmodules file for this rev */
                if (recurse_submodules) {
                        submodule_free(opt->repo);
+                       obj_read_lock();
                        gitmodules_config_oid(&real_obj->oid);
+                       obj_read_unlock();
                }
                if (grep_object(opt, pathspec, real_obj, list->objects[i].name,
                                list->objects[i].path)) {
@@ -958,6 +946,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                        /* die the same way as if we did it at the beginning */
                        setup_git_directory();
        }
+       /* Ignore --recurse-submodules if --no-index is given or implied */
+       if (!use_index)
+               recurse_submodules = 0;
 
        /*
         * skip a -- separator; we know it cannot be
@@ -1062,7 +1053,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        pathspec.recursive = 1;
        pathspec.recurse_submodules = !!recurse_submodules;
 
-       if (list.nr || cached || show_in_pager) {
+       if (recurse_submodules && untracked)
+               die(_("--untracked not supported with --recurse-submodules"));
+
+       if (show_in_pager) {
                if (num_threads > 1)
                        warning(_("invalid option combination, ignoring --threads"));
                num_threads = 1;
@@ -1072,7 +1066,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        } else if (num_threads < 0)
                die(_("invalid number of threads specified (%d)"), num_threads);
        else if (num_threads == 0)
-               num_threads = HAVE_THREADS ? GREP_NUM_THREADS_DEFAULT : 1;
+               num_threads = HAVE_THREADS ? online_cpus() : 1;
 
        if (num_threads > 1) {
                if (!HAVE_THREADS)
@@ -1081,6 +1075,17 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                    && (opt.pre_context || opt.post_context ||
                        opt.file_break || opt.funcbody))
                        skip_first_line = 1;
+
+               /*
+                * Pre-read gitmodules (if not read already) and force eager
+                * initialization of packed_git to prevent racy lazy
+                * reading/initialization once worker threads are started.
+                */
+               if (recurse_submodules)
+                       repo_read_gitmodules(the_repository, 1);
+               if (startup_info->have_repository)
+                       (void)get_packed_git(the_repository);
+
                start_threads(&opt);
        } else {
                /*
@@ -1115,9 +1120,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                }
        }
 
-       if (recurse_submodules && (!use_index || untracked))
-               die(_("option not supported with --recurse-submodules"));
-
        if (!show_in_pager && !opt.status_only)
                setup_pager();
 
index 062e9114412504b86f36f8bf6601b77de88ada09..d127d2225f897f111124fb55b12cd1aae7db7a8e 100644 (file)
@@ -62,6 +62,7 @@ static int show_diffstat = 1, shortlog_len = -1, squash;
 static int option_commit = -1;
 static int option_edit = -1;
 static int allow_trivial = 1, have_message, verify_signatures;
+static int check_trust_level = 1;
 static int overwrite_ignore = 1;
 static struct strbuf merge_msg = STRBUF_INIT;
 static struct strategy **use_strategies;
@@ -631,6 +632,8 @@ static int git_merge_config(const char *k, const char *v, void *cb)
        } else if (!strcmp(k, "commit.gpgsign")) {
                sign_commit = git_config_bool(k, v) ? "" : NULL;
                return 0;
+       } else if (!strcmp(k, "gpg.mintrustlevel")) {
+               check_trust_level = 0;
        }
 
        status = fmt_merge_msg_config(k, v, cb);
@@ -1397,7 +1400,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                        die(_("Can merge only exactly one commit into empty head"));
 
                if (verify_signatures)
-                       verify_merge_signature(remoteheads->item, verbosity);
+                       verify_merge_signature(remoteheads->item, verbosity,
+                                              check_trust_level);
 
                remote_head_oid = &remoteheads->item->object.oid;
                read_empty(remote_head_oid, 0);
@@ -1420,7 +1424,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 
        if (verify_signatures) {
                for (p = remoteheads; p; p = p->next) {
-                       verify_merge_signature(p->item, verbosity);
+                       verify_merge_signature(p->item, verbosity,
+                                              check_trust_level);
                }
        }
 
index ef17efd94e282a3f74b947e8a11ae18f6712ea31..940fbcb7b375b69fe8094d9d03f313a279d2df44 100644 (file)
@@ -92,10 +92,11 @@ static struct progress *progress_state;
 
 static struct packed_git *reuse_packfile;
 static uint32_t reuse_packfile_objects;
-static off_t reuse_packfile_offset;
+static struct bitmap *reuse_packfile_bitmap;
 
 static int use_bitmap_index_default = 1;
 static int use_bitmap_index = -1;
+static int allow_pack_reuse = 1;
 static enum {
        WRITE_BITMAP_FALSE = 0,
        WRITE_BITMAP_QUIET,
@@ -785,57 +786,185 @@ static struct object_entry **compute_write_order(void)
        return wo;
 }
 
-static off_t write_reused_pack(struct hashfile *f)
+
+/*
+ * A reused set of objects. All objects in a chunk have the same
+ * relative position in the original packfile and the generated
+ * packfile.
+ */
+
+static struct reused_chunk {
+       /* The offset of the first object of this chunk in the original
+        * packfile. */
+       off_t original;
+       /* The offset of the first object of this chunk in the generated
+        * packfile minus "original". */
+       off_t difference;
+} *reused_chunks;
+static int reused_chunks_nr;
+static int reused_chunks_alloc;
+
+static void record_reused_object(off_t where, off_t offset)
+{
+       if (reused_chunks_nr && reused_chunks[reused_chunks_nr-1].difference == offset)
+               return;
+
+       ALLOC_GROW(reused_chunks, reused_chunks_nr + 1,
+                  reused_chunks_alloc);
+       reused_chunks[reused_chunks_nr].original = where;
+       reused_chunks[reused_chunks_nr].difference = offset;
+       reused_chunks_nr++;
+}
+
+/*
+ * Binary search to find the chunk that "where" is in. Note
+ * that we're not looking for an exact match, just the first
+ * chunk that contains it (which implicitly ends at the start
+ * of the next chunk.
+ */
+static off_t find_reused_offset(off_t where)
+{
+       int lo = 0, hi = reused_chunks_nr;
+       while (lo < hi) {
+               int mi = lo + ((hi - lo) / 2);
+               if (where == reused_chunks[mi].original)
+                       return reused_chunks[mi].difference;
+               if (where < reused_chunks[mi].original)
+                       hi = mi;
+               else
+                       lo = mi + 1;
+       }
+
+       /*
+        * The first chunk starts at zero, so we can't have gone below
+        * there.
+        */
+       assert(lo);
+       return reused_chunks[lo-1].difference;
+}
+
+static void write_reused_pack_one(size_t pos, struct hashfile *out,
+                                 struct pack_window **w_curs)
 {
-       unsigned char buffer[8192];
-       off_t to_write, total;
-       int fd;
+       off_t offset, next, cur;
+       enum object_type type;
+       unsigned long size;
 
-       if (!is_pack_valid(reuse_packfile))
-               die(_("packfile is invalid: %s"), reuse_packfile->pack_name);
+       offset = reuse_packfile->revindex[pos].offset;
+       next = reuse_packfile->revindex[pos + 1].offset;
 
-       fd = git_open(reuse_packfile->pack_name);
-       if (fd < 0)
-               die_errno(_("unable to open packfile for reuse: %s"),
-                         reuse_packfile->pack_name);
+       record_reused_object(offset, offset - hashfile_total(out));
 
-       if (lseek(fd, sizeof(struct pack_header), SEEK_SET) == -1)
-               die_errno(_("unable to seek in reused packfile"));
+       cur = offset;
+       type = unpack_object_header(reuse_packfile, w_curs, &cur, &size);
+       assert(type >= 0);
 
-       if (reuse_packfile_offset < 0)
-               reuse_packfile_offset = reuse_packfile->pack_size - the_hash_algo->rawsz;
+       if (type == OBJ_OFS_DELTA) {
+               off_t base_offset;
+               off_t fixup;
+
+               unsigned char header[MAX_PACK_OBJECT_HEADER];
+               unsigned len;
+
+               base_offset = get_delta_base(reuse_packfile, w_curs, &cur, type, offset);
+               assert(base_offset != 0);
+
+               /* Convert to REF_DELTA if we must... */
+               if (!allow_ofs_delta) {
+                       int base_pos = find_revindex_position(reuse_packfile, base_offset);
+                       const unsigned char *base_sha1 =
+                               nth_packed_object_sha1(reuse_packfile,
+                                                      reuse_packfile->revindex[base_pos].nr);
+
+                       len = encode_in_pack_object_header(header, sizeof(header),
+                                                          OBJ_REF_DELTA, size);
+                       hashwrite(out, header, len);
+                       hashwrite(out, base_sha1, 20);
+                       copy_pack_data(out, reuse_packfile, w_curs, cur, next - cur);
+                       return;
+               }
 
-       total = to_write = reuse_packfile_offset - sizeof(struct pack_header);
+               /* Otherwise see if we need to rewrite the offset... */
+               fixup = find_reused_offset(offset) -
+                       find_reused_offset(base_offset);
+               if (fixup) {
+                       unsigned char ofs_header[10];
+                       unsigned i, ofs_len;
+                       off_t ofs = offset - base_offset - fixup;
 
-       while (to_write) {
-               int read_pack = xread(fd, buffer, sizeof(buffer));
+                       len = encode_in_pack_object_header(header, sizeof(header),
+                                                          OBJ_OFS_DELTA, size);
 
-               if (read_pack <= 0)
-                       die_errno(_("unable to read from reused packfile"));
+                       i = sizeof(ofs_header) - 1;
+                       ofs_header[i] = ofs & 127;
+                       while (ofs >>= 7)
+                               ofs_header[--i] = 128 | (--ofs & 127);
 
-               if (read_pack > to_write)
-                       read_pack = to_write;
+                       ofs_len = sizeof(ofs_header) - i;
 
-               hashwrite(f, buffer, read_pack);
-               to_write -= read_pack;
+                       hashwrite(out, header, len);
+                       hashwrite(out, ofs_header + sizeof(ofs_header) - ofs_len, ofs_len);
+                       copy_pack_data(out, reuse_packfile, w_curs, cur, next - cur);
+                       return;
+               }
+
+               /* ...otherwise we have no fixup, and can write it verbatim */
+       }
+
+       copy_pack_data(out, reuse_packfile, w_curs, offset, next - offset);
+}
+
+static size_t write_reused_pack_verbatim(struct hashfile *out,
+                                        struct pack_window **w_curs)
+{
+       size_t pos = 0;
+
+       while (pos < reuse_packfile_bitmap->word_alloc &&
+                       reuse_packfile_bitmap->words[pos] == (eword_t)~0)
+               pos++;
+
+       if (pos) {
+               off_t to_write;
+
+               written = (pos * BITS_IN_EWORD);
+               to_write = reuse_packfile->revindex[written].offset
+                       - sizeof(struct pack_header);
+
+               /* We're recording one chunk, not one object. */
+               record_reused_object(sizeof(struct pack_header), 0);
+               hashflush(out);
+               copy_pack_data(out, reuse_packfile, w_curs,
+                       sizeof(struct pack_header), to_write);
 
-               /*
-                * We don't know the actual number of objects written,
-                * only how many bytes written, how many bytes total, and
-                * how many objects total. So we can fake it by pretending all
-                * objects we are writing are the same size. This gives us a
-                * smooth progress meter, and at the end it matches the true
-                * answer.
-                */
-               written = reuse_packfile_objects *
-                               (((double)(total - to_write)) / total);
                display_progress(progress_state, written);
        }
+       return pos;
+}
+
+static void write_reused_pack(struct hashfile *f)
+{
+       size_t i = 0;
+       uint32_t offset;
+       struct pack_window *w_curs = NULL;
+
+       if (allow_ofs_delta)
+               i = write_reused_pack_verbatim(f, &w_curs);
+
+       for (; i < reuse_packfile_bitmap->word_alloc; ++i) {
+               eword_t word = reuse_packfile_bitmap->words[i];
+               size_t pos = (i * BITS_IN_EWORD);
 
-       close(fd);
-       written = reuse_packfile_objects;
-       display_progress(progress_state, written);
-       return reuse_packfile_offset - sizeof(struct pack_header);
+               for (offset = 0; offset < BITS_IN_EWORD; ++offset) {
+                       if ((word >> offset) == 0)
+                               break;
+
+                       offset += ewah_bit_ctz64(word >> offset);
+                       write_reused_pack_one(pos + offset, f, &w_curs);
+                       display_progress(progress_state, ++written);
+               }
+       }
+
+       unuse_pack(&w_curs);
 }
 
 static const char no_split_warning[] = N_(
@@ -868,11 +997,9 @@ static void write_pack_file(void)
                offset = write_pack_header(f, nr_remaining);
 
                if (reuse_packfile) {
-                       off_t packfile_size;
                        assert(pack_to_stdout);
-
-                       packfile_size = write_reused_pack(f);
-                       offset += packfile_size;
+                       write_reused_pack(f);
+                       offset = hashfile_total(f);
                }
 
                nr_written = 0;
@@ -1001,6 +1128,10 @@ static int have_duplicate_entry(const struct object_id *oid,
 {
        struct object_entry *entry;
 
+       if (reuse_packfile_bitmap &&
+           bitmap_walk_contains(bitmap_git, reuse_packfile_bitmap, oid))
+               return 1;
+
        entry = packlist_find(&to_pack, oid);
        if (!entry)
                return 0;
@@ -2553,6 +2684,13 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
        free(p);
 }
 
+static int obj_is_packed(const struct object_id *oid)
+{
+       return packlist_find(&to_pack, oid) ||
+               (reuse_packfile_bitmap &&
+                bitmap_walk_contains(bitmap_git, reuse_packfile_bitmap, oid));
+}
+
 static void add_tag_chain(const struct object_id *oid)
 {
        struct tag *tag;
@@ -2564,7 +2702,7 @@ static void add_tag_chain(const struct object_id *oid)
         * it was included via bitmaps, we would not have parsed it
         * previously).
         */
-       if (packlist_find(&to_pack, oid))
+       if (obj_is_packed(oid))
                return;
 
        tag = lookup_tag(the_repository, oid);
@@ -2588,7 +2726,7 @@ static int add_ref_tag(const char *path, const struct object_id *oid, int flag,
 
        if (starts_with(path, "refs/tags/") && /* is a tag? */
            !peel_ref(path, &peeled)    && /* peelable? */
-           packlist_find(&to_pack, &peeled))      /* object packed? */
+           obj_is_packed(&peeled)) /* object packed? */
                add_tag_chain(oid);
        return 0;
 }
@@ -2656,6 +2794,7 @@ static void prepare_pack(int window, int depth)
 
        if (nr_deltas && n > 1) {
                unsigned nr_done = 0;
+
                if (progress)
                        progress_state = start_progress(_("Compressing objects"),
                                                        nr_deltas);
@@ -2700,6 +2839,10 @@ static int git_pack_config(const char *k, const char *v, void *cb)
                use_bitmap_index_default = git_config_bool(k, v);
                return 0;
        }
+       if (!strcmp(k, "pack.allowpackreuse")) {
+               allow_pack_reuse = git_config_bool(k, v);
+               return 0;
+       }
        if (!strcmp(k, "pack.threads")) {
                delta_search_threads = git_config_int(k, v);
                if (delta_search_threads < 0)
@@ -3031,8 +3174,8 @@ static void loosen_unused_packed_objects(void)
  */
 static int pack_options_allow_reuse(void)
 {
-       return pack_to_stdout &&
-              allow_ofs_delta &&
+       return allow_pack_reuse &&
+              pack_to_stdout &&
               !ignore_packed_keep_on_disk &&
               !ignore_packed_keep_in_core &&
               (!local || !have_non_local_packs) &&
@@ -3049,7 +3192,7 @@ static int get_object_list_from_bitmap(struct rev_info *revs)
                        bitmap_git,
                        &reuse_packfile,
                        &reuse_packfile_objects,
-                       &reuse_packfile_offset)) {
+                       &reuse_packfile_bitmap)) {
                assert(reuse_packfile_objects);
                nr_result += reuse_packfile_objects;
                display_progress(progress_state, nr_result);
@@ -3510,7 +3653,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        if (progress)
                fprintf_ln(stderr,
                           _("Total %"PRIu32" (delta %"PRIu32"),"
-                            " reused %"PRIu32" (delta %"PRIu32")"),
-                          written, written_delta, reused, reused_delta);
+                            " reused %"PRIu32" (delta %"PRIu32"),"
+                            " pack-reused %"PRIu32),
+                          written, written_delta, reused, reused_delta,
+                          reuse_packfile_objects);
        return 0;
 }
index d25ff13a60f2d08efdc160c359364ab94a5b19ae..d4e3e77c8eb6daa0906f2e41ba3cacd7bc0200ba 100644 (file)
@@ -107,6 +107,7 @@ static char *opt_ff;
 static char *opt_verify_signatures;
 static int opt_autostash = -1;
 static int config_autostash;
+static int check_trust_level = 1;
 static struct argv_array opt_strategies = ARGV_ARRAY_INIT;
 static struct argv_array opt_strategy_opts = ARGV_ARRAY_INIT;
 static char *opt_gpg_sign;
@@ -355,6 +356,8 @@ static enum rebase_type config_get_rebase(void)
  */
 static int git_pull_config(const char *var, const char *value, void *cb)
 {
+       int status;
+
        if (!strcmp(var, "rebase.autostash")) {
                config_autostash = git_config_bool(var, value);
                return 0;
@@ -362,7 +365,14 @@ static int git_pull_config(const char *var, const char *value, void *cb)
                recurse_submodules = git_config_bool(var, value) ?
                        RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
                return 0;
+       } else if (!strcmp(var, "gpg.mintrustlevel")) {
+               check_trust_level = 0;
        }
+
+       status = git_gpg_config(var, value, cb);
+       if (status)
+               return status;
+
        return git_default_config(var, value, cb);
 }
 
@@ -587,7 +597,8 @@ static int pull_into_void(const struct object_id *merge_head,
                        die(_("unable to access commit %s"),
                            oid_to_hex(merge_head));
 
-               verify_merge_signature(commit, opt_verbosity);
+               verify_merge_signature(commit, opt_verbosity,
+                                      check_trust_level);
        }
 
        /*
index 8081741f8aac4e64010ae11871078917b5d535e8..6154ad8fa516717956deb63a5e4892f0c80de529 100644 (file)
@@ -246,21 +246,17 @@ static int edit_todo_file(unsigned flags)
 }
 
 static int get_revision_ranges(struct commit *upstream, struct commit *onto,
-                              const char **head_hash,
+                              struct object_id *orig_head, const char **head_hash,
                               char **revisions, char **shortrevisions)
 {
        struct commit *base_rev = upstream ? upstream : onto;
        const char *shorthead;
-       struct object_id orig_head;
-
-       if (get_oid("HEAD", &orig_head))
-               return error(_("no HEAD?"));
 
-       *head_hash = find_unique_abbrev(&orig_head, GIT_MAX_HEXSZ);
+       *head_hash = find_unique_abbrev(orig_head, GIT_MAX_HEXSZ);
        *revisions = xstrfmt("%s...%s", oid_to_hex(&base_rev->object.oid),
                                                   *head_hash);
 
-       shorthead = find_unique_abbrev(&orig_head, DEFAULT_ABBREV);
+       shorthead = find_unique_abbrev(orig_head, DEFAULT_ABBREV);
 
        if (upstream) {
                const char *shortrev;
@@ -314,12 +310,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
        struct replay_opts replay = get_replay_opts(opts);
        struct string_list commands = STRING_LIST_INIT_DUP;
 
-       if (prepare_branch_to_be_rebased(the_repository, &replay,
-                                        opts->switch_to))
-               return -1;
-
-       if (get_revision_ranges(opts->upstream, opts->onto, &head_hash,
-                               &revisions, &shortrevisions))
+       if (get_revision_ranges(opts->upstream, opts->onto, &opts->orig_head,
+                               &head_hash, &revisions, &shortrevisions))
                return -1;
 
        if (init_basic_state(&replay,
index 4d3430900d06d82e8e06441f8fdfea00f7aecf78..81dfd563c0d937eb8943f84493d2d48e8af15f1b 100644 (file)
@@ -560,15 +560,16 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
+
                if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
                        flags |= EXPIRE_REFLOGS_DRY_RUN;
-               else if (starts_with(arg, "--expire=")) {
-                       if (parse_expiry_date(arg + 9, &cb.cmd.expire_total))
+               else if (skip_prefix(arg, "--expire=", &arg)) {
+                       if (parse_expiry_date(arg, &cb.cmd.expire_total))
                                die(_("'%s' is not a valid timestamp"), arg);
                        explicit_expiry |= EXPIRE_TOTAL;
                }
-               else if (starts_with(arg, "--expire-unreachable=")) {
-                       if (parse_expiry_date(arg + 21, &cb.cmd.expire_unreachable))
+               else if (skip_prefix(arg, "--expire-unreachable=", &arg)) {
+                       if (parse_expiry_date(arg, &cb.cmd.expire_unreachable))
                                die(_("'%s' is not a valid timestamp"), arg);
                        explicit_expiry |= EXPIRE_UNREACH;
                }
index b3bed891cb15e9a97a587cf2a6dab49f309578d2..7aeb384362df0d5933239632bd56ca31ba8ef483 100644 (file)
@@ -13,6 +13,7 @@
 #include "resolve-undo.h"
 #include "unpack-trees.h"
 #include "wt-status.h"
+#include "quote.h"
 
 static const char *empty_base = "";
 
@@ -77,8 +78,10 @@ static int sparse_checkout_list(int argc, const char **argv)
 
                string_list_sort(&sl);
 
-               for (i = 0; i < sl.nr; i++)
-                       printf("%s\n", sl.items[i].string);
+               for (i = 0; i < sl.nr; i++) {
+                       quote_c_style(sl.items[i].string, NULL, stdout, 0);
+                       printf("\n");
+               }
 
                return 0;
        }
@@ -140,6 +143,22 @@ static int update_working_directory(struct pattern_list *pl)
        return result;
 }
 
+static char *escaped_pattern(char *pattern)
+{
+       char *p = pattern;
+       struct strbuf final = STRBUF_INIT;
+
+       while (*p) {
+               if (is_glob_special(*p))
+                       strbuf_addch(&final, '\\');
+
+               strbuf_addch(&final, *p);
+               p++;
+       }
+
+       return strbuf_detach(&final, NULL);
+}
+
 static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
 {
        int i;
@@ -164,10 +183,11 @@ static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
        fprintf(fp, "/*\n!/*/\n");
 
        for (i = 0; i < sl.nr; i++) {
-               char *pattern = sl.items[i].string;
+               char *pattern = escaped_pattern(sl.items[i].string);
 
                if (strlen(pattern))
                        fprintf(fp, "%s/\n!%s/*/\n", pattern, pattern);
+               free(pattern);
        }
 
        string_list_clear(&sl, 0);
@@ -185,8 +205,9 @@ static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
        string_list_remove_duplicates(&sl, 0);
 
        for (i = 0; i < sl.nr; i++) {
-               char *pattern = sl.items[i].string;
+               char *pattern = escaped_pattern(sl.items[i].string);
                fprintf(fp, "%s/\n", pattern);
+               free(pattern);
        }
 }
 
@@ -199,6 +220,10 @@ static int write_patterns_and_update(struct pattern_list *pl)
        int result;
 
        sparse_filename = get_sparse_checkout_filename();
+
+       if (safe_create_leading_directories(sparse_filename))
+               die(_("failed to create directory for sparse-checkout file"));
+
        fd = hold_lock_file_for_update(&lk, sparse_filename,
                                      LOCK_DIE_ON_ERROR);
 
@@ -419,8 +444,21 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
                pl.use_cone_patterns = 1;
 
                if (set_opts.use_stdin) {
-                       while (!strbuf_getline(&line, stdin))
+                       struct strbuf unquoted = STRBUF_INIT;
+                       while (!strbuf_getline(&line, stdin)) {
+                               if (line.buf[0] == '"') {
+                                       strbuf_reset(&unquoted);
+                                       if (unquote_c_style(&unquoted, line.buf, NULL))
+                                               die(_("unable to unquote C-style string '%s'"),
+                                               line.buf);
+
+                                       strbuf_swap(&unquoted, &line);
+                               }
+
                                strbuf_to_cone_pattern(&line, &pl);
+                       }
+
+                       strbuf_release(&unquoted);
                } else {
                        for (i = 0; i < argc; i++) {
                                strbuf_setlen(&line, 0);
index 4ad3adf4ba5a01d78f75dfc59b7ee99b25880e0c..879fc5f368346e0307e65d2a00d9fc4bad81ffa0 100644 (file)
@@ -998,9 +998,9 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,
 {
        int ret = 0;
        struct child_process cp_read_tree = CHILD_PROCESS_INIT;
-       struct child_process cp_add_i = CHILD_PROCESS_INIT;
        struct child_process cp_diff_tree = CHILD_PROCESS_INIT;
        struct index_state istate = { NULL };
+       char *old_index_env = NULL, *old_repo_index_file;
 
        remove_path(stash_index_path.buf);
 
@@ -1014,16 +1014,19 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,
        }
 
        /* Find out what the user wants. */
-       cp_add_i.git_cmd = 1;
-       argv_array_pushl(&cp_add_i.args, "add--interactive", "--patch=stash",
-                        "--", NULL);
-       add_pathspecs(&cp_add_i.args, ps);
-       argv_array_pushf(&cp_add_i.env_array, "GIT_INDEX_FILE=%s",
-                        stash_index_path.buf);
-       if (run_command(&cp_add_i)) {
-               ret = -1;
-               goto done;
-       }
+       old_repo_index_file = the_repository->index_file;
+       the_repository->index_file = stash_index_path.buf;
+       old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
+       setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
+
+       ret = run_add_interactive(NULL, "--patch=stash", ps);
+
+       the_repository->index_file = old_repo_index_file;
+       if (old_index_env && *old_index_env)
+               setenv(INDEX_ENVIRONMENT, old_index_env, 1);
+       else
+               unsetenv(INDEX_ENVIRONMENT);
+       FREE_AND_NULL(old_index_env);
 
        /* State of the working tree. */
        if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0,
diff --git a/cache.h b/cache.h
index 40ae160991f6d5717077018a06ac93e1a382cd24..37c899b53f7c3d36f1145d33f19d5c8302b911f5 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -324,7 +324,7 @@ struct index_state {
        struct hashmap dir_hash;
        struct object_id oid;
        struct untracked_cache *untracked;
-       uint64_t fsmonitor_last_update;
+       char *fsmonitor_last_update;
        struct ewah_bitmap *fsmonitor_dirty;
        struct mem_pool *ce_mem_pool;
        struct progress *progress;
index ff0ef7f08e759059a9e0053946f2f1cda3c26026..4df54c4efea8930e34f76205afa92b67e4275662 100755 (executable)
@@ -20,6 +20,7 @@ linux-gcc)
        export GIT_TEST_OE_DELTA_SIZE=5
        export GIT_TEST_COMMIT_GRAPH=1
        export GIT_TEST_MULTI_PACK_INDEX=1
+       export GIT_TEST_ADD_I_USE_BUILTIN=1
        make test
        ;;
 linux-gcc-4.8)
index b3e76ef8634820646f2e2fc4b4e9b5cc2d4b97ce..de41888430a260630650341371f9fa6216a4bcbe 100755 (executable)
@@ -7,6 +7,7 @@
 
 filter_log () {
        sed -e '/^GIT_VERSION = /d' \
+           -e "/constant Gem::ConfigMap is deprecated/d" \
            -e '/^    \* new asciidoc flags$/d' \
            -e '/stripped namespace before processing/d' \
            -e '/Attributed.*IDs for element/d' \
index 434ec030d6b2a0074c46c376c6322c92fd5c8a39..3f91d3efc5b697933289277606fbd2c053fca795 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -1136,21 +1136,23 @@ int check_commit_signature(const struct commit *commit, struct signature_check *
        return ret;
 }
 
-void verify_merge_signature(struct commit *commit, int verbosity)
+void verify_merge_signature(struct commit *commit, int verbosity,
+                           int check_trust)
 {
        char hex[GIT_MAX_HEXSZ + 1];
        struct signature_check signature_check;
+       int ret;
        memset(&signature_check, 0, sizeof(signature_check));
 
-       check_commit_signature(commit, &signature_check);
+       ret = check_commit_signature(commit, &signature_check);
 
        find_unique_abbrev_r(hex, &commit->object.oid, DEFAULT_ABBREV);
        switch (signature_check.result) {
        case 'G':
+               if (ret || (check_trust && signature_check.trust_level < TRUST_MARGINAL))
+                       die(_("Commit %s has an untrusted GPG signature, "
+                             "allegedly by %s."), hex, signature_check.signer);
                break;
-       case 'U':
-               die(_("Commit %s has an untrusted GPG signature, "
-                     "allegedly by %s."), hex, signature_check.signer);
        case 'B':
                die(_("Commit %s has a bad GPG signature "
                      "allegedly by %s."), hex, signature_check.signer);
index 221cdaa34b826bc160048567ffdf4872e1b37c47..008a0fa4a01d06c0e191e5988823ff630931d30d 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -383,8 +383,18 @@ int compare_commits_by_author_date(const void *a_, const void *b_, void *unused)
  * Verify a single commit with check_commit_signature() and die() if it is not
  * a good signature. This isn't really suitable for general use, but is a
  * helper to implement consistent logic for pull/merge --verify-signatures.
+ *
+ * The check_trust parameter is meant for backward-compatibility.  The GPG
+ * interface verifies key trust with a default trust level that is below the
+ * default trust level for merge operations.  Its value should be non-zero if
+ * the user hasn't set a minimum trust level explicitly in their configuration.
+ *
+ * If the user has set a minimum trust level, then that value should be obeyed
+ * and check_trust should be zero, even if the configured trust level is below
+ * the default trust level for merges.
  */
-void verify_merge_signature(struct commit *commit, int verbose);
+void verify_merge_signature(struct commit *commit, int verbose,
+                           int check_trust);
 
 int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused);
 int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_, void *unused);
index 01e7c818400544ae8aa69dcf544f633d99057d7f..f90a46d9b956cee22b8f2c3f3d02f33be423e5b3 100644 (file)
@@ -135,8 +135,10 @@ extern "C" {
    alignment relative to 0.  */
 
 #define __PTR_ALIGN(B, P, A)                                               \
-  __BPTR_ALIGN (sizeof (PTR_INT_TYPE) < sizeof (void *) ? (B) : (char *) 0, \
-               P, A)
+  (sizeof (PTR_INT_TYPE) < sizeof(void *) ?                                 \
+   __BPTR_ALIGN((B), (P), (A)) :                                            \
+   (void *)__BPTR_ALIGN((PTR_INT_TYPE)(void *)0, (PTR_INT_TYPE)(P), (A))            \
+  )
 
 #include <string.h>
 
index 08a26096637712689a071bf58586939bb6ff7c75..2d3412860d4d3a10cb989a769260496326055924 100644 (file)
 extern "C" {
 #endif
 
+#define regcomp git_regcomp
+#define regexec git_regexec
+#define regerror git_regerror
+#define regfree git_regfree
+
 /* The following two types have to be signed and unsigned integer type
    wide enough to hold a value of a pointer.  For most ANSI compilers
    ptrdiff_t and size_t should be likely OK.  Still size of these two
index fa13ee672db33ef2fbcc16295230b99aa9d4f595..35bca03d1470601568d17ed3cf71c5c622d43d87 100644 (file)
@@ -2,6 +2,9 @@
 #include "compat/terminal.h"
 #include "sigchain.h"
 #include "strbuf.h"
+#include "run-command.h"
+#include "string-list.h"
+#include "hashmap.h"
 
 #if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
 
@@ -32,7 +35,7 @@ static void restore_term(void)
        term_fd = -1;
 }
 
-static int disable_echo(void)
+static int disable_bits(tcflag_t bits)
 {
        struct termios t;
 
@@ -43,7 +46,7 @@ static int disable_echo(void)
        old_term = t;
        sigchain_push_common(restore_term_on_signal);
 
-       t.c_lflag &= ~ECHO;
+       t.c_lflag &= ~bits;
        if (!tcsetattr(term_fd, TCSAFLUSH, &t))
                return 0;
 
@@ -53,17 +56,44 @@ error:
        return -1;
 }
 
+static int disable_echo(void)
+{
+       return disable_bits(ECHO);
+}
+
+static int enable_non_canonical(void)
+{
+       return disable_bits(ICANON | ECHO);
+}
+
 #elif defined(GIT_WINDOWS_NATIVE)
 
 #define INPUT_PATH "CONIN$"
 #define OUTPUT_PATH "CONOUT$"
 #define FORCE_TEXT "t"
 
+static int use_stty = 1;
+static struct string_list stty_restore = STRING_LIST_INIT_DUP;
 static HANDLE hconin = INVALID_HANDLE_VALUE;
 static DWORD cmode;
 
 static void restore_term(void)
 {
+       if (use_stty) {
+               int i;
+               struct child_process cp = CHILD_PROCESS_INIT;
+
+               if (stty_restore.nr == 0)
+                       return;
+
+               argv_array_push(&cp.args, "stty");
+               for (i = 0; i < stty_restore.nr; i++)
+                       argv_array_push(&cp.args, stty_restore.items[i].string);
+               run_command(&cp);
+               string_list_clear(&stty_restore, 0);
+               return;
+       }
+
        if (hconin == INVALID_HANDLE_VALUE)
                return;
 
@@ -72,8 +102,39 @@ static void restore_term(void)
        hconin = INVALID_HANDLE_VALUE;
 }
 
-static int disable_echo(void)
+static int disable_bits(DWORD bits)
 {
+       if (use_stty) {
+               struct child_process cp = CHILD_PROCESS_INIT;
+
+               argv_array_push(&cp.args, "stty");
+
+               if (bits & ENABLE_LINE_INPUT) {
+                       string_list_append(&stty_restore, "icanon");
+                       argv_array_push(&cp.args, "-icanon");
+               }
+
+               if (bits & ENABLE_ECHO_INPUT) {
+                       string_list_append(&stty_restore, "echo");
+                       argv_array_push(&cp.args, "-echo");
+               }
+
+               if (bits & ENABLE_PROCESSED_INPUT) {
+                       string_list_append(&stty_restore, "-ignbrk");
+                       string_list_append(&stty_restore, "intr");
+                       string_list_append(&stty_restore, "^c");
+                       argv_array_push(&cp.args, "ignbrk");
+                       argv_array_push(&cp.args, "intr");
+                       argv_array_push(&cp.args, "");
+               }
+
+               if (run_command(&cp) == 0)
+                       return 0;
+
+               /* `stty` could not be executed; access the Console directly */
+               use_stty = 0;
+       }
+
        hconin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ, NULL, OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL, NULL);
@@ -82,7 +143,7 @@ static int disable_echo(void)
 
        GetConsoleMode(hconin, &cmode);
        sigchain_push_common(restore_term_on_signal);
-       if (!SetConsoleMode(hconin, cmode & (~ENABLE_ECHO_INPUT))) {
+       if (!SetConsoleMode(hconin, cmode & ~bits)) {
                CloseHandle(hconin);
                hconin = INVALID_HANDLE_VALUE;
                return -1;
@@ -91,6 +152,47 @@ static int disable_echo(void)
        return 0;
 }
 
+static int disable_echo(void)
+{
+       return disable_bits(ENABLE_ECHO_INPUT);
+}
+
+static int enable_non_canonical(void)
+{
+       return disable_bits(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
+}
+
+/*
+ * Override `getchar()`, as the default implementation does not use
+ * `ReadFile()`.
+ *
+ * This poses a problem when we want to see whether the standard
+ * input has more characters, as the default of Git for Windows is to start the
+ * Bash in a MinTTY, which uses a named pipe to emulate a pty, in which case
+ * our `poll()` emulation calls `PeekNamedPipe()`, which seems to require
+ * `ReadFile()` to be called first to work properly (it only reports 0
+ * available bytes, otherwise).
+ *
+ * So let's just override `getchar()` with a version backed by `ReadFile()` and
+ * go our merry ways from here.
+ */
+static int mingw_getchar(void)
+{
+       DWORD read = 0;
+       unsigned char ch;
+
+       if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), &ch, 1, &read, NULL))
+               return EOF;
+
+       if (!read) {
+               error("Unexpected 0 read");
+               return EOF;
+       }
+
+       return ch;
+}
+#define getchar mingw_getchar
+
 #endif
 
 #ifndef FORCE_TEXT
@@ -137,6 +239,126 @@ char *git_terminal_prompt(const char *prompt, int echo)
        return buf.buf;
 }
 
+/*
+ * The `is_known_escape_sequence()` function returns 1 if the passed string
+ * corresponds to an Escape sequence that the terminal capabilities contains.
+ *
+ * To avoid depending on ncurses or other platform-specific libraries, we rely
+ * on the presence of the `infocmp` executable to do the job for us (failing
+ * silently if the program is not available or refused to run).
+ */
+struct escape_sequence_entry {
+       struct hashmap_entry entry;
+       char sequence[FLEX_ARRAY];
+};
+
+static int sequence_entry_cmp(const void *hashmap_cmp_fn_data,
+                             const struct escape_sequence_entry *e1,
+                             const struct escape_sequence_entry *e2,
+                             const void *keydata)
+{
+       return strcmp(e1->sequence, keydata ? keydata : e2->sequence);
+}
+
+static int is_known_escape_sequence(const char *sequence)
+{
+       static struct hashmap sequences;
+       static int initialized;
+
+       if (!initialized) {
+               struct child_process cp = CHILD_PROCESS_INIT;
+               struct strbuf buf = STRBUF_INIT;
+               char *p, *eol;
+
+               hashmap_init(&sequences, (hashmap_cmp_fn)sequence_entry_cmp,
+                            NULL, 0);
+
+               argv_array_pushl(&cp.args, "infocmp", "-L", "-1", NULL);
+               if (pipe_command(&cp, NULL, 0, &buf, 0, NULL, 0))
+                       strbuf_setlen(&buf, 0);
+
+               for (eol = p = buf.buf; *p; p = eol + 1) {
+                       p = strchr(p, '=');
+                       if (!p)
+                               break;
+                       p++;
+                       eol = strchrnul(p, '\n');
+
+                       if (starts_with(p, "\\E")) {
+                               char *comma = memchr(p, ',', eol - p);
+                               struct escape_sequence_entry *e;
+
+                               p[0] = '^';
+                               p[1] = '[';
+                               FLEX_ALLOC_MEM(e, sequence, p, comma - p);
+                               hashmap_entry_init(&e->entry,
+                                                  strhash(e->sequence));
+                               hashmap_add(&sequences, &e->entry);
+                       }
+                       if (!*eol)
+                               break;
+               }
+               initialized = 1;
+       }
+
+       return !!hashmap_get_from_hash(&sequences, strhash(sequence), sequence);
+}
+
+int read_key_without_echo(struct strbuf *buf)
+{
+       static int warning_displayed;
+       int ch;
+
+       if (warning_displayed || enable_non_canonical() < 0) {
+               if (!warning_displayed) {
+                       warning("reading single keystrokes not supported on "
+                               "this platform; reading line instead");
+                       warning_displayed = 1;
+               }
+
+               return strbuf_getline(buf, stdin);
+       }
+
+       strbuf_reset(buf);
+       ch = getchar();
+       if (ch == EOF) {
+               restore_term();
+               return EOF;
+       }
+       strbuf_addch(buf, ch);
+
+       if (ch == '\033' /* ESC */) {
+               /*
+                * We are most likely looking at an Escape sequence. Let's try
+                * to read more bytes, waiting at most half a second, assuming
+                * that the sequence is complete if we did not receive any byte
+                * within that time.
+                *
+                * Start by replacing the Escape byte with ^[ */
+               strbuf_splice(buf, buf->len - 1, 1, "^[", 2);
+
+               /*
+                * Query the terminal capabilities once about all the Escape
+                * sequences it knows about, so that we can avoid waiting for
+                * half a second when we know that the sequence is complete.
+                */
+               while (!is_known_escape_sequence(buf->buf)) {
+                       struct pollfd pfd = { .fd = 0, .events = POLLIN };
+
+                       if (poll(&pfd, 1, 500) < 1)
+                               break;
+
+                       ch = getchar();
+                       if (ch == EOF)
+                               return 0;
+                       strbuf_addch(buf, ch);
+               }
+       }
+
+       restore_term();
+       return 0;
+}
+
 #else
 
 char *git_terminal_prompt(const char *prompt, int echo)
@@ -144,4 +366,23 @@ char *git_terminal_prompt(const char *prompt, int echo)
        return getpass(prompt);
 }
 
+int read_key_without_echo(struct strbuf *buf)
+{
+       static int warning_displayed;
+       const char *res;
+
+       if (!warning_displayed) {
+               warning("reading single keystrokes not supported on this "
+                       "platform; reading line instead");
+               warning_displayed = 1;
+       }
+
+       res = getpass("");
+       strbuf_reset(buf);
+       if (!res)
+               return EOF;
+       strbuf_addstr(buf, res);
+       return 0;
+}
+
 #endif
index 97db7cd69d65fc1a03ffb25e4b53ad6d296016e5..a9d52b8464e2f6e3c39bc107078cb5b2a36aa5d5 100644 (file)
@@ -3,4 +3,7 @@
 
 char *git_terminal_prompt(const char *prompt, int echo);
 
+/* Read a single keystroke, without echoing it to the terminal */
+int read_key_without_echo(struct strbuf *buf);
+
 #endif /* COMPAT_TERMINAL_H */
index ec95a3b2d035293d8058d91061d4852d9a45097d..d9f71b7cbb71676567930f327c1c0acf0b67641a 100755 (executable)
@@ -45,9 +45,9 @@ while (@ARGV) {
        } elsif ("$arg" eq "-liconv") {
                push(@args, "libiconv.lib");
        } elsif ("$arg" eq "-lcrypto") {
-               push(@args, "libeay32.lib");
+               push(@args, "libcrypto.lib");
        } elsif ("$arg" eq "-lssl") {
-               push(@args, "ssleay32.lib");
+               push(@args, "libssl.lib");
        } elsif ("$arg" eq "-lcurl") {
                my $lib = "";
                # Newer vcpkg definitions call this libcurl_imp.lib; Do we
index c337f5f7f4dd0797a8dd3c4e54fb77fd1b4f6df4..7e9bd1bc622eefa3dad76c8e786767a0ffa3077f 100644 (file)
@@ -52,19 +52,28 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
                strbuf_release(&idx_file);
        }
 
-       if (opt->check_refs_only) {
+       if (opt->check_refs_are_promisor_objects_only) {
                /*
                 * For partial clones, we don't want to have to do a regular
                 * connectivity check because we have to enumerate and exclude
                 * all promisor objects (slow), and then the connectivity check
                 * itself becomes a no-op because in a partial clone every
                 * object is a promisor object. Instead, just make sure we
-                * received the objects pointed to by each wanted ref.
+                * received, in a promisor packfile, the objects pointed to by
+                * each wanted ref.
                 */
                do {
-                       if (!repo_has_object_file_with_flags(the_repository, &oid,
-                                                            OBJECT_INFO_SKIP_FETCH_OBJECT))
-                               return 1;
+                       struct packed_git *p;
+
+                       for (p = get_all_packs(the_repository); p; p = p->next) {
+                               if (!p->pack_promisor)
+                                       continue;
+                               if (find_pack_entry_one(oid.hash, p))
+                                       goto promisor_pack_found;
+                       }
+                       return 1;
+promisor_pack_found:
+                       ;
                } while (!fn(cb_data, &oid));
                return 0;
        }
index ce2e7d8f2e535aac72fb317448ad20fb2133429b..eba5c261bac1887289ac70425cee8c97e9006ece 100644 (file)
@@ -48,12 +48,13 @@ struct check_connected_options {
        unsigned is_deepening_fetch : 1;
 
        /*
-        * If non-zero, only check the top-level objects referenced by the
-        * wanted refs (passed in as cb_data). This is useful for partial
-        * clones, where enumerating and excluding all promisor objects is very
-        * slow and the commit-walk itself becomes a no-op.
+        * If non-zero, only check that the top-level objects referenced by the
+        * wanted refs (passed in as cb_data) are promisor objects. This is
+        * useful for partial clones, where enumerating and excluding all
+        * promisor objects is very slow and the commit-walk itself becomes a
+        * no-op.
         */
-       unsigned check_refs_only : 1;
+       unsigned check_refs_are_promisor_objects_only : 1;
 };
 
 #define CHECK_CONNECTED_INIT { 0 }
index fba8a3f056a0e16591496f77a8b505ec01d01964..070978506ad533b82f72aed9bd4f4062ee88cc71 100755 (executable)
@@ -343,9 +343,9 @@ sub handleLinkLine
         } elsif ("$part" eq "-lz") {
             push(@libs, "zlib.lib");
         } elsif ("$part" eq "-lcrypto") {
-            push(@libs, "libeay32.lib");
+            push(@libs, "libcrypto.lib");
         } elsif ("$part" eq "-lssl") {
-            push(@libs, "ssleay32.lib");
+            push(@libs, "libssl.lib");
         } elsif ("$part" eq "-lcurl") {
             push(@libs, "libcurl.lib");
         } elsif ("$part" eq "-lexpat") {
index e4d9ff4a95ceb56e568131c148de5aaed85219c2..1aac5a56c0637cf09b68f2232e061fe0755703c3 100644 (file)
@@ -1069,15 +1069,32 @@ __git_aliased_command ()
        done
 }
 
-# __git_find_on_cmdline requires 1 argument
+# Check whether one of the given words is present on the command line,
+# and print the first word found.
+#
+# Usage: __git_find_on_cmdline [<option>]... "<wordlist>"
+# --show-idx: Optionally show the index of the found word in the $words array.
 __git_find_on_cmdline ()
 {
-       local word subcommand c=1
+       local word c=1 show_idx
+
+       while test $# -gt 1; do
+               case "$1" in
+               --show-idx)     show_idx=y ;;
+               *)              return 1 ;;
+               esac
+               shift
+       done
+       local wordlist="$1"
+
        while [ $c -lt $cword ]; do
-               word="${words[c]}"
-               for subcommand in $1; do
-                       if [ "$subcommand" = "$word" ]; then
-                               echo "$subcommand"
+               for word in $wordlist; do
+                       if [ "$word" = "${words[c]}" ]; then
+                               if [ -n "$show_idx" ]; then
+                                       echo "$c $word"
+                               else
+                                       echo "$word"
+                               fi
                                return
                        fi
                done
@@ -2718,6 +2735,27 @@ _git_show_branch ()
        __git_complete_revlist
 }
 
+_git_sparse_checkout ()
+{
+       local subcommands="list init set disable"
+       local subcommand="$(__git_find_on_cmdline "$subcommands")"
+       if [ -z "$subcommand" ]; then
+               __gitcomp "$subcommands"
+               return
+       fi
+
+       case "$subcommand,$cur" in
+       init,--*)
+               __gitcomp "--cone"
+               ;;
+       set,--*)
+               __gitcomp "--stdin"
+               ;;
+       *)
+               ;;
+       esac
+}
+
 _git_stash ()
 {
        local save_opts='--all --keep-index --no-keep-index --quiet --patch --include-untracked'
@@ -2969,33 +3007,83 @@ _git_whatchanged ()
        _git_log
 }
 
+__git_complete_worktree_paths ()
+{
+       local IFS=$'\n'
+       __gitcomp_nl "$(git worktree list --porcelain |
+               # Skip the first entry: it's the path of the main worktree,
+               # which can't be moved, removed, locked, etc.
+               sed -n -e '2,$ s/^worktree //p')"
+}
+
 _git_worktree ()
 {
        local subcommands="add list lock move prune remove unlock"
-       local subcommand="$(__git_find_on_cmdline "$subcommands")"
-       if [ -z "$subcommand" ]; then
+       local subcommand subcommand_idx
+
+       subcommand="$(__git_find_on_cmdline --show-idx "$subcommands")"
+       subcommand_idx="${subcommand% *}"
+       subcommand="${subcommand#* }"
+
+       case "$subcommand,$cur" in
+       ,*)
                __gitcomp "$subcommands"
-       else
-               case "$subcommand,$cur" in
-               add,--*)
-                       __gitcomp_builtin worktree_add
-                       ;;
-               list,--*)
-                       __gitcomp_builtin worktree_list
-                       ;;
-               lock,--*)
-                       __gitcomp_builtin worktree_lock
-                       ;;
-               prune,--*)
-                       __gitcomp_builtin worktree_prune
-                       ;;
-               remove,--*)
-                       __gitcomp "--force"
+               ;;
+       *,--*)
+               __gitcomp_builtin worktree_$subcommand
+               ;;
+       add,*)  # usage: git worktree add [<options>] <path> [<commit-ish>]
+               # Here we are not completing an --option, it's either the
+               # path or a ref.
+               case "$prev" in
+               -b|-B)  # Complete refs for branch to be created/reseted.
+                       __git_complete_refs
                        ;;
-               *)
+               -*)     # The previous word is an -o|--option without an
+                       # unstuck argument: have to complete the path for
+                       # the new worktree, so don't list anything, but let
+                       # Bash fall back to filename completion.
+                       ;;
+               *)      # The previous word is not an --option, so it must
+                       # be either the 'add' subcommand, the unstuck
+                       # argument of an option (e.g. branch for -b|-B), or
+                       # the path for the new worktree.
+                       if [ $cword -eq $((subcommand_idx+1)) ]; then
+                               # Right after the 'add' subcommand: have to
+                               # complete the path, so fall back to Bash
+                               # filename completion.
+                               :
+                       else
+                               case "${words[cword-2]}" in
+                               -b|-B)  # After '-b <branch>': have to
+                                       # complete the path, so fall back
+                                       # to Bash filename completion.
+                                       ;;
+                               *)      # After the path: have to complete
+                                       # the ref to be checked out.
+                                       __git_complete_refs
+                                       ;;
+                               esac
+                       fi
                        ;;
                esac
-       fi
+               ;;
+       lock,*|remove,*|unlock,*)
+               __git_complete_worktree_paths
+               ;;
+       move,*)
+               if [ $cword -eq $((subcommand_idx+1)) ]; then
+                       # The first parameter must be an existing working
+                       # tree to be moved.
+                       __git_complete_worktree_paths
+               else
+                       # The second parameter is the destination: it could
+                       # be any path, so don't list anything, but let Bash
+                       # fall back to filename completion.
+                       :
+               fi
+               ;;
+       esac
 }
 
 __git_complete_common () {
diff --git a/contrib/credential/netrc/.gitignore b/contrib/credential/netrc/.gitignore
new file mode 100644 (file)
index 0000000..d41cdde
--- /dev/null
@@ -0,0 +1 @@
+git-credential-netrc
index 6174e3bb83826273f005b01f6d9e9b2093664e33..c284fb8ac49012981d3fcde4e75f46a3e2784e32 100644 (file)
@@ -1,8 +1,30 @@
 # The default target of this Makefile is...
 all::
 
-test:
+SCRIPT_PERL = git-credential-netrc.perl
+GIT_ROOT_DIR = ../../..
+HERE = contrib/credential/netrc
+
+SCRIPT_PERL_FULL = $(patsubst %,$(HERE)/%,$(SCRIPT_PERL))
+
+all:: build
+
+build:
+       $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+                build-perl-script
+
+install: build
+       $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+                install-perl-script
+
+clean:
+       $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+                clean-perl-script
+
+test: build
        ./t-git-credential-netrc.sh
 
-testverbose:
+testverbose: build
        ./t-git-credential-netrc.sh -d -v
+
+.PHONY: all build install clean test testverbose
similarity index 99%
rename from contrib/credential/netrc/git-credential-netrc
rename to contrib/credential/netrc/git-credential-netrc.perl
index ebfc123ec641ddfa1bb2aaa83dab268c19083b2a..bc57cc65884b97dc6a0d33a067f00dac35ee19a8 100755 (executable)
@@ -423,7 +423,7 @@ sub load_config {
        # load settings from git config
        my $options = shift;
        # set from command argument, gpg.program option, or default to gpg
-       $options->{'gpg'} //= Git->repository()->config('gpg.program')
+       $options->{'gpg'} //= Git::config('gpg.program')
                          // 'gpg';
        log_verbose("using $options{'gpg'} for GPG operations");
 }
index 33507ee0a943e59b1af50576039ea8770251a2d0..5ead3ce678bb746fa82cef9e3b8e8ac407aa0741 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -1940,7 +1940,7 @@ static struct stream_filter *ident_filter(const struct object_id *oid)
  * the contents cannot be filtered without reading the whole thing
  * in-core.
  *
- * Note that you would be crazy to set CRLF, smuge/clean or ident to a
+ * Note that you would be crazy to set CRLF, smudge/clean or ident to a
  * large binary blob you would want us not to slurp into the memory!
  */
 struct stream_filter *get_stream_filter(const struct index_state *istate,
index a98b1eee53f40367ad06c7aa82da7af0ca6ed6bc..f9cbd317fbbce89547a405783fb5cc69e7815efa 100644 (file)
@@ -42,6 +42,15 @@ void hashflush(struct hashfile *f);
 void crc32_begin(struct hashfile *);
 uint32_t crc32_end(struct hashfile *);
 
+/*
+ * Returns the total number of bytes fed to the hashfile so far (including ones
+ * that have not been written out to the descriptor yet).
+ */
+static inline off_t hashfile_total(struct hashfile *f)
+{
+       return f->total + f->offset;
+}
+
 static inline void hashwrite_u8(struct hashfile *f, uint8_t data)
 {
        hashwrite(f, &data, sizeof(data));
diff --git a/diff.c b/diff.c
index 0f333c27d6f4740f1469b1cf369421896c41b7cc..f2cfbf2214a29fc9ddf0e356230df7625d3d1139 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -414,14 +414,6 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
-       if (!strcmp(var, "diff.wserrorhighlight")) {
-               int val = parse_ws_error_highlight(value);
-               if (val < 0)
-                       return -1;
-               ws_error_highlight_default = val;
-               return 0;
-       }
-
        if (git_color_config(var, value, cb) < 0)
                return -1;
 
@@ -450,6 +442,14 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
                return color_parse(value, diff_colors[slot]);
        }
 
+       if (!strcmp(var, "diff.wserrorhighlight")) {
+               int val = parse_ws_error_highlight(value);
+               if (val < 0)
+                       return -1;
+               ws_error_highlight_default = val;
+               return 0;
+       }
+
        /* like GNU diff's --suppress-blank-empty option  */
        if (!strcmp(var, "diff.suppressblankempty") ||
                        /* for backwards compatibility */
diff --git a/dir.c b/dir.c
index 512c5d7ea6d45e380a3e9f94d22af8820302a7b8..0dc0f113c0595c090f0864786b05aaf857005a73 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -41,7 +41,8 @@ struct cached_dir {
        int nr_files;
        int nr_dirs;
 
-       struct dirent *de;
+       const char *d_name;
+       int d_type;
        const char *file;
        struct untracked_cache_dir *ucd;
 };
@@ -50,8 +51,8 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
        struct index_state *istate, const char *path, int len,
        struct untracked_cache_dir *untracked,
        int check_only, int stop_at_first_file, const struct pathspec *pathspec);
-static int get_dtype(struct dirent *de, struct index_state *istate,
-                    const char *path, int len);
+static int resolve_dtype(int dtype, struct index_state *istate,
+                        const char *path, int len);
 
 int count_slashes(const char *s)
 {
@@ -635,11 +636,42 @@ int pl_hashmap_cmp(const void *unused_cmp_data,
        return strncmp(ee1->pattern, ee2->pattern, min_len);
 }
 
+static char *dup_and_filter_pattern(const char *pattern)
+{
+       char *set, *read;
+       size_t count  = 0;
+       char *result = xstrdup(pattern);
+
+       set = result;
+       read = result;
+
+       while (*read) {
+               /* skip escape characters (once) */
+               if (*read == '\\')
+                       read++;
+
+               *set = *read;
+
+               set++;
+               read++;
+               count++;
+       }
+       *set = 0;
+
+       if (count > 2 &&
+           *(set - 1) == '*' &&
+           *(set - 2) == '/')
+               *(set - 2) = 0;
+
+       return result;
+}
+
 static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern *given)
 {
        struct pattern_entry *translated;
        char *truncated;
        char *data = NULL;
+       const char *prev, *cur, *next;
 
        if (!pl->use_cone_patterns)
                return;
@@ -656,17 +688,57 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern
                return;
        }
 
+       if (given->patternlen <= 2 ||
+           *given->pattern == '*' ||
+           strstr(given->pattern, "**")) {
+               /* Not a cone pattern. */
+               warning(_("unrecognized pattern: '%s'"), given->pattern);
+               goto clear_hashmaps;
+       }
+
+       prev = given->pattern;
+       cur = given->pattern + 1;
+       next = given->pattern + 2;
+
+       while (*cur) {
+               /* Watch for glob characters '*', '\', '[', '?' */
+               if (!is_glob_special(*cur))
+                       goto increment;
+
+               /* But only if *prev != '\\' */
+               if (*prev == '\\')
+                       goto increment;
+
+               /* But allow the initial '\' */
+               if (*cur == '\\' &&
+                   is_glob_special(*next))
+                       goto increment;
+
+               /* But a trailing '/' then '*' is fine */
+               if (*prev == '/' &&
+                   *cur == '*' &&
+                   *next == 0)
+                       goto increment;
+
+               /* Not a cone pattern. */
+               warning(_("unrecognized pattern: '%s'"), given->pattern);
+               goto clear_hashmaps;
+
+       increment:
+               prev++;
+               cur++;
+               next++;
+       }
+
        if (given->patternlen > 2 &&
            !strcmp(given->pattern + given->patternlen - 2, "/*")) {
                if (!(given->flags & PATTERN_FLAG_NEGATIVE)) {
                        /* Not a cone pattern. */
-                       pl->use_cone_patterns = 0;
                        warning(_("unrecognized pattern: '%s'"), given->pattern);
                        goto clear_hashmaps;
                }
 
-               truncated = xstrdup(given->pattern);
-               truncated[given->patternlen - 2] = 0;
+               truncated = dup_and_filter_pattern(given->pattern);
 
                translated = xmalloc(sizeof(struct pattern_entry));
                translated->pattern = truncated;
@@ -700,7 +772,7 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern
 
        translated = xmalloc(sizeof(struct pattern_entry));
 
-       translated->pattern = xstrdup(given->pattern);
+       translated->pattern = dup_and_filter_pattern(given->pattern);
        translated->patternlen = given->patternlen;
        hashmap_entry_init(&translated->ent,
                           ignore_case ?
@@ -1215,8 +1287,7 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname
                int prefix = pattern->nowildcardlen;
 
                if (pattern->flags & PATTERN_FLAG_MUSTBEDIR) {
-                       if (*dtype == DT_UNKNOWN)
-                               *dtype = get_dtype(NULL, istate, pathname, pathlen);
+                       *dtype = resolve_dtype(*dtype, istate, pathname, pathlen);
                        if (*dtype != DT_DIR)
                                continue;
                }
@@ -1842,10 +1913,9 @@ static int get_index_dtype(struct index_state *istate,
        return DT_UNKNOWN;
 }
 
-static int get_dtype(struct dirent *de, struct index_state *istate,
-                    const char *path, int len)
+static int resolve_dtype(int dtype, struct index_state *istate,
+                        const char *path, int len)
 {
-       int dtype = de ? DTYPE(de) : DT_UNKNOWN;
        struct stat st;
 
        if (dtype != DT_UNKNOWN)
@@ -1870,14 +1940,13 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
                                          struct strbuf *path,
                                          int baselen,
                                          const struct pathspec *pathspec,
-                                         int dtype, struct dirent *de)
+                                         int dtype)
 {
        int exclude;
        int has_path_in_index = !!index_file_exists(istate, path->buf, path->len, ignore_case);
        enum path_treatment path_treatment;
 
-       if (dtype == DT_UNKNOWN)
-               dtype = get_dtype(de, istate, path->buf, path->len);
+       dtype = resolve_dtype(dtype, istate, path->buf, path->len);
 
        /* Always exclude indexed files */
        if (dtype != DT_DIR && has_path_in_index)
@@ -1985,21 +2054,18 @@ static enum path_treatment treat_path(struct dir_struct *dir,
                                      int baselen,
                                      const struct pathspec *pathspec)
 {
-       int dtype;
-       struct dirent *de = cdir->de;
-
-       if (!de)
+       if (!cdir->d_name)
                return treat_path_fast(dir, untracked, cdir, istate, path,
                                       baselen, pathspec);
-       if (is_dot_or_dotdot(de->d_name) || !fspathcmp(de->d_name, ".git"))
+       if (is_dot_or_dotdot(cdir->d_name) || !fspathcmp(cdir->d_name, ".git"))
                return path_none;
        strbuf_setlen(path, baselen);
-       strbuf_addstr(path, de->d_name);
+       strbuf_addstr(path, cdir->d_name);
        if (simplify_away(path->buf, path->len, pathspec))
                return path_none;
 
-       dtype = DTYPE(de);
-       return treat_one_path(dir, untracked, istate, path, baselen, pathspec, dtype, de);
+       return treat_one_path(dir, untracked, istate, path, baselen, pathspec,
+                             cdir->d_type);
 }
 
 static void add_untracked(struct untracked_cache_dir *dir, const char *name)
@@ -2087,10 +2153,17 @@ static int open_cached_dir(struct cached_dir *cdir,
 
 static int read_cached_dir(struct cached_dir *cdir)
 {
+       struct dirent *de;
+
        if (cdir->fdir) {
-               cdir->de = readdir(cdir->fdir);
-               if (!cdir->de)
+               de = readdir(cdir->fdir);
+               if (!de) {
+                       cdir->d_name = NULL;
+                       cdir->d_type = DT_UNKNOWN;
                        return -1;
+               }
+               cdir->d_name = de->d_name;
+               cdir->d_type = DTYPE(de);
                return 0;
        }
        while (cdir->nr_dirs < cdir->untracked->dirs_nr) {
@@ -2216,7 +2289,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
                /* recurse into subdir if instructed by treat_path */
                if ((state == path_recurse) ||
                        ((state == path_untracked) &&
-                        (get_dtype(cdir.de, istate, path.buf, path.len) == DT_DIR) &&
+                        (resolve_dtype(cdir.d_type, istate, path.buf, path.len) == DT_DIR) &&
                         ((dir->flags & DIR_SHOW_IGNORED_TOO) ||
                          (pathspec &&
                           do_match_pathspec(istate, pathspec, path.buf, path.len,
@@ -2308,16 +2381,16 @@ static int treat_leading_path(struct dir_struct *dir,
         * WARNING WARNING WARNING:
         *
         * Any updates to the traversal logic here may need corresponding
-        * updates in treat_leading_path().  See the commit message for the
-        * commit adding this warning as well as the commit preceding it
-        * for details.
+        * updates in read_directory_recursive().  See 777b420347 (dir:
+        * synchronize treat_leading_path() and read_directory_recursive(),
+        * 2019-12-19) and its parent commit for details.
         */
 
        struct strbuf sb = STRBUF_INIT;
+       struct strbuf subdir = STRBUF_INIT;
        int prevlen, baselen;
        const char *cp;
        struct cached_dir cdir;
-       struct dirent *de;
        enum path_treatment state = path_none;
 
        /*
@@ -2342,22 +2415,8 @@ static int treat_leading_path(struct dir_struct *dir,
        if (!len)
                return 1;
 
-       /*
-        * We need a manufactured dirent with sufficient space to store a
-        * leading directory component of path in its d_name.  Here, we
-        * assume that the dirent's d_name is either declared as
-        *    char d_name[BIG_ENOUGH]
-        * or that it is declared at the end of the struct as
-        *    char d_name[]
-        * For either case, padding with len+1 bytes at the end will ensure
-        * sufficient storage space.
-        */
-       de = xcalloc(1, st_add3(sizeof(struct dirent), len, 1));
        memset(&cdir, 0, sizeof(cdir));
-       cdir.de = de;
-#if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
-       de->d_type = DT_DIR;
-#endif
+       cdir.d_type = DT_DIR;
        baselen = 0;
        prevlen = 0;
        while (1) {
@@ -2374,15 +2433,20 @@ static int treat_leading_path(struct dir_struct *dir,
                        break;
                strbuf_reset(&sb);
                strbuf_add(&sb, path, prevlen);
-               memcpy(de->d_name, path+prevlen, baselen-prevlen);
-               de->d_name[baselen-prevlen] = '\0';
+               strbuf_reset(&subdir);
+               strbuf_add(&subdir, path+prevlen, baselen-prevlen);
+               cdir.d_name = subdir.buf;
                state = treat_path(dir, NULL, &cdir, istate, &sb, prevlen,
                                    pathspec);
                if (state == path_untracked &&
-                   get_dtype(cdir.de, istate, sb.buf, sb.len) == DT_DIR &&
+                   resolve_dtype(cdir.d_type, istate, sb.buf, sb.len) == DT_DIR &&
                    (dir->flags & DIR_SHOW_IGNORED_TOO ||
                     do_match_pathspec(istate, pathspec, sb.buf, sb.len,
                                       baselen, NULL, DO_MATCH_LEADING_PATHSPEC) == MATCHED_RECURSIVELY_LEADING_PATHSPEC)) {
+                       if (!match_pathspec(istate, pathspec, sb.buf, sb.len,
+                                           0 /* prefix */, NULL,
+                                           0 /* do NOT special case dirs */))
+                               state = path_none;
                        add_path_to_appropriate_result_list(dir, NULL, &cdir,
                                                            istate,
                                                            &sb, baselen,
@@ -2399,7 +2463,7 @@ static int treat_leading_path(struct dir_struct *dir,
                                            &sb, baselen, pathspec,
                                            state);
 
-       free(de);
+       strbuf_release(&subdir);
        strbuf_release(&sb);
        return state == path_recurse;
 }
index 52f1178db4ce35103545a3e1ee00a3000b08eb90..b5fed9621f425b08af27c56be0e3f88a331e314b 100644 (file)
 #define EWAH_MASK(x) ((eword_t)1 << (x % BITS_IN_EWORD))
 #define EWAH_BLOCK(x) (x / BITS_IN_EWORD)
 
-struct bitmap *bitmap_new(void)
+struct bitmap *bitmap_word_alloc(size_t word_alloc)
 {
        struct bitmap *bitmap = xmalloc(sizeof(struct bitmap));
-       bitmap->words = xcalloc(32, sizeof(eword_t));
-       bitmap->word_alloc = 32;
+       bitmap->words = xcalloc(word_alloc, sizeof(eword_t));
+       bitmap->word_alloc = word_alloc;
        return bitmap;
 }
 
+struct bitmap *bitmap_new(void)
+{
+       return bitmap_word_alloc(32);
+}
+
 void bitmap_set(struct bitmap *self, size_t pos)
 {
        size_t block = EWAH_BLOCK(pos);
 
        if (block >= self->word_alloc) {
                size_t old_size = self->word_alloc;
-               self->word_alloc = block * 2;
+               self->word_alloc = block ? block * 2 : 1;
                REALLOC_ARRAY(self->words, self->word_alloc);
                memset(self->words + old_size, 0x0,
                        (self->word_alloc - old_size) * sizeof(eword_t));
index 84b2a29faa0c57410168ad5afb2dbaa7a49ddd60..1b98b57c8b7b623f4e76024b488081d09bf9bad6 100644 (file)
@@ -172,6 +172,7 @@ struct bitmap {
 };
 
 struct bitmap *bitmap_new(void);
+struct bitmap *bitmap_word_alloc(size_t word_alloc);
 void bitmap_set(struct bitmap *self, size_t pos);
 int bitmap_get(struct bitmap *self, size_t pos);
 void bitmap_reset(struct bitmap *self);
index 868cca01e250896054b1f7025c7180c87ab1b88d..932bd9012daf9eb91dd445374f52a78ceade918b 100644 (file)
@@ -6,8 +6,10 @@
 #include "run-command.h"
 #include "strbuf.h"
 
-#define INDEX_EXTENSION_VERSION        (1)
-#define HOOK_INTERFACE_VERSION (1)
+#define INDEX_EXTENSION_VERSION1       (1)
+#define INDEX_EXTENSION_VERSION2       (2)
+#define HOOK_INTERFACE_VERSION1                (1)
+#define HOOK_INTERFACE_VERSION2                (2)
 
 struct trace_key trace_fsmonitor = TRACE_KEY_INIT(FSMONITOR);
 
@@ -24,6 +26,22 @@ static void fsmonitor_ewah_callback(size_t pos, void *is)
        ce->ce_flags &= ~CE_FSMONITOR_VALID;
 }
 
+static int fsmonitor_hook_version(void)
+{
+       int hook_version;
+
+       if (git_config_get_int("core.fsmonitorhookversion", &hook_version))
+               return -1;
+
+       if (hook_version == HOOK_INTERFACE_VERSION1 ||
+           hook_version == HOOK_INTERFACE_VERSION2)
+               return hook_version;
+
+       warning("Invalid hook version '%i' in core.fsmonitorhookversion. "
+               "Must be 1 or 2.", hook_version);
+       return -1;
+}
+
 int read_fsmonitor_extension(struct index_state *istate, const void *data,
        unsigned long sz)
 {
@@ -32,17 +50,26 @@ int read_fsmonitor_extension(struct index_state *istate, const void *data,
        uint32_t ewah_size;
        struct ewah_bitmap *fsmonitor_dirty;
        int ret;
+       uint64_t timestamp;
+       struct strbuf last_update = STRBUF_INIT;
 
-       if (sz < sizeof(uint32_t) + sizeof(uint64_t) + sizeof(uint32_t))
+       if (sz < sizeof(uint32_t) + 1 + sizeof(uint32_t))
                return error("corrupt fsmonitor extension (too short)");
 
        hdr_version = get_be32(index);
        index += sizeof(uint32_t);
-       if (hdr_version != INDEX_EXTENSION_VERSION)
+       if (hdr_version == INDEX_EXTENSION_VERSION1) {
+               timestamp = get_be64(index);
+               strbuf_addf(&last_update, "%"PRIu64"", timestamp);
+               index += sizeof(uint64_t);
+       } else if (hdr_version == INDEX_EXTENSION_VERSION2) {
+               strbuf_addstr(&last_update, index);
+               index += last_update.len + 1;
+       } else {
                return error("bad fsmonitor version %d", hdr_version);
+       }
 
-       istate->fsmonitor_last_update = get_be64(index);
-       index += sizeof(uint64_t);
+       istate->fsmonitor_last_update = strbuf_detach(&last_update, NULL);
 
        ewah_size = get_be32(index);
        index += sizeof(uint32_t);
@@ -79,7 +106,6 @@ void fill_fsmonitor_bitmap(struct index_state *istate)
 void write_fsmonitor_extension(struct strbuf *sb, struct index_state *istate)
 {
        uint32_t hdr_version;
-       uint64_t tm;
        uint32_t ewah_start;
        uint32_t ewah_size = 0;
        int fixup = 0;
@@ -89,11 +115,12 @@ void write_fsmonitor_extension(struct strbuf *sb, struct index_state *istate)
                BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",
                    (uintmax_t)istate->fsmonitor_dirty->bit_size, istate->cache_nr);
 
-       put_be32(&hdr_version, INDEX_EXTENSION_VERSION);
+       put_be32(&hdr_version, INDEX_EXTENSION_VERSION2);
        strbuf_add(sb, &hdr_version, sizeof(uint32_t));
 
-       put_be64(&tm, istate->fsmonitor_last_update);
-       strbuf_add(sb, &tm, sizeof(uint64_t));
+       strbuf_addstr(sb, istate->fsmonitor_last_update);
+       strbuf_addch(sb, 0); /* Want to keep a NUL */
+
        fixup = sb->len;
        strbuf_add(sb, &ewah_size, sizeof(uint32_t)); /* we'll fix this up later */
 
@@ -110,9 +137,9 @@ void write_fsmonitor_extension(struct strbuf *sb, struct index_state *istate)
 }
 
 /*
- * Call the query-fsmonitor hook passing the time of the last saved results.
+ * Call the query-fsmonitor hook passing the last update token of the saved results.
  */
-static int query_fsmonitor(int version, uint64_t last_update, struct strbuf *query_result)
+static int query_fsmonitor(int version, const char *last_update, struct strbuf *query_result)
 {
        struct child_process cp = CHILD_PROCESS_INIT;
 
@@ -121,7 +148,7 @@ static int query_fsmonitor(int version, uint64_t last_update, struct strbuf *que
 
        argv_array_push(&cp.args, core_fsmonitor);
        argv_array_pushf(&cp.args, "%d", version);
-       argv_array_pushf(&cp.args, "%" PRIuMAX, (uintmax_t)last_update);
+       argv_array_pushf(&cp.args, "%s", last_update);
        cp.use_shell = 1;
        cp.dir = get_git_work_tree();
 
@@ -148,14 +175,18 @@ static void fsmonitor_refresh_callback(struct index_state *istate, const char *n
 void refresh_fsmonitor(struct index_state *istate)
 {
        struct strbuf query_result = STRBUF_INIT;
-       int query_success = 0;
-       size_t bol; /* beginning of line */
+       int query_success = 0, hook_version = -1;
+       size_t bol = 0; /* beginning of line */
        uint64_t last_update;
+       struct strbuf last_update_token = STRBUF_INIT;
        char *buf;
        unsigned int i;
 
        if (!core_fsmonitor || istate->fsmonitor_has_run_once)
                return;
+
+       hook_version = fsmonitor_hook_version();
+
        istate->fsmonitor_has_run_once = 1;
 
        trace_printf_key(&trace_fsmonitor, "refresh fsmonitor");
@@ -164,26 +195,60 @@ void refresh_fsmonitor(struct index_state *istate)
         * should be inclusive to ensure we don't miss potential changes.
         */
        last_update = getnanotime();
+       if (hook_version == HOOK_INTERFACE_VERSION1)
+               strbuf_addf(&last_update_token, "%"PRIu64"", last_update);
 
        /*
-        * If we have a last update time, call query_fsmonitor for the set of
-        * changes since that time, else assume everything is possibly dirty
+        * If we have a last update token, call query_fsmonitor for the set of
+        * changes since that token, else assume everything is possibly dirty
         * and check it all.
         */
        if (istate->fsmonitor_last_update) {
-               query_success = !query_fsmonitor(HOOK_INTERFACE_VERSION,
-                       istate->fsmonitor_last_update, &query_result);
+               if (hook_version == -1 || hook_version == HOOK_INTERFACE_VERSION2) {
+                       query_success = !query_fsmonitor(HOOK_INTERFACE_VERSION2,
+                               istate->fsmonitor_last_update, &query_result);
+
+                       if (query_success) {
+                               if (hook_version < 0)
+                                       hook_version = HOOK_INTERFACE_VERSION2;
+
+                               /*
+                                * First entry will be the last update token
+                                * Need to use a char * variable because static
+                                * analysis was suggesting to use strbuf_addbuf
+                                * but we don't want to copy the entire strbuf
+                                * only the the chars up to the first NUL
+                                */
+                               buf = query_result.buf;
+                               strbuf_addstr(&last_update_token, buf);
+                               if (!last_update_token.len) {
+                                       warning("Empty last update token.");
+                                       query_success = 0;
+                               } else {
+                                       bol = last_update_token.len + 1;
+                               }
+                       } else if (hook_version < 0) {
+                               hook_version = HOOK_INTERFACE_VERSION1;
+                               if (!last_update_token.len)
+                                       strbuf_addf(&last_update_token, "%"PRIu64"", last_update);
+                       }
+               }
+
+               if (hook_version == HOOK_INTERFACE_VERSION1) {
+                       query_success = !query_fsmonitor(HOOK_INTERFACE_VERSION1,
+                               istate->fsmonitor_last_update, &query_result);
+               }
+
                trace_performance_since(last_update, "fsmonitor process '%s'", core_fsmonitor);
                trace_printf_key(&trace_fsmonitor, "fsmonitor process '%s' returned %s",
                        core_fsmonitor, query_success ? "success" : "failure");
        }
 
        /* a fsmonitor process can return '/' to indicate all entries are invalid */
-       if (query_success && query_result.buf[0] != '/') {
+       if (query_success && query_result.buf[bol] != '/') {
                /* Mark all entries returned by the monitor as dirty */
                buf = query_result.buf;
-               bol = 0;
-               for (i = 0; i < query_result.len; i++) {
+               for (i = bol; i < query_result.len; i++) {
                        if (buf[i] != '\0')
                                continue;
                        fsmonitor_refresh_callback(istate, buf + bol);
@@ -217,18 +282,21 @@ void refresh_fsmonitor(struct index_state *istate)
        }
        strbuf_release(&query_result);
 
-       /* Now that we've updated istate, save the last_update time */
-       istate->fsmonitor_last_update = last_update;
+       /* Now that we've updated istate, save the last_update_token */
+       FREE_AND_NULL(istate->fsmonitor_last_update);
+       istate->fsmonitor_last_update = strbuf_detach(&last_update_token, NULL);
 }
 
 void add_fsmonitor(struct index_state *istate)
 {
        unsigned int i;
+       struct strbuf last_update = STRBUF_INIT;
 
        if (!istate->fsmonitor_last_update) {
                trace_printf_key(&trace_fsmonitor, "add fsmonitor");
                istate->cache_changed |= FSMONITOR_CHANGED;
-               istate->fsmonitor_last_update = getnanotime();
+               strbuf_addf(&last_update, "%"PRIu64"", getnanotime());
+               istate->fsmonitor_last_update = strbuf_detach(&last_update, NULL);
 
                /* reset the fsmonitor state */
                for (i = 0; i < istate->cache_nr; i++)
@@ -250,7 +318,7 @@ void remove_fsmonitor(struct index_state *istate)
        if (istate->fsmonitor_last_update) {
                trace_printf_key(&trace_fsmonitor, "remove fsmonitor");
                istate->cache_changed |= FSMONITOR_CHANGED;
-               istate->fsmonitor_last_update = 0;
+               FREE_AND_NULL(istate->fsmonitor_last_update);
        }
 }
 
index 53fa5743018f19d8428f4a24379907bcbb6d80bf..4d4ebb4f2ba54e3061cc8ade350a9fb2d9421690 100755 (executable)
@@ -207,7 +207,7 @@ create_stash () {
 
                # find out what the user wants
                GIT_INDEX_FILE="$TMP-index" \
-                       git add--interactive --patch=stash -- "$@" &&
+                       git add --legacy-stash-p -- "$@" &&
 
                # state of the working tree
                w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
index 40d9e7c594e590bd97069e29096dc34f3e642ed9..9a71a6690db35d674456c677be1212e44e57a582 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -7,6 +7,14 @@
 #            2007 Trolltech ASA
 # License: MIT <http://www.opensource.org/licenses/mit-license.php>
 #
+# pylint: disable=invalid-name,missing-docstring,too-many-arguments,broad-except
+# pylint: disable=no-self-use,wrong-import-position,consider-iterating-dictionary
+# pylint: disable=wrong-import-order,unused-import,too-few-public-methods
+# pylint: disable=too-many-lines,ungrouped-imports,fixme,too-many-locals
+# pylint: disable=line-too-long,bad-whitespace,superfluous-parens
+# pylint: disable=too-many-statements,too-many-instance-attributes
+# pylint: disable=too-many-branches,too-many-nested-blocks
+#
 import sys
 if sys.hexversion < 0x02040000:
     # The limiter is the subprocess module
@@ -161,6 +169,9 @@ def calcDiskFree():
         return st.f_bavail * st.f_frsize
 
 def die(msg):
+    """ Terminate execution. Make sure that any running child processes have been wait()ed for before
+        calling this.
+    """
     if verbose:
         raise Exception(msg)
     else:
@@ -618,6 +629,14 @@ class P4RequestSizeException(P4ServerException):
         super(P4RequestSizeException, self).__init__(exit_code, p4_result)
         self.limit = limit
 
+class P4CommandException(P4Exception):
+    """ Something went wrong calling p4 which means we have to give up """
+    def __init__(self, msg):
+        self.msg = msg
+
+    def __str__(self):
+        return self.msg
+
 def isModeExecChanged(src_mode, dst_mode):
     return isModeExec(src_mode) != isModeExec(dst_mode)
 
@@ -3539,6 +3558,74 @@ class P4Sync(Command, P4UserMap):
             print("IO error details: {}".format(err))
             print(self.gitError.read())
 
+
+    def importRevisions(self, args, branch_arg_given):
+        changes = []
+
+        if len(self.changesFile) > 0:
+            with open(self.changesFile) as f:
+                output = f.readlines()
+            changeSet = set()
+            for line in output:
+                changeSet.add(int(line))
+
+            for change in changeSet:
+                changes.append(change)
+
+            changes.sort()
+        else:
+            # catch "git p4 sync" with no new branches, in a repo that
+            # does not have any existing p4 branches
+            if len(args) == 0:
+                if not self.p4BranchesInGit:
+                    raise P4CommandException("No remote p4 branches.  Perhaps you never did \"git p4 clone\" in here.")
+
+                # The default branch is master, unless --branch is used to
+                # specify something else.  Make sure it exists, or complain
+                # nicely about how to use --branch.
+                if not self.detectBranches:
+                    if not branch_exists(self.branch):
+                        if branch_arg_given:
+                            raise P4CommandException("Error: branch %s does not exist." % self.branch)
+                        else:
+                            raise P4CommandException("Error: no branch %s; perhaps specify one with --branch." %
+                                self.branch)
+
+            if self.verbose:
+                print("Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
+                                                          self.changeRange))
+            changes = p4ChangesForPaths(self.depotPaths, self.changeRange, self.changes_block_size)
+
+            if len(self.maxChanges) > 0:
+                changes = changes[:min(int(self.maxChanges), len(changes))]
+
+        if len(changes) == 0:
+            if not self.silent:
+                print("No changes to import!")
+        else:
+            if not self.silent and not self.detectBranches:
+                print("Import destination: %s" % self.branch)
+
+            self.updatedBranches = set()
+
+            if not self.detectBranches:
+                if args:
+                    # start a new branch
+                    self.initialParent = ""
+                else:
+                    # build on a previous revision
+                    self.initialParent = parseRevision(self.branch)
+
+            self.importChanges(changes)
+
+            if not self.silent:
+                print("")
+                if len(self.updatedBranches) > 0:
+                    sys.stdout.write("Updated branches: ")
+                    for b in self.updatedBranches:
+                        sys.stdout.write("%s " % b)
+                    sys.stdout.write("\n")
+
     def openStreams(self):
         self.importProcess = subprocess.Popen(["git", "fast-import"],
                                               stdin=subprocess.PIPE,
@@ -3549,11 +3636,14 @@ class P4Sync(Command, P4UserMap):
         self.gitError = self.importProcess.stderr
 
     def closeStreams(self):
+        if self.gitStream is None:
+            return
         self.gitStream.close()
         if self.importProcess.wait() != 0:
             die("fast-import failed: %s" % self.gitError.read())
         self.gitOutput.close()
         self.gitError.close()
+        self.gitStream = None
 
     def run(self, args):
         if self.importIntoRemotes:
@@ -3737,87 +3827,36 @@ class P4Sync(Command, P4UserMap):
                     b = b[len(self.projectName):]
                 self.createdBranches.add(b)
 
-        self.openStreams()
-
-        if revision:
-            self.importHeadRevision(revision)
-        else:
-            changes = []
-
-            if len(self.changesFile) > 0:
-                output = open(self.changesFile).readlines()
-                changeSet = set()
-                for line in output:
-                    changeSet.add(int(line))
-
-                for change in changeSet:
-                    changes.append(change)
-
-                changes.sort()
-            else:
-                # catch "git p4 sync" with no new branches, in a repo that
-                # does not have any existing p4 branches
-                if len(args) == 0:
-                    if not self.p4BranchesInGit:
-                        die("No remote p4 branches.  Perhaps you never did \"git p4 clone\" in here.")
-
-                    # The default branch is master, unless --branch is used to
-                    # specify something else.  Make sure it exists, or complain
-                    # nicely about how to use --branch.
-                    if not self.detectBranches:
-                        if not branch_exists(self.branch):
-                            if branch_arg_given:
-                                die("Error: branch %s does not exist." % self.branch)
-                            else:
-                                die("Error: no branch %s; perhaps specify one with --branch." %
-                                    self.branch)
+        p4_check_access()
 
-                if self.verbose:
-                    print("Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
-                                                              self.changeRange))
-                changes = p4ChangesForPaths(self.depotPaths, self.changeRange, self.changes_block_size)
+        self.openStreams()
 
-                if len(self.maxChanges) > 0:
-                    changes = changes[:min(int(self.maxChanges), len(changes))]
+        err = None
 
-            if len(changes) == 0:
-                if not self.silent:
-                    print("No changes to import!")
+        try:
+            if revision:
+                self.importHeadRevision(revision)
             else:
-                if not self.silent and not self.detectBranches:
-                    print("Import destination: %s" % self.branch)
+                self.importRevisions(args, branch_arg_given)
 
-                self.updatedBranches = set()
+            if gitConfigBool("git-p4.importLabels"):
+                self.importLabels = True
 
-                if not self.detectBranches:
-                    if args:
-                        # start a new branch
-                        self.initialParent = ""
-                    else:
-                        # build on a previous revision
-                        self.initialParent = parseRevision(self.branch)
+            if self.importLabels:
+                p4Labels = getP4Labels(self.depotPaths)
+                gitTags = getGitTags()
 
-                self.importChanges(changes)
+                missingP4Labels = p4Labels - gitTags
+                self.importP4Labels(self.gitStream, missingP4Labels)
 
-                if not self.silent:
-                    print("")
-                    if len(self.updatedBranches) > 0:
-                        sys.stdout.write("Updated branches: ")
-                        for b in self.updatedBranches:
-                            sys.stdout.write("%s " % b)
-                        sys.stdout.write("\n")
-
-        if gitConfigBool("git-p4.importLabels"):
-            self.importLabels = True
-
-        if self.importLabels:
-            p4Labels = getP4Labels(self.depotPaths)
-            gitTags = getGitTags()
+        except P4CommandException as e:
+            err = e
 
-            missingP4Labels = p4Labels - gitTags
-            self.importP4Labels(self.gitStream, missingP4Labels)
+        finally:
+            self.closeStreams()
 
-        self.closeStreams()
+        if err:
+            die(str(err))
 
         # Cleanup temporary branches created during import
         if self.tempBranches != []:
index aaa1809d243c6671a9c6fc959d96bd366a65efec..afcb4c09481c936b21e55b2ebb3802343a9a7c9e 100755 (executable)
@@ -241,13 +241,15 @@ cmd_add()
            die "$(eval_gettext "'\$sm_path' does not have a commit checked out")"
        fi
 
-       if test -z "$force" &&
-               ! git add --dry-run --ignore-missing --no-warn-embedded-repo "$sm_path" > /dev/null 2>&1
+       if test -z "$force"
        then
-               eval_gettextln "The following path is ignored by one of your .gitignore files:
-\$sm_path
-Use -f if you really want to add it." >&2
-               exit 1
+           dryerr=$(git add --dry-run --ignore-missing --no-warn-embedded-repo "$sm_path" 2>&1 >/dev/null)
+           res=$?
+           if test $res -ne 0
+           then
+                echo >&2 "$dryerr"
+                exit $res
+           fi
        fi
 
        if test -n "$custom_name"
index 5134ce27806866c41d5eda19cf01110cbf7849f2..2d538bcd6e30d1314f76d89764284f8735cd187f 100644 (file)
@@ -7,6 +7,8 @@
 #include "tempfile.h"
 
 static char *configured_signing_key;
+static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;
+
 struct gpg_format {
        const char *name;
        const char *program;
@@ -85,6 +87,8 @@ void signature_check_clear(struct signature_check *sigc)
 #define GPG_STATUS_UID         (1<<2)
 /* The status includes key fingerprints */
 #define GPG_STATUS_FINGERPRINT (1<<3)
+/* The status includes trust level */
+#define GPG_STATUS_TRUST_LEVEL (1<<4)
 
 /* Short-hand for standard exclusive *SIG status with keyid & UID */
 #define GPG_STATUS_STDSIG      (GPG_STATUS_EXCLUSIVE|GPG_STATUS_KEYID|GPG_STATUS_UID)
@@ -96,13 +100,23 @@ static struct {
 } sigcheck_gpg_status[] = {
        { 'G', "GOODSIG ", GPG_STATUS_STDSIG },
        { 'B', "BADSIG ", GPG_STATUS_STDSIG },
-       { 'U', "TRUST_NEVER", 0 },
-       { 'U', "TRUST_UNDEFINED", 0 },
        { 'E', "ERRSIG ", GPG_STATUS_EXCLUSIVE|GPG_STATUS_KEYID },
        { 'X', "EXPSIG ", GPG_STATUS_STDSIG },
        { 'Y', "EXPKEYSIG ", GPG_STATUS_STDSIG },
        { 'R', "REVKEYSIG ", GPG_STATUS_STDSIG },
        { 0, "VALIDSIG ", GPG_STATUS_FINGERPRINT },
+       { 0, "TRUST_", GPG_STATUS_TRUST_LEVEL },
+};
+
+static struct {
+       const char *key;
+       enum signature_trust_level value;
+} sigcheck_gpg_trust_level[] = {
+       { "UNDEFINED", TRUST_UNDEFINED },
+       { "NEVER", TRUST_NEVER },
+       { "MARGINAL", TRUST_MARGINAL },
+       { "FULLY", TRUST_FULLY },
+       { "ULTIMATE", TRUST_ULTIMATE },
 };
 
 static void replace_cstring(char **field, const char *line, const char *next)
@@ -115,6 +129,20 @@ static void replace_cstring(char **field, const char *line, const char *next)
                *field = NULL;
 }
 
+static int parse_gpg_trust_level(const char *level,
+                                enum signature_trust_level *res)
+{
+       size_t i;
+
+       for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_trust_level); i++) {
+               if (!strcmp(sigcheck_gpg_trust_level[i].key, level)) {
+                       *res = sigcheck_gpg_trust_level[i].value;
+                       return 0;
+               }
+       }
+       return 1;
+}
+
 static void parse_gpg_output(struct signature_check *sigc)
 {
        const char *buf = sigc->gpg_status;
@@ -136,9 +164,18 @@ static void parse_gpg_output(struct signature_check *sigc)
                /* Iterate over all search strings */
                for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
                        if (skip_prefix(line, sigcheck_gpg_status[i].check, &line)) {
+                               /*
+                                * GOODSIG, BADSIG etc. can occur only once for
+                                * each signature.  Therefore, if we had more
+                                * than one then we're dealing with multiple
+                                * signatures.  We don't support them
+                                * currently, and they're rather hard to
+                                * create, so something is likely fishy and we
+                                * should reject them altogether.
+                                */
                                if (sigcheck_gpg_status[i].flags & GPG_STATUS_EXCLUSIVE) {
                                        if (seen_exclusive_status++)
-                                               goto found_duplicate_status;
+                                               goto error;
                                }
 
                                if (sigcheck_gpg_status[i].result)
@@ -154,6 +191,25 @@ static void parse_gpg_output(struct signature_check *sigc)
                                                replace_cstring(&sigc->signer, line, next);
                                        }
                                }
+
+                               /* Do we have trust level? */
+                               if (sigcheck_gpg_status[i].flags & GPG_STATUS_TRUST_LEVEL) {
+                                       /*
+                                        * GPG v1 and v2 differs in how the
+                                        * TRUST_ lines are written.  Some
+                                        * trust lines contain no additional
+                                        * space-separated information for v1.
+                                        */
+                                       size_t trust_size = strcspn(line, " \n");
+                                       char *trust = xmemdupz(line, trust_size);
+
+                                       if (parse_gpg_trust_level(trust, &sigc->trust_level)) {
+                                               free(trust);
+                                               goto error;
+                                       }
+                                       free(trust);
+                               }
+
                                /* Do we have fingerprint? */
                                if (sigcheck_gpg_status[i].flags & GPG_STATUS_FINGERPRINT) {
                                        const char *limit;
@@ -191,14 +247,7 @@ static void parse_gpg_output(struct signature_check *sigc)
        }
        return;
 
-found_duplicate_status:
-       /*
-        * GOODSIG, BADSIG etc. can occur only once for each signature.
-        * Therefore, if we had more than one then we're dealing with multiple
-        * signatures.  We don't support them currently, and they're rather
-        * hard to create, so something is likely fishy and we should reject
-        * them altogether.
-        */
+error:
        sigc->result = 'E';
        /* Clear partial data to avoid confusion */
        FREE_AND_NULL(sigc->primary_key_fingerprint);
@@ -264,6 +313,7 @@ int check_signature(const char *payload, size_t plen, const char *signature,
        int status;
 
        sigc->result = 'N';
+       sigc->trust_level = -1;
 
        status = verify_signed_buffer(payload, plen, signature, slen,
                                      &gpg_output, &gpg_status);
@@ -273,7 +323,8 @@ int check_signature(const char *payload, size_t plen, const char *signature,
        sigc->gpg_output = strbuf_detach(&gpg_output, NULL);
        sigc->gpg_status = strbuf_detach(&gpg_status, NULL);
        parse_gpg_output(sigc);
-       status |= sigc->result != 'G' && sigc->result != 'U';
+       status |= sigc->result != 'G';
+       status |= sigc->trust_level < configured_min_trust_level;
 
  out:
        strbuf_release(&gpg_status);
@@ -320,6 +371,8 @@ int git_gpg_config(const char *var, const char *value, void *cb)
 {
        struct gpg_format *fmt = NULL;
        char *fmtname = NULL;
+       char *trust;
+       int ret;
 
        if (!strcmp(var, "user.signingkey")) {
                if (!value)
@@ -339,6 +392,20 @@ int git_gpg_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (!strcmp(var, "gpg.mintrustlevel")) {
+               if (!value)
+                       return config_error_nonbool(var);
+
+               trust = xstrdup_toupper(value);
+               ret = parse_gpg_trust_level(trust, &configured_min_trust_level);
+               free(trust);
+
+               if (ret)
+                       return error("unsupported value for %s: %s", var,
+                                    value);
+               return 0;
+       }
+
        if (!strcmp(var, "gpg.program") || !strcmp(var, "gpg.openpgp.program"))
                fmtname = "openpgp";
 
index 93cc3aff5c93ce8fa19aadea909960f9267edbdc..f4e9b4f3715a0b0d4be3ef526388bf6b87b56fda 100644 (file)
@@ -7,6 +7,14 @@ struct strbuf;
 #define GPG_VERIFY_RAW                 2
 #define GPG_VERIFY_OMIT_STATUS 4
 
+enum signature_trust_level {
+       TRUST_UNDEFINED,
+       TRUST_NEVER,
+       TRUST_MARGINAL,
+       TRUST_FULLY,
+       TRUST_ULTIMATE,
+};
+
 struct signature_check {
        char *payload;
        char *gpg_output;
@@ -16,7 +24,6 @@ struct signature_check {
         * possible "result":
         * 0 (not checked)
         * N (checked but no further result)
-        * U (untrusted good)
         * G (good)
         * B (bad)
         */
@@ -25,6 +32,7 @@ struct signature_check {
        char *key;
        char *fingerprint;
        char *primary_key_fingerprint;
+       enum signature_trust_level trust_level;
 };
 
 void signature_check_clear(struct signature_check *sigc);
diff --git a/graph.c b/graph.c
index aaf97069bd0b140da425dbc1a5b9fc7250459f13..4fb25ad464db5778262936cdcc97b5088abc7678 100644 (file)
--- a/graph.c
+++ b/graph.c
@@ -1233,8 +1233,14 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l
                         * prevent any other edges from moving
                         * horizontally.
                         */
-                       if (horizontal_edge == -1)
-                               horizontal_edge = i;
+                       if (horizontal_edge == -1) {
+                               int j;
+                               horizontal_edge_target = target;
+                               horizontal_edge = i - 1;
+
+                               for (j = (target * 2) + 3; j < (i - 2); j += 2)
+                                       graph->mapping[j] = target;
+                       }
                }
        }
 
diff --git a/grep.c b/grep.c
index 0552b127c1ac11f06852be42d60b992aa2891fcf..13232a904aca4906f28aae2e04165d1fda47b922 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -1540,11 +1540,6 @@ static inline void grep_attr_unlock(void)
                pthread_mutex_unlock(&grep_attr_mutex);
 }
 
-/*
- * Same as git_attr_mutex, but protecting the thread-unsafe object db access.
- */
-pthread_mutex_t grep_read_mutex;
-
 static int match_funcname(struct grep_opt *opt, struct grep_source *gs, char *bol, char *eol)
 {
        xdemitconf_t *xecfg = opt->priv;
@@ -1741,13 +1736,20 @@ static int fill_textconv_grep(struct repository *r,
        }
 
        /*
-        * fill_textconv is not remotely thread-safe; it may load objects
-        * behind the scenes, and it modifies the global diff tempfile
-        * structure.
+        * fill_textconv is not remotely thread-safe; it modifies the global
+        * diff tempfile structure, writes to the_repo's odb and might
+        * internally call thread-unsafe functions such as the
+        * prepare_packed_git() lazy-initializator. Because of the last two, we
+        * must ensure mutual exclusion between this call and the object reading
+        * API, thus we use obj_read_lock() here.
+        *
+        * TODO: allowing text conversion to run in parallel with object
+        * reading operations might increase performance in the multithreaded
+        * non-worktreee git-grep with --textconv.
         */
-       grep_read_lock();
+       obj_read_lock();
        size = fill_textconv(r, driver, df, &buf);
-       grep_read_unlock();
+       obj_read_unlock();
        free_filespec(df);
 
        /*
@@ -1813,10 +1815,15 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
                grep_source_load_driver(gs, opt->repo->index);
                /*
                 * We might set up the shared textconv cache data here, which
-                * is not thread-safe.
+                * is not thread-safe. Also, get_oid_with_context() and
+                * parse_object() might be internally called. As they are not
+                * currenty thread-safe and might be racy with object reading,
+                * obj_read_lock() must be called.
                 */
                grep_attr_lock();
+               obj_read_lock();
                textconv = userdiff_get_textconv(opt->repo, gs->driver);
+               obj_read_unlock();
                grep_attr_unlock();
        }
 
@@ -2116,10 +2123,7 @@ static int grep_source_load_oid(struct grep_source *gs)
 {
        enum object_type type;
 
-       grep_read_lock();
        gs->buf = read_object_file(gs->identifier, &type, &gs->size);
-       grep_read_unlock();
-
        if (!gs->buf)
                return error(_("'%s': unable to read %s"),
                             gs->name,
diff --git a/grep.h b/grep.h
index 811fd274c95b0528223fff8d684c5299c10a9967..9115db8515059bf170d3d56e8146f9193d3318e4 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -220,18 +220,5 @@ int grep_threads_ok(const struct grep_opt *opt);
  */
 extern int grep_use_locks;
 extern pthread_mutex_t grep_attr_mutex;
-extern pthread_mutex_t grep_read_mutex;
-
-static inline void grep_read_lock(void)
-{
-       if (grep_use_locks)
-               pthread_mutex_lock(&grep_read_mutex);
-}
-
-static inline void grep_read_unlock(void)
-{
-       if (grep_use_locks)
-               pthread_mutex_unlock(&grep_read_mutex);
-}
 
 #endif
index 10dca5644b7b60f899cf1ef64e01550505d1fd8d..aee1769a7ac344c0d0fed681588efce4c09df564 100644 (file)
@@ -1712,6 +1712,14 @@ static char *find_path_for_conflict(struct merge_options *opt,
        return new_path;
 }
 
+/*
+ * Toggle the stage number between "ours" and "theirs" (2 and 3).
+ */
+static inline int flip_stage(int stage)
+{
+       return (2 + 3) - stage;
+}
+
 static int handle_rename_rename_1to2(struct merge_options *opt,
                                     struct rename_conflict_info *ci)
 {
@@ -1756,14 +1764,14 @@ static int handle_rename_rename_1to2(struct merge_options *opt,
                 * such cases, we should keep the added file around,
                 * resolving the conflict at that path in its favor.
                 */
-               add = &ci->ren1->dst_entry->stages[2 ^ 1];
+               add = &ci->ren1->dst_entry->stages[flip_stage(2)];
                if (is_valid(add)) {
                        if (update_file(opt, 0, add, a->path))
                                return -1;
                }
                else
                        remove_file_from_index(opt->repo->index, a->path);
-               add = &ci->ren2->dst_entry->stages[3 ^ 1];
+               add = &ci->ren2->dst_entry->stages[flip_stage(3)];
                if (is_valid(add)) {
                        if (update_file(opt, 0, add, b->path))
                                return -1;
@@ -1776,7 +1784,7 @@ static int handle_rename_rename_1to2(struct merge_options *opt,
                 * rename/add collision.  If not, we can write the file out
                 * to the specified location.
                 */
-               add = &ci->ren1->dst_entry->stages[2 ^ 1];
+               add = &ci->ren1->dst_entry->stages[flip_stage(2)];
                if (is_valid(add)) {
                        add->path = mfi.blob.path = a->path;
                        if (handle_file_collision(opt, a->path,
@@ -1797,7 +1805,7 @@ static int handle_rename_rename_1to2(struct merge_options *opt,
                                return -1;
                }
 
-               add = &ci->ren2->dst_entry->stages[3 ^ 1];
+               add = &ci->ren2->dst_entry->stages[flip_stage(3)];
                if (is_valid(add)) {
                        add->path = mfi.blob.path = b->path;
                        if (handle_file_collision(opt, b->path,
@@ -1846,7 +1854,7 @@ static int handle_rename_rename_2to1(struct merge_options *opt,
        path_side_1_desc = xstrfmt("version of %s from %s", path, a->path);
        path_side_2_desc = xstrfmt("version of %s from %s", path, b->path);
        ostage1 = ci->ren1->branch == opt->branch1 ? 3 : 2;
-       ostage2 = ostage1 ^ 1;
+       ostage2 = flip_stage(ostage1);
        ci->ren1->src_entry->stages[ostage1].path = a->path;
        ci->ren2->src_entry->stages[ostage2].path = b->path;
        if (merge_mode_and_contents(opt, a, c1,
diff --git a/notes.c b/notes.c
index 0c79964c26a71d6843c43c94fe260131334749f2..a0349fa77831bcb79525e3f3a06132297857df87 100644 (file)
--- a/notes.c
+++ b/notes.c
@@ -1279,10 +1279,8 @@ static void format_note(struct notes_tree *t, const struct object_id *object_oid
                if (!ref || !strcmp(ref, GIT_NOTES_DEFAULT_REF)) {
                        strbuf_addstr(sb, "\nNotes:\n");
                } else {
-                       if (starts_with(ref, "refs/"))
-                               ref += 5;
-                       if (starts_with(ref, "notes/"))
-                               ref += 6;
+                       skip_prefix(ref, "refs/", &ref);
+                       skip_prefix(ref, "notes/", &ref);
                        strbuf_addf(sb, "\nNotes (%s):\n", ref);
                }
        }
index bb19f4aa9973a6c54d4bb73e609730db7f01e160..5b047637e3e02e9e8eda665095b3630ae20025e4 100644 (file)
@@ -6,6 +6,7 @@
 #include "list.h"
 #include "sha1-array.h"
 #include "strbuf.h"
+#include "thread-utils.h"
 
 struct object_directory {
        struct object_directory *next;
@@ -125,6 +126,8 @@ struct raw_object_store {
         * (see git-replace(1)).
         */
        struct oidmap *replace_map;
+       unsigned replace_map_initialized : 1;
+       pthread_mutex_t replace_mutex; /* protect object replace functions */
 
        struct commit_graph *commit_graph;
        unsigned commit_graph_attempted : 1; /* if loading has been attempted */
@@ -209,6 +212,14 @@ int hash_object_file_literally(const void *buf, unsigned long len,
                               const char *type, struct object_id *oid,
                               unsigned flags);
 
+/*
+ * Add an object file to the in-memory object store, without writing it
+ * to disk.
+ *
+ * Callers are responsible for calling write_object_file to record the
+ * object in persistent storage before writing any other new objects
+ * that reference it.
+ */
 int pretend_object_file(void *, unsigned long, enum object_type,
                        struct object_id *oid);
 
@@ -250,6 +261,40 @@ int has_loose_object_nonlocal(const struct object_id *);
 
 void assert_oid_type(const struct object_id *oid, enum object_type expect);
 
+/*
+ * Enabling the object read lock allows multiple threads to safely call the
+ * following functions in parallel: repo_read_object_file(), read_object_file(),
+ * read_object_file_extended(), read_object_with_reference(), read_object(),
+ * oid_object_info() and oid_object_info_extended().
+ *
+ * obj_read_lock() and obj_read_unlock() may also be used to protect other
+ * section which cannot execute in parallel with object reading. Since the used
+ * lock is a recursive mutex, these sections can even contain calls to object
+ * reading functions. However, beware that in these cases zlib inflation won't
+ * be performed in parallel, losing performance.
+ *
+ * TODO: oid_object_info_extended()'s call stack has a recursive behavior. If
+ * any of its callees end up calling it, this recursive call won't benefit from
+ * parallel inflation.
+ */
+void enable_obj_read_lock(void);
+void disable_obj_read_lock(void);
+
+extern int obj_read_use_lock;
+extern pthread_mutex_t obj_read_mutex;
+
+static inline void obj_read_lock(void)
+{
+       if(obj_read_use_lock)
+               pthread_mutex_lock(&obj_read_mutex);
+}
+
+static inline void obj_read_unlock(void)
+{
+       if(obj_read_use_lock)
+               pthread_mutex_unlock(&obj_read_mutex);
+}
+
 struct object_info {
        /* Request */
        enum object_type *typep;
@@ -293,8 +338,6 @@ struct object_info {
 #define OBJECT_INFO_LOOKUP_REPLACE 1
 /* Allow reading from a loose object file of unknown/bogus type */
 #define OBJECT_INFO_ALLOW_UNKNOWN_TYPE 2
-/* Do not check cached storage */
-#define OBJECT_INFO_SKIP_CACHED 4
 /* Do not retry packed storage after checking packed and loose storage */
 #define OBJECT_INFO_QUICK 8
 /* Do not check loose object */
index 81f5820fc358b7a52dae5c43d8edc0e793187bdb..c94be9449922de81e9576b8146d0ed08ab5ee9a6 100644 (file)
--- a/object.c
+++ b/object.c
@@ -481,6 +481,7 @@ struct raw_object_store *raw_object_store_new(void)
        memset(o, 0, sizeof(*o));
        INIT_LIST_HEAD(&o->packed_git_mru);
        hashmap_init(&o->pack_map, pack_map_entry_cmp, NULL, 0);
+       pthread_mutex_init(&o->replace_mutex, NULL);
        return o;
 }
 
@@ -508,6 +509,7 @@ void raw_object_store_clear(struct raw_object_store *o)
 
        oidmap_free(o->replace_map, 1);
        FREE_AND_NULL(o->replace_map);
+       pthread_mutex_destroy(&o->replace_mutex);
 
        free_commit_graph(o->commit_graph);
        o->commit_graph = NULL;
index e07c798879b7277797b093a3eac6b4aece9ee4d2..5a8689cdf8a379976901e49b8150a4e0662c812c 100644 (file)
@@ -326,6 +326,13 @@ failed:
        munmap(bitmap_git->map, bitmap_git->map_size);
        bitmap_git->map = NULL;
        bitmap_git->map_size = 0;
+
+       kh_destroy_oid_map(bitmap_git->bitmaps);
+       bitmap_git->bitmaps = NULL;
+
+       kh_destroy_oid_pos(bitmap_git->ext_index.positions);
+       bitmap_git->ext_index.positions = NULL;
+
        return -1;
 }
 
@@ -622,7 +629,7 @@ static void show_objects_for_type(
        enum object_type object_type,
        show_reachable_fn show_reach)
 {
-       size_t pos = 0, i = 0;
+       size_t i = 0;
        uint32_t offset;
 
        struct ewah_iterator it;
@@ -630,13 +637,15 @@ static void show_objects_for_type(
 
        struct bitmap *objects = bitmap_git->result;
 
-       if (bitmap_git->reuse_objects == bitmap_git->pack->num_objects)
-               return;
-
        ewah_iterator_init(&it, type_filter);
 
-       while (i < objects->word_alloc && ewah_iterator_next(&filter, &it)) {
+       for (i = 0; i < objects->word_alloc &&
+                       ewah_iterator_next(&filter, &it); i++) {
                eword_t word = objects->words[i] & filter;
+               size_t pos = (i * BITS_IN_EWORD);
+
+               if (!word)
+                       continue;
 
                for (offset = 0; offset < BITS_IN_EWORD; ++offset) {
                        struct object_id oid;
@@ -648,9 +657,6 @@ static void show_objects_for_type(
 
                        offset += ewah_bit_ctz64(word >> offset);
 
-                       if (pos + offset < bitmap_git->reuse_objects)
-                               continue;
-
                        entry = &bitmap_git->pack->revindex[pos + offset];
                        nth_packed_object_oid(&oid, bitmap_git->pack, entry->nr);
 
@@ -659,9 +665,6 @@ static void show_objects_for_type(
 
                        show_reach(&oid, object_type, 0, hash, bitmap_git->pack, entry->offset);
                }
-
-               pos += BITS_IN_EWORD;
-               i++;
        }
 }
 
@@ -768,66 +771,139 @@ cleanup:
        return NULL;
 }
 
-int reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
-                                      struct packed_git **packfile,
-                                      uint32_t *entries,
-                                      off_t *up_to)
+static void try_partial_reuse(struct bitmap_index *bitmap_git,
+                             size_t pos,
+                             struct bitmap *reuse,
+                             struct pack_window **w_curs)
 {
+       struct revindex_entry *revidx;
+       off_t offset;
+       enum object_type type;
+       unsigned long size;
+
+       if (pos >= bitmap_git->pack->num_objects)
+               return; /* not actually in the pack */
+
+       revidx = &bitmap_git->pack->revindex[pos];
+       offset = revidx->offset;
+       type = unpack_object_header(bitmap_git->pack, w_curs, &offset, &size);
+       if (type < 0)
+               return; /* broken packfile, punt */
+
+       if (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA) {
+               off_t base_offset;
+               int base_pos;
+
+               /*
+                * Find the position of the base object so we can look it up
+                * in our bitmaps. If we can't come up with an offset, or if
+                * that offset is not in the revidx, the pack is corrupt.
+                * There's nothing we can do, so just punt on this object,
+                * and the normal slow path will complain about it in
+                * more detail.
+                */
+               base_offset = get_delta_base(bitmap_git->pack, w_curs,
+                                            &offset, type, revidx->offset);
+               if (!base_offset)
+                       return;
+               base_pos = find_revindex_position(bitmap_git->pack, base_offset);
+               if (base_pos < 0)
+                       return;
+
+               /*
+                * We assume delta dependencies always point backwards. This
+                * lets us do a single pass, and is basically always true
+                * due to the way OFS_DELTAs work. You would not typically
+                * find REF_DELTA in a bitmapped pack, since we only bitmap
+                * packs we write fresh, and OFS_DELTA is the default). But
+                * let's double check to make sure the pack wasn't written with
+                * odd parameters.
+                */
+               if (base_pos >= pos)
+                       return;
+
+               /*
+                * And finally, if we're not sending the base as part of our
+                * reuse chunk, then don't send this object either. The base
+                * would come after us, along with other objects not
+                * necessarily in the pack, which means we'd need to convert
+                * to REF_DELTA on the fly. Better to just let the normal
+                * object_entry code path handle it.
+                */
+               if (!bitmap_get(reuse, base_pos))
+                       return;
+       }
+
        /*
-        * Reuse the packfile content if we need more than
-        * 90% of its objects
+        * If we got here, then the object is OK to reuse. Mark it.
         */
-       static const double REUSE_PERCENT = 0.9;
+       bitmap_set(reuse, pos);
+}
 
+int reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
+                                      struct packed_git **packfile_out,
+                                      uint32_t *entries,
+                                      struct bitmap **reuse_out)
+{
        struct bitmap *result = bitmap_git->result;
-       uint32_t reuse_threshold;
-       uint32_t i, reuse_objects = 0;
+       struct bitmap *reuse;
+       struct pack_window *w_curs = NULL;
+       size_t i = 0;
+       uint32_t offset;
 
        assert(result);
 
-       for (i = 0; i < result->word_alloc; ++i) {
-               if (result->words[i] != (eword_t)~0) {
-                       reuse_objects += ewah_bit_ctz64(~result->words[i]);
-                       break;
-               }
+       while (i < result->word_alloc && result->words[i] == (eword_t)~0)
+               i++;
 
-               reuse_objects += BITS_IN_EWORD;
-       }
+       /* Don't mark objects not in the packfile */
+       if (i > bitmap_git->pack->num_objects / BITS_IN_EWORD)
+               i = bitmap_git->pack->num_objects / BITS_IN_EWORD;
 
-#ifdef GIT_BITMAP_DEBUG
-       {
-               const unsigned char *sha1;
-               struct revindex_entry *entry;
+       reuse = bitmap_word_alloc(i);
+       memset(reuse->words, 0xFF, i * sizeof(eword_t));
 
-               entry = &bitmap_git->reverse_index->revindex[reuse_objects];
-               sha1 = nth_packed_object_sha1(bitmap_git->pack, entry->nr);
+       for (; i < result->word_alloc; ++i) {
+               eword_t word = result->words[i];
+               size_t pos = (i * BITS_IN_EWORD);
+
+               for (offset = 0; offset < BITS_IN_EWORD; ++offset) {
+                       if ((word >> offset) == 0)
+                               break;
 
-               fprintf(stderr, "Failed to reuse at %d (%016llx)\n",
-                       reuse_objects, result->words[i]);
-               fprintf(stderr, " %s\n", hash_to_hex(sha1));
+                       offset += ewah_bit_ctz64(word >> offset);
+                       try_partial_reuse(bitmap_git, pos + offset, reuse, &w_curs);
+               }
        }
-#endif
 
-       if (!reuse_objects)
-               return -1;
+       unuse_pack(&w_curs);
 
-       if (reuse_objects >= bitmap_git->pack->num_objects) {
-               bitmap_git->reuse_objects = *entries = bitmap_git->pack->num_objects;
-               *up_to = -1; /* reuse the full pack */
-               *packfile = bitmap_git->pack;
-               return 0;
+       *entries = bitmap_popcount(reuse);
+       if (!*entries) {
+               bitmap_free(reuse);
+               return -1;
        }
 
-       reuse_threshold = bitmap_popcount(bitmap_git->result) * REUSE_PERCENT;
+       /*
+        * Drop any reused objects from the result, since they will not
+        * need to be handled separately.
+        */
+       bitmap_and_not(result, reuse);
+       *packfile_out = bitmap_git->pack;
+       *reuse_out = reuse;
+       return 0;
+}
 
-       if (reuse_objects < reuse_threshold)
-               return -1;
+int bitmap_walk_contains(struct bitmap_index *bitmap_git,
+                        struct bitmap *bitmap, const struct object_id *oid)
+{
+       int idx;
 
-       bitmap_git->reuse_objects = *entries = reuse_objects;
-       *up_to = bitmap_git->pack->revindex[reuse_objects].offset;
-       *packfile = bitmap_git->pack;
+       if (!bitmap)
+               return 0;
 
-       return 0;
+       idx = bitmap_position(bitmap_git, oid);
+       return idx >= 0 && bitmap_get(bitmap, idx);
 }
 
 void traverse_bitmap_commit_list(struct bitmap_index *bitmap_git,
@@ -1118,16 +1194,6 @@ void free_bitmap_index(struct bitmap_index *b)
 int bitmap_has_oid_in_uninteresting(struct bitmap_index *bitmap_git,
                                    const struct object_id *oid)
 {
-       int pos;
-
-       if (!bitmap_git)
-               return 0; /* no bitmap loaded */
-       if (!bitmap_git->haves)
-               return 0; /* walk had no "haves" */
-
-       pos = bitmap_position_packfile(bitmap_git, oid);
-       if (pos < 0)
-               return 0;
-
-       return bitmap_get(bitmap_git->haves, pos);
+       return bitmap_git &&
+               bitmap_walk_contains(bitmap_git, bitmap_git->haves, oid);
 }
index 466c5afa09c37c4e5a56a33604510659b209e86b..bcd03b8993fd7a409cd12a40ddab366b4f6ab22e 100644 (file)
@@ -3,6 +3,7 @@
 
 #include "ewah/ewok.h"
 #include "khash.h"
+#include "pack.h"
 #include "pack-objects.h"
 
 struct commit;
@@ -49,10 +50,13 @@ void test_bitmap_walk(struct rev_info *revs);
 struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs);
 int reuse_partial_packfile_from_bitmap(struct bitmap_index *,
                                       struct packed_git **packfile,
-                                      uint32_t *entries, off_t *up_to);
+                                      uint32_t *entries,
+                                      struct bitmap **reuse_out);
 int rebuild_existing_bitmaps(struct bitmap_index *, struct packing_data *mapping,
                             kh_oid_map_t *reused_bitmaps, int show_progress);
 void free_bitmap_index(struct bitmap_index *);
+int bitmap_walk_contains(struct bitmap_index *,
+                        struct bitmap *bitmap, const struct object_id *oid);
 
 /*
  * After a traversal has been performed by prepare_bitmap_walk(), this can be
index 7e7c04e4d802e20438a8a94181c6a92fa5c5184b..99dd1a7d094fe864b5e60853965e2cb579003fd2 100644 (file)
@@ -1004,12 +1004,14 @@ void reprepare_packed_git(struct repository *r)
 {
        struct object_directory *odb;
 
+       obj_read_lock();
        for (odb = r->objects->odb; odb; odb = odb->next)
                odb_clear_loose_cache(odb);
 
        r->objects->approximate_object_count_valid = 0;
        r->objects->packed_git_initialized = 0;
        prepare_packed_git(r);
+       obj_read_unlock();
 }
 
 struct packed_git *get_packed_git(struct repository *r)
@@ -1086,7 +1088,23 @@ unsigned long get_size_from_delta(struct packed_git *p,
        do {
                in = use_pack(p, w_curs, curpos, &stream.avail_in);
                stream.next_in = in;
+               /*
+                * Note: the window section returned by use_pack() must be
+                * available throughout git_inflate()'s unlocked execution. To
+                * ensure no other thread will modify the window in the
+                * meantime, we rely on the packed_window.inuse_cnt. This
+                * counter is incremented before window reading and checked
+                * before window disposal.
+                *
+                * Other worrying sections could be the call to close_pack_fd(),
+                * which can close packs even with in-use windows, and to
+                * reprepare_packed_git(). Regarding the former, mmap doc says:
+                * "closing the file descriptor does not unmap the region". And
+                * for the latter, it won't re-open already available packs.
+                */
+               obj_read_unlock();
                st = git_inflate(&stream, Z_FINISH);
+               obj_read_lock();
                curpos += stream.next_in - in;
        } while ((st == Z_OK || st == Z_BUF_ERROR) &&
                 stream.total_out < sizeof(delta_head));
@@ -1162,11 +1180,11 @@ const struct packed_git *has_packed_and_bad(struct repository *r,
        return NULL;
 }
 
-static off_t get_delta_base(struct packed_git *p,
-                                   struct pack_window **w_curs,
-                                   off_t *curpos,
-                                   enum object_type type,
-                                   off_t delta_obj_offset)
+off_t get_delta_base(struct packed_git *p,
+                    struct pack_window **w_curs,
+                    off_t *curpos,
+                    enum object_type type,
+                    off_t delta_obj_offset)
 {
        unsigned char *base_info = use_pack(p, w_curs, *curpos, NULL);
        off_t base_offset;
@@ -1445,6 +1463,14 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset,
        struct delta_base_cache_entry *ent = xmalloc(sizeof(*ent));
        struct list_head *lru, *tmp;
 
+       /*
+        * Check required to avoid redundant entries when more than one thread
+        * is unpacking the same object, in unpack_entry() (since its phases I
+        * and III might run concurrently across multiple threads).
+        */
+       if (in_delta_base_cache(p, base_offset))
+               return;
+
        delta_base_cached += base_size;
 
        list_for_each_safe(lru, tmp, &delta_base_cache_lru) {
@@ -1574,7 +1600,15 @@ static void *unpack_compressed_entry(struct packed_git *p,
        do {
                in = use_pack(p, w_curs, curpos, &stream.avail_in);
                stream.next_in = in;
+               /*
+                * Note: we must ensure the window section returned by
+                * use_pack() will be available throughout git_inflate()'s
+                * unlocked execution. Please refer to the comment at
+                * get_size_from_delta() to see how this is done.
+                */
+               obj_read_unlock();
                st = git_inflate(&stream, Z_FINISH);
+               obj_read_lock();
                if (!stream.avail_out)
                        break; /* the payload is larger than it should be */
                curpos += stream.next_in - in;
index fc7904ec8147004cdcec5f013a7bace27c8697ed..ec536a4ae51fc94db9c4c94397bd9177ff34c1c2 100644 (file)
@@ -151,6 +151,9 @@ void *unpack_entry(struct repository *r, struct packed_git *, off_t, enum object
 unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
 unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t);
 int unpack_object_header(struct packed_git *, struct pack_window **, off_t *, unsigned long *);
+off_t get_delta_base(struct packed_git *p, struct pack_window **w_curs,
+                    off_t *curpos, enum object_type type,
+                    off_t delta_obj_offset);
 
 void release_pack_memory(size_t);
 
index 60fae3ad213782dffa82e3c7dd246b47c566bf56..a0cef401fcb6d5237aac5eaaefc155054db0f9cf 100644 (file)
@@ -357,8 +357,7 @@ is_abbreviated:
                        }
                        /* negated? */
                        if (!starts_with(arg, "no-")) {
-                               if (starts_with(long_name, "no-")) {
-                                       long_name += 3;
+                               if (skip_prefix(long_name, "no-", &long_name)) {
                                        opt_flags |= OPT_UNSET;
                                        goto again;
                                }
@@ -420,7 +419,7 @@ static void check_typos(const char *arg, const struct option *options)
                return;
 
        if (starts_with(arg, "no-")) {
-               error(_("did you mean `--%s` (with two dashes ?)"), arg);
+               error(_("did you mean `--%s` (with two dashes)?"), arg);
                exit(129);
        }
 
@@ -428,7 +427,7 @@ static void check_typos(const char *arg, const struct option *options)
                if (!options->long_name)
                        continue;
                if (starts_with(options->long_name, arg)) {
-                       error(_("did you mean `--%s` (with two dashes ?)"), arg);
+                       error(_("did you mean `--%s` (with two dashes)?"), arg);
                        exit(129);
                }
        }
index 305e903192a7ae4fb125090436130f434bd46e7c..f5fbbc5ffb769e63312c80275dacf59ef28c9731 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -1311,9 +1311,18 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
                case '?':
                        switch (c->signature_check.result) {
                        case 'G':
+                               switch (c->signature_check.trust_level) {
+                               case TRUST_UNDEFINED:
+                               case TRUST_NEVER:
+                                       strbuf_addch(sb, 'U');
+                                       break;
+                               default:
+                                       strbuf_addch(sb, 'G');
+                                       break;
+                               }
+                               break;
                        case 'B':
                        case 'E':
-                       case 'U':
                        case 'N':
                        case 'X':
                        case 'Y':
@@ -1337,6 +1346,25 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
                        if (c->signature_check.primary_key_fingerprint)
                                strbuf_addstr(sb, c->signature_check.primary_key_fingerprint);
                        break;
+               case 'T':
+                       switch (c->signature_check.trust_level) {
+                       case TRUST_UNDEFINED:
+                               strbuf_addstr(sb, "undefined");
+                               break;
+                       case TRUST_NEVER:
+                               strbuf_addstr(sb, "never");
+                               break;
+                       case TRUST_MARGINAL:
+                               strbuf_addstr(sb, "marginal");
+                               break;
+                       case TRUST_FULLY:
+                               strbuf_addstr(sb, "fully");
+                               break;
+                       case TRUST_ULTIMATE:
+                               strbuf_addstr(sb, "ultimate");
+                               break;
+                       }
+                       break;
                default:
                        return 0;
                }
index 9741f057505d2041be1e8dbd49b444aaeab4bd82..803bef5c87e00617537d3ef1fa04204b7350afb4 100644 (file)
@@ -17,9 +17,8 @@ static enum protocol_version parse_protocol_version(const char *value)
 enum protocol_version get_protocol_version_config(void)
 {
        const char *value;
-       enum protocol_version retval = protocol_v0;
        const char *git_test_k = "GIT_TEST_PROTOCOL_VERSION";
-       const char *git_test_v = getenv(git_test_k);
+       const char *git_test_v;
 
        if (!git_config_get_string_const("protocol.version", &value)) {
                enum protocol_version version = parse_protocol_version(value);
@@ -28,19 +27,19 @@ enum protocol_version get_protocol_version_config(void)
                        die("unknown value for config 'protocol.version': %s",
                            value);
 
-               retval = version;
+               return version;
        }
 
+       git_test_v = getenv(git_test_k);
        if (git_test_v && *git_test_v) {
                enum protocol_version env = parse_protocol_version(git_test_v);
 
                if (env == protocol_unknown_version)
                        die("unknown value for %s: %s", git_test_k, git_test_v);
-               if (retval < env)
-                       retval = env;
+               return env;
        }
 
-       return retval;
+       return protocol_v2;
 }
 
 enum protocol_version determine_protocol_version_server(void)
index aa18ae82b724812ea0b2939842395ac78f77bab1..ac001dea5887a119f839ab4577797f9a9a179174 100644 (file)
@@ -5,6 +5,13 @@
 #include "strbuf.h"
 #include "commit-slab.h"
 #include "config.h"
+#include "dir.h"
+
+static const char edit_todo_list_advice[] =
+N_("You can fix this with 'git rebase --edit-todo' "
+"and then run 'git rebase --continue'.\n"
+"Or you can abort the rebase with 'git rebase"
+" --abort'.\n");
 
 enum missing_commit_check_level {
        MISSING_COMMIT_CHECK_IGNORE = 0,
@@ -91,22 +98,27 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,
                   struct todo_list *new_todo, const char *shortrevisions,
                   const char *shortonto, unsigned flags)
 {
-       const char *todo_file = rebase_path_todo();
+       const char *todo_file = rebase_path_todo(),
+               *todo_backup = rebase_path_todo_backup();
        unsigned initial = shortrevisions && shortonto;
+       int incorrect = 0;
 
        /* If the user is editing the todo list, we first try to parse
         * it.  If there is an error, we do not return, because the user
         * might want to fix it in the first place. */
        if (!initial)
-               todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list);
+               incorrect = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list) |
+                       file_exists(rebase_path_dropped());
 
        if (todo_list_write_to_file(r, todo_list, todo_file, shortrevisions, shortonto,
                                    -1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP))
                return error_errno(_("could not write '%s'"), todo_file);
 
-       if (initial && copy_file(rebase_path_todo_backup(), todo_file, 0666))
-               return error(_("could not copy '%s' to '%s'."), todo_file,
-                            rebase_path_todo_backup());
+       if (!incorrect &&
+           todo_list_write_to_file(r, todo_list, todo_backup,
+                                   shortrevisions, shortonto, -1,
+                                   (flags | TODO_LIST_APPEND_TODO_HELP) & ~TODO_LIST_SHORTEN_IDS) < 0)
+               return error(_("could not write '%s'."), rebase_path_todo_backup());
 
        if (launch_sequence_editor(todo_file, &new_todo->buf, NULL))
                return -2;
@@ -115,10 +127,23 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,
        if (initial && new_todo->buf.len == 0)
                return -3;
 
-       /* For the initial edit, the todo list gets parsed in
-        * complete_action(). */
-       if (!initial)
-               return todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo);
+       if (todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo)) {
+               fprintf(stderr, _(edit_todo_list_advice));
+               return -4;
+       }
+
+       if (incorrect) {
+               if (todo_list_check_against_backup(r, new_todo)) {
+                       write_file(rebase_path_dropped(), "");
+                       return -4;
+               }
+
+               if (incorrect > 0)
+                       unlink(rebase_path_dropped());
+       } else if (todo_list_check(todo_list, new_todo)) {
+               write_file(rebase_path_dropped(), "");
+               return -4;
+       }
 
        return 0;
 }
@@ -183,7 +208,52 @@ int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo)
                "the level of warnings.\n"
                "The possible behaviours are: ignore, warn, error.\n\n"));
 
+       fprintf(stderr, _(edit_todo_list_advice));
+
 leave_check:
        clear_commit_seen(&commit_seen);
        return res;
 }
+
+int todo_list_check_against_backup(struct repository *r, struct todo_list *todo_list)
+{
+       struct todo_list backup = TODO_LIST_INIT;
+       int res = 0;
+
+       if (strbuf_read_file(&backup.buf, rebase_path_todo_backup(), 0) > 0) {
+               todo_list_parse_insn_buffer(r, backup.buf.buf, &backup);
+               res = todo_list_check(&backup, todo_list);
+       }
+
+       todo_list_release(&backup);
+       return res;
+}
+
+int check_todo_list_from_file(struct repository *r)
+{
+       struct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;
+       int res = 0;
+
+       if (strbuf_read_file(&new_todo.buf, rebase_path_todo(), 0) < 0) {
+               res = error(_("could not read '%s'."), rebase_path_todo());
+               goto out;
+       }
+
+       if (strbuf_read_file(&old_todo.buf, rebase_path_todo_backup(), 0) < 0) {
+               res = error(_("could not read '%s'."), rebase_path_todo_backup());
+               goto out;
+       }
+
+       res = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);
+       if (!res)
+               res = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);
+       if (res)
+               fprintf(stderr, _(edit_todo_list_advice));
+       if (!res)
+               res = todo_list_check(&old_todo, &new_todo);
+out:
+       todo_list_release(&old_todo);
+       todo_list_release(&new_todo);
+
+       return res;
+}
index 44dbb06311a84168d8a5970267a14ade85671a98..4af0c1fcc702ed74ecf60d31a71eeb6154c108f9 100644 (file)
@@ -11,6 +11,11 @@ void append_todo_help(unsigned keep_empty, int command_count,
 int edit_todo_list(struct repository *r, struct todo_list *todo_list,
                   struct todo_list *new_todo, const char *shortrevisions,
                   const char *shortonto, unsigned flags);
+
 int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo);
+int todo_list_check_against_backup(struct repository *r,
+                                  struct todo_list *todo_list);
+
+int check_todo_list_from_file(struct repository *r);
 
 #endif
index 0ea66a28b6c9b951041c050c5489c8110905549c..561c33ac8a97a8e0a3de9ba27ca9bd51937c6c03 100644 (file)
@@ -465,8 +465,7 @@ stat_ref:
        close(fd);
        strbuf_rtrim(&sb_contents);
        buf = sb_contents.buf;
-       if (starts_with(buf, "ref:")) {
-               buf += 4;
+       if (skip_prefix(buf, "ref:", &buf)) {
                while (isspace(*buf))
                        buf++;
 
index 350d92a074ed82a99060cebb1a4a3fb0a98cfd8e..8eb96152f51b641477c3b01bd62801d2bd2075eb 100644 (file)
@@ -1255,8 +1255,9 @@ static void parse_push(struct strbuf *buf)
        int ret;
 
        do {
-               if (starts_with(buf->buf, "push "))
-                       argv_array_push(&specs, buf->buf + 5);
+               const char *arg;
+               if (skip_prefix(buf->buf, "push ", &arg))
+                       argv_array_push(&specs, arg);
                else
                        die(_("http transport does not support %s"), buf->buf);
 
index e295e87943102c2a1ad903aea1a634d34bf603a0..7bd9aba6ee6c339e02c6fe25262a75a19bfc6e68 100644 (file)
@@ -34,14 +34,23 @@ static int register_replace_ref(struct repository *r,
 
 void prepare_replace_object(struct repository *r)
 {
-       if (r->objects->replace_map)
+       if (r->objects->replace_map_initialized)
                return;
 
+       pthread_mutex_lock(&r->objects->replace_mutex);
+       if (r->objects->replace_map_initialized) {
+               pthread_mutex_unlock(&r->objects->replace_mutex);
+               return;
+       }
+
        r->objects->replace_map =
                xmalloc(sizeof(*r->objects->replace_map));
        oidmap_init(r->objects->replace_map, 0);
 
        for_each_replace_ref(r, register_replace_ref, NULL);
+       r->objects->replace_map_initialized = 1;
+
+       pthread_mutex_unlock(&r->objects->replace_mutex);
 }
 
 /* We allow "recursive" replacement. Only within reason, though */
index 04ed7a85a2402d64e715c38850a41bea43d0f3b3..3fbc32eb7b7ef7bf9119f83aa7e3a4b6c20104b3 100644 (file)
@@ -24,12 +24,17 @@ const struct object_id *do_lookup_replace_object(struct repository *r,
  * name (replaced recursively, if necessary).  The return value is
  * either sha1 or a pointer to a permanently-allocated value.  When
  * object replacement is suppressed, always return sha1.
+ *
+ * Note: some thread debuggers might point a data race on the
+ * replace_map_initialized reading in this function. However, we know there's no
+ * problem in the value being updated by one thread right after another one read
+ * it here (and it should be written to only once, anyway).
  */
 static inline const struct object_id *lookup_replace_object(struct repository *r,
                                                            const struct object_id *oid)
 {
        if (!read_replace_refs ||
-           (r->objects->replace_map &&
+           (r->objects->replace_map_initialized &&
             r->objects->replace_map->map.tablesize == 0))
                return oid;
        return do_lookup_replace_object(r, oid);
index 9942f120a9b928831008a561ce6f09b60fa5b0e0..f5e1149f9b395e77c3d55a147332c1117746d7b1 100644 (file)
@@ -213,8 +213,9 @@ static char *locate_in_PATH(const char *file)
 static int exists_in_PATH(const char *file)
 {
        char *r = locate_in_PATH(file);
+       int found = r != NULL;
        free(r);
-       return r != NULL;
+       return found;
 }
 
 int sane_execvp(const char *file, char * const argv[])
index b9dbf1adb078188a0163ad8ae29cce92c0587b88..ba90a513b95f1ddd2b53d6eeafd28ef5d49745e6 100644 (file)
@@ -57,6 +57,8 @@ static GIT_PATH_FUNC(rebase_path, "rebase-merge")
 GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
 GIT_PATH_FUNC(rebase_path_todo_backup, "rebase-merge/git-rebase-todo.backup")
 
+GIT_PATH_FUNC(rebase_path_dropped, "rebase-merge/dropped")
+
 /*
  * The rebase command lines that have already been processed. A line
  * is moved here when it is first handled, before any associated user
@@ -588,7 +590,7 @@ static int do_recursive_merge(struct repository *r,
        struct merge_options o;
        struct tree *next_tree, *base_tree, *head_tree;
        int clean;
-       char **xopt;
+       int i;
        struct lock_file index_lock = LOCK_INIT;
 
        if (repo_hold_locked_index(r, &index_lock, LOCK_REPORT_ON_ERROR) < 0)
@@ -608,8 +610,8 @@ static int do_recursive_merge(struct repository *r,
        next_tree = next ? get_commit_tree(next) : empty_tree(r);
        base_tree = base ? get_commit_tree(base) : empty_tree(r);
 
-       for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)
-               parse_merge_opt(&o, *xopt);
+       for (i = 0; i < opts->xopts_nr; i++)
+               parse_merge_opt(&o, opts->xopts[i]);
 
        clean = merge_trees(&o,
                            head_tree,
@@ -2118,6 +2120,8 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
        saved = *end_of_object_name;
        *end_of_object_name = '\0';
        status = get_oid(bol, &commit_oid);
+       if (status < 0)
+               error(_("could not parse '%s'"), bol); /* return later */
        *end_of_object_name = saved;
 
        bol = end_of_object_name + strspn(end_of_object_name, " \t");
@@ -2125,11 +2129,10 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
        item->arg_len = (int)(eol - bol);
 
        if (status < 0)
-               return error(_("could not parse '%.*s'"),
-                            (int)(end_of_object_name - bol), bol);
+               return status;
 
        item->commit = lookup_commit_reference(r, &commit_oid);
-       return !item->commit;
+       return item->commit ? 0 : -1;
 }
 
 int sequencer_get_last_command(struct repository *r, enum replay_action *action)
@@ -3715,20 +3718,6 @@ static int run_git_checkout(struct repository *r, struct replay_opts *opts,
        return ret;
 }
 
-int prepare_branch_to_be_rebased(struct repository *r, struct replay_opts *opts,
-                                const char *commit)
-{
-       const char *action;
-
-       if (commit && *commit) {
-               action = reflog_message(opts, "start", "checkout %s", commit);
-               if (run_git_checkout(r, opts, commit, action))
-                       return error(_("could not checkout %s"), commit);
-       }
-
-       return 0;
-}
-
 static int checkout_onto(struct repository *r, struct replay_opts *opts,
                         const char *onto_name, const struct object_id *onto,
                         const char *orig_head)
@@ -4238,6 +4227,14 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
        if (is_rebase_i(opts)) {
                if ((res = read_populate_todo(r, &todo_list, opts)))
                        goto release_todo_list;
+
+               if (file_exists(rebase_path_dropped())) {
+                       if ((res = todo_list_check_against_backup(r, &todo_list)))
+                               goto release_todo_list;
+
+                       unlink(rebase_path_dropped());
+               }
+
                if (commit_staged_changes(r, opts, &todo_list)) {
                        res = -1;
                        goto release_todo_list;
@@ -4984,41 +4981,6 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,
        return res;
 }
 
-static const char edit_todo_list_advice[] =
-N_("You can fix this with 'git rebase --edit-todo' "
-"and then run 'git rebase --continue'.\n"
-"Or you can abort the rebase with 'git rebase"
-" --abort'.\n");
-
-int check_todo_list_from_file(struct repository *r)
-{
-       struct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;
-       int res = 0;
-
-       if (strbuf_read_file_or_whine(&new_todo.buf, rebase_path_todo()) < 0) {
-               res = -1;
-               goto out;
-       }
-
-       if (strbuf_read_file_or_whine(&old_todo.buf, rebase_path_todo_backup()) < 0) {
-               res = -1;
-               goto out;
-       }
-
-       res = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);
-       if (!res)
-               res = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);
-       if (!res)
-               res = todo_list_check(&old_todo, &new_todo);
-       if (res)
-               fprintf(stderr, _(edit_todo_list_advice));
-out:
-       todo_list_release(&old_todo);
-       todo_list_release(&new_todo);
-
-       return res;
-}
-
 /* skip picking commits whose parents are unchanged */
 static int skip_unnecessary_picks(struct repository *r,
                                  struct todo_list *todo_list,
@@ -5075,7 +5037,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
 {
        const char *shortonto, *todo_file = rebase_path_todo();
        struct todo_list new_todo = TODO_LIST_INIT;
-       struct strbuf *buf = &todo_list->buf;
+       struct strbuf *buf = &todo_list->buf, buf2 = STRBUF_INIT;
        struct object_id oid = onto->object.oid;
        int res;
 
@@ -5116,17 +5078,22 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
                todo_list_release(&new_todo);
 
                return error(_("nothing to do"));
-       }
-
-       if (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) ||
-           todo_list_check(todo_list, &new_todo)) {
-               fprintf(stderr, _(edit_todo_list_advice));
+       } else if (res == -4) {
                checkout_onto(r, opts, onto_name, &onto->object.oid, orig_head);
                todo_list_release(&new_todo);
 
                return -1;
        }
 
+       /* Expand the commit IDs */
+       todo_list_to_strbuf(r, &new_todo, &buf2, -1, 0);
+       strbuf_swap(&new_todo.buf, &buf2);
+       strbuf_release(&buf2);
+       new_todo.total_nr -= new_todo.nr;
+       if (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) < 0)
+               BUG("invalid todo list after expanding IDs:\n%s",
+                   new_todo.buf.buf);
+
        if (opts->allow_ff && skip_unnecessary_picks(r, &new_todo, &oid)) {
                todo_list_release(&new_todo);
                return error(_("could not skip unnecessary pick commands"));
index 9f9ae291e3c4ad8bc30abc3c68e18e413df9b6f6..393571e89a6ce26f6c7655ce081f47faee9c24e5 100644 (file)
@@ -11,6 +11,7 @@ const char *git_path_commit_editmsg(void);
 const char *git_path_seq_dir(void);
 const char *rebase_path_todo(void);
 const char *rebase_path_todo_backup(void);
+const char *rebase_path_dropped(void);
 
 #define APPEND_SIGNOFF_DEDUP (1u << 0)
 
@@ -155,7 +156,6 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
 
 void todo_list_add_exec_commands(struct todo_list *todo_list,
                                 struct string_list *commands);
-int check_todo_list_from_file(struct repository *r);
 int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,
                    const char *shortrevisions, const char *onto_name,
                    struct commit *onto, const char *orig_head, struct string_list *commands,
@@ -190,9 +190,6 @@ void commit_post_rewrite(struct repository *r,
                         const struct commit *current_head,
                         const struct object_id *new_head);
 
-int prepare_branch_to_be_rebased(struct repository *r, struct replay_opts *opts,
-                                const char *commit);
-
 #define SUMMARY_INITIAL_COMMIT   (1 << 0)
 #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
 void print_commit_summary(struct repository *repo,
diff --git a/setup.c b/setup.c
index e2a479a64fa4076bad8eaf6cf0949d0ae263da9d..12228c0d9c1e3dfb91c474aba8e891f0836dc6d9 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -197,9 +197,26 @@ static void NORETURN die_verify_filename(struct repository *r,
  */
 static int looks_like_pathspec(const char *arg)
 {
-       /* anything with a wildcard character */
-       if (!no_wildcard(arg))
-               return 1;
+       const char *p;
+       int escaped = 0;
+
+       /*
+        * Wildcard characters imply the user is looking to match pathspecs
+        * that aren't in the filesystem. Note that this doesn't include
+        * backslash even though it's a glob special; by itself it doesn't
+        * cause any increase in the match. Likewise ignore backslash-escaped
+        * wildcard characters.
+        */
+       for (p = arg; *p; p++) {
+               if (escaped) {
+                       escaped = 0;
+               } else if (is_glob_special(*p)) {
+                       if (*p == '\\')
+                               escaped = 1;
+                       else
+                               return 1;
+               }
+       }
 
        /* long-form pathspec magic */
        if (starts_with(arg, ":("))
index fcedef97d4e8d34c116b55dd1ea38bf5318105a9..d785de8a851bd6a9965a62cad15285b16fca0231 100644 (file)
@@ -1147,6 +1147,8 @@ static int unpack_loose_short_header(git_zstream *stream,
                                     unsigned char *map, unsigned long mapsize,
                                     void *buffer, unsigned long bufsiz)
 {
+       int ret;
+
        /* Get the data stream */
        memset(stream, 0, sizeof(*stream));
        stream->next_in = map;
@@ -1155,7 +1157,11 @@ static int unpack_loose_short_header(git_zstream *stream,
        stream->avail_out = bufsiz;
 
        git_inflate_init(stream);
-       return git_inflate(stream, 0);
+       obj_read_unlock();
+       ret = git_inflate(stream, 0);
+       obj_read_lock();
+
+       return ret;
 }
 
 int unpack_loose_header(git_zstream *stream,
@@ -1200,7 +1206,9 @@ static int unpack_loose_header_to_strbuf(git_zstream *stream, unsigned char *map
        stream->avail_out = bufsiz;
 
        do {
+               obj_read_unlock();
                status = git_inflate(stream, 0);
+               obj_read_lock();
                strbuf_add(header, buffer, stream->next_out - (unsigned char *)buffer);
                if (memchr(buffer, '\0', stream->next_out - (unsigned char *)buffer))
                        return 0;
@@ -1240,8 +1248,11 @@ static void *unpack_loose_rest(git_zstream *stream,
                 */
                stream->next_out = buf + bytes;
                stream->avail_out = size - bytes;
-               while (status == Z_OK)
+               while (status == Z_OK) {
+                       obj_read_unlock();
                        status = git_inflate(stream, Z_FINISH);
+                       obj_read_lock();
+               }
        }
        if (status == Z_STREAM_END && !stream->avail_in) {
                git_inflate_end(stream);
@@ -1411,17 +1422,41 @@ static int loose_object_info(struct repository *r,
        return (status < 0) ? status : 0;
 }
 
+int obj_read_use_lock = 0;
+pthread_mutex_t obj_read_mutex;
+
+void enable_obj_read_lock(void)
+{
+       if (obj_read_use_lock)
+               return;
+
+       obj_read_use_lock = 1;
+       init_recursive_mutex(&obj_read_mutex);
+}
+
+void disable_obj_read_lock(void)
+{
+       if (!obj_read_use_lock)
+               return;
+
+       obj_read_use_lock = 0;
+       pthread_mutex_destroy(&obj_read_mutex);
+}
+
 int fetch_if_missing = 1;
 
-int oid_object_info_extended(struct repository *r, const struct object_id *oid,
-                            struct object_info *oi, unsigned flags)
+static int do_oid_object_info_extended(struct repository *r,
+                                      const struct object_id *oid,
+                                      struct object_info *oi, unsigned flags)
 {
        static struct object_info blank_oi = OBJECT_INFO_INIT;
+       struct cached_object *co;
        struct pack_entry e;
        int rtype;
        const struct object_id *real = oid;
        int already_retried = 0;
 
+
        if (flags & OBJECT_INFO_LOOKUP_REPLACE)
                real = lookup_replace_object(r, oid);
 
@@ -1431,24 +1466,22 @@ int oid_object_info_extended(struct repository *r, const struct object_id *oid,
        if (!oi)
                oi = &blank_oi;
 
-       if (!(flags & OBJECT_INFO_SKIP_CACHED)) {
-               struct cached_object *co = find_cached_object(real);
-               if (co) {
-                       if (oi->typep)
-                               *(oi->typep) = co->type;
-                       if (oi->sizep)
-                               *(oi->sizep) = co->size;
-                       if (oi->disk_sizep)
-                               *(oi->disk_sizep) = 0;
-                       if (oi->delta_base_sha1)
-                               hashclr(oi->delta_base_sha1);
-                       if (oi->type_name)
-                               strbuf_addstr(oi->type_name, type_name(co->type));
-                       if (oi->contentp)
-                               *oi->contentp = xmemdupz(co->buf, co->size);
-                       oi->whence = OI_CACHED;
-                       return 0;
-               }
+       co = find_cached_object(real);
+       if (co) {
+               if (oi->typep)
+                       *(oi->typep) = co->type;
+               if (oi->sizep)
+                       *(oi->sizep) = co->size;
+               if (oi->disk_sizep)
+                       *(oi->disk_sizep) = 0;
+               if (oi->delta_base_sha1)
+                       hashclr(oi->delta_base_sha1);
+               if (oi->type_name)
+                       strbuf_addstr(oi->type_name, type_name(co->type));
+               if (oi->contentp)
+                       *oi->contentp = xmemdupz(co->buf, co->size);
+               oi->whence = OI_CACHED;
+               return 0;
        }
 
        while (1) {
@@ -1497,7 +1530,7 @@ int oid_object_info_extended(struct repository *r, const struct object_id *oid,
        rtype = packed_object_info(r, e.p, e.offset, oi);
        if (rtype < 0) {
                mark_bad_packed_object(e.p, real->hash);
-               return oid_object_info_extended(r, real, oi, 0);
+               return do_oid_object_info_extended(r, real, oi, 0);
        } else if (oi->whence == OI_PACKED) {
                oi->u.packed.offset = e.offset;
                oi->u.packed.pack = e.p;
@@ -1508,6 +1541,17 @@ int oid_object_info_extended(struct repository *r, const struct object_id *oid,
        return 0;
 }
 
+int oid_object_info_extended(struct repository *r, const struct object_id *oid,
+                            struct object_info *oi, unsigned flags)
+{
+       int ret;
+       obj_read_lock();
+       ret = do_oid_object_info_extended(r, oid, oi, flags);
+       obj_read_unlock();
+       return ret;
+}
+
+
 /* returns enum object_type or negative */
 int oid_object_info(struct repository *r,
                    const struct object_id *oid,
@@ -1580,6 +1624,7 @@ void *read_object_file_extended(struct repository *r,
        if (data)
                return data;
 
+       obj_read_lock();
        if (errno && errno != ENOENT)
                die_errno(_("failed to read object %s"), oid_to_hex(oid));
 
@@ -1595,6 +1640,7 @@ void *read_object_file_extended(struct repository *r,
        if ((p = has_packed_and_bad(r, repl->hash)) != NULL)
                die(_("packed object %s (stored in %s) is corrupt"),
                    oid_to_hex(repl), p->pack_name);
+       obj_read_unlock();
 
        return NULL;
 }
@@ -1936,8 +1982,7 @@ int repo_has_object_file_with_flags(struct repository *r,
 {
        if (!startup_info->have_repository)
                return 0;
-       return oid_object_info_extended(r, oid, NULL,
-                                       flags | OBJECT_INFO_SKIP_CACHED) >= 0;
+       return oid_object_info_extended(r, oid, NULL, flags) >= 0;
 }
 
 int repo_has_object_file(struct repository *r,
index 200eb373ad80fbcc395c9adab0d6b7f72008b99e..f2e24ea666f3be8b94a1624f44c885be617efe8e 100644 (file)
@@ -908,26 +908,21 @@ static int get_oid_basic(struct repository *r, const char *str, int len,
                                real_ref, flags, at_time, nth, oid, NULL,
                                &co_time, &co_tz, &co_cnt)) {
                        if (!len) {
-                               if (starts_with(real_ref, "refs/heads/")) {
-                                       str = real_ref + 11;
-                                       len = strlen(real_ref + 11);
-                               } else {
-                                       /* detached HEAD */
+                               if (!skip_prefix(real_ref, "refs/heads/", &str))
                                        str = "HEAD";
-                                       len = 4;
-                               }
+                               len = strlen(str);
                        }
                        if (at_time) {
                                if (!(flags & GET_OID_QUIETLY)) {
-                                       warning("Log for '%.*s' only goes "
-                                               "back to %s.", len, str,
+                                       warning(_("log for '%.*s' only goes back to %s"),
+                                               len, str,
                                                show_date(co_time, co_tz, DATE_MODE(RFC2822)));
                                }
                        } else {
                                if (flags & GET_OID_QUIETLY) {
                                        exit(128);
                                }
-                               die("Log for '%.*s' only has %d entries.",
+                               die(_("log for '%.*s' only has %d entries"),
                                    len, str, co_cnt);
                        }
                }
@@ -1692,14 +1687,14 @@ static void diagnose_invalid_oid_path(struct repository *r,
                prefix = "";
 
        if (file_exists(filename))
-               die("Path '%s' exists on disk, but not in '%.*s'.",
+               die(_("path '%s' exists on disk, but not in '%.*s'"),
                    filename, object_name_len, object_name);
        if (is_missing_file_error(errno)) {
                char *fullname = xstrfmt("%s%s", prefix, filename);
 
                if (!get_tree_entry(r, tree_oid, fullname, &oid, &mode)) {
-                       die("Path '%s' exists, but not '%s'.\n"
-                           "Did you mean '%.*s:%s' aka '%.*s:./%s'?",
+                       die(_("path '%s' exists, but not '%s'\n"
+                           "hint: Did you mean '%.*s:%s' aka '%.*s:./%s'?"),
                            fullname,
                            filename,
                            object_name_len, object_name,
@@ -1707,7 +1702,7 @@ static void diagnose_invalid_oid_path(struct repository *r,
                            object_name_len, object_name,
                            filename);
                }
-               die("Path '%s' does not exist in '%.*s'",
+               die(_("path '%s' does not exist in '%.*s'"),
                    filename, object_name_len, object_name);
        }
 }
@@ -1735,8 +1730,8 @@ static void diagnose_invalid_index_path(struct repository *r,
                ce = istate->cache[pos];
                if (ce_namelen(ce) == namelen &&
                    !memcmp(ce->name, filename, namelen))
-                       die("Path '%s' is in the index, but not at stage %d.\n"
-                           "Did you mean ':%d:%s'?",
+                       die(_("path '%s' is in the index, but not at stage %d\n"
+                           "hint: Did you mean ':%d:%s'?"),
                            filename, stage,
                            ce_stage(ce), filename);
        }
@@ -1751,17 +1746,17 @@ static void diagnose_invalid_index_path(struct repository *r,
                ce = istate->cache[pos];
                if (ce_namelen(ce) == fullname.len &&
                    !memcmp(ce->name, fullname.buf, fullname.len))
-                       die("Path '%s' is in the index, but not '%s'.\n"
-                           "Did you mean ':%d:%s' aka ':%d:./%s'?",
+                       die(_("path '%s' is in the index, but not '%s'\n"
+                           "hint: Did you mean ':%d:%s' aka ':%d:./%s'?"),
                            fullname.buf, filename,
                            ce_stage(ce), fullname.buf,
                            ce_stage(ce), filename);
        }
 
        if (repo_file_exists(r, filename))
-               die("Path '%s' exists on disk, but not in the index.", filename);
+               die(_("path '%s' exists on disk, but not in the index"), filename);
        if (is_missing_file_error(errno))
-               die("Path '%s' does not exist (neither on disk nor in the index).",
+               die(_("path '%s' does not exist (neither on disk nor in the index)"),
                    filename);
 
        strbuf_release(&fullname);
@@ -1774,7 +1769,7 @@ static char *resolve_relative_path(struct repository *r, const char *rel)
                return NULL;
 
        if (r != the_repository || !is_inside_work_tree())
-               die("relative path syntax can't be used outside working tree.");
+               die(_("relative path syntax can't be used outside working tree"));
 
        /* die() inside prefix_path() if resolved path is outside worktree */
        return prefix_path(startup_info->prefix,
@@ -1912,7 +1907,7 @@ static enum get_oid_result get_oid_with_context_1(struct repository *repo,
                        return ret;
                } else {
                        if (only_to_die)
-                               die("Invalid object name '%.*s'.", len, name);
+                               die(_("invalid object name '%.*s'."), len, name);
                }
        }
        return ret;
index 7bb0ad07e61774b595b6b448a15a5d36a5cc8877..6c5d274126ac6c5e1b796f0a33fb33fba3ebda2a 100644 (file)
@@ -4,7 +4,8 @@
 /**
  * The string_list API offers a data structure and functions to handle
  * sorted and unsorted arrays of strings.  A "sorted" list is one whose
- * entries are sorted by string value in `strcmp()` order.
+ * entries are sorted by string value in the order specified by the `cmp`
+ * member (`strcmp()` by default).
  *
  * The caller:
  *
@@ -209,7 +210,8 @@ struct string_list_item *string_list_append(struct string_list *list, const char
 struct string_list_item *string_list_append_nodup(struct string_list *list, char *string);
 
 /**
- * Sort the list's entries by string value in `strcmp()` order.
+ * Sort the list's entries by string value in order specified by list->cmp
+ * (strcmp() if list->cmp is NULL).
  */
 void string_list_sort(struct string_list *list);
 
index 85064810b20adc7dc02e1d7a776b58437e9595c6..bd5e14ab20aea2684c889dfe564a667b38ef286f 100644 (file)
@@ -674,10 +674,13 @@ static int gitmodules_cb(const char *var, const char *value, void *data)
        return parse_config(var, value, &parameter);
 }
 
-void repo_read_gitmodules(struct repository *repo)
+void repo_read_gitmodules(struct repository *repo, int skip_if_read)
 {
        submodule_cache_check_init(repo);
 
+       if (repo->submodule_cache->gitmodules_read && skip_if_read)
+               return;
+
        if (repo_read_index(repo) < 0)
                return;
 
@@ -703,20 +706,11 @@ void gitmodules_config_oid(const struct object_id *commit_oid)
        the_repository->submodule_cache->gitmodules_read = 1;
 }
 
-static void gitmodules_read_check(struct repository *repo)
-{
-       submodule_cache_check_init(repo);
-
-       /* read the repo's .gitmodules file if it hasn't been already */
-       if (!repo->submodule_cache->gitmodules_read)
-               repo_read_gitmodules(repo);
-}
-
 const struct submodule *submodule_from_name(struct repository *r,
                                            const struct object_id *treeish_name,
                const char *name)
 {
-       gitmodules_read_check(r);
+       repo_read_gitmodules(r, 1);
        return config_from(r->submodule_cache, treeish_name, name, lookup_name);
 }
 
@@ -724,7 +718,7 @@ const struct submodule *submodule_from_path(struct repository *r,
                                            const struct object_id *treeish_name,
                const char *path)
 {
-       gitmodules_read_check(r);
+       repo_read_gitmodules(r, 1);
        return config_from(r->submodule_cache, treeish_name, path, lookup_path);
 }
 
index 42918b55e88e4855ffffa0a35eaa1994db91cb14..c11e22cf509ad1be7531c1740f4701578e6db723 100644 (file)
@@ -61,7 +61,7 @@ int option_fetch_parse_recurse_submodules(const struct option *opt,
                                          const char *arg, int unset);
 int parse_update_recurse_submodules_arg(const char *opt, const char *arg);
 int parse_push_recurse_submodules_arg(const char *opt, const char *arg);
-void repo_read_gitmodules(struct repository *repo);
+void repo_read_gitmodules(struct repository *repo, int skip_if_read);
 void gitmodules_config_oid(const struct object_id *commit_oid);
 
 /**
index 9da7181321f089e8450ec7e39692f7928f5638a8..31f391d7d2541c1498387248e669248de3430b5d 100644 (file)
@@ -82,7 +82,7 @@ int is_staging_gitmodules_ok(struct index_state *istate)
        if ((pos >= 0) && (pos < istate->cache_nr)) {
                struct stat st;
                if (lstat(GITMODULES_FILE, &st) == 0 &&
-                   ie_match_stat(istate, istate->cache[pos], &st, 0) & DATA_CHANGED)
+                   ie_modified(istate, istate->cache[pos], &st, 0) & DATA_CHANGED)
                        return 0;
        }
 
@@ -431,7 +431,7 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt,
        else if (!strcmp(arg, "dirty"))
                diffopt->flags.ignore_dirty_submodules = 1;
        else if (strcmp(arg, "none"))
-               die("bad --ignore-submodules argument: %s", arg);
+               die(_("bad --ignore-submodules argument: %s"), arg);
        /*
         * Please update _git_status() in git-completion.bash when you
         * add new options
@@ -812,9 +812,9 @@ static void collect_changed_submodules_cb(struct diff_queue_struct *q,
                                submodule = submodule_from_name(me->repo,
                                                                commit_oid, name);
                        if (submodule) {
-                               warning("Submodule in commit %s at path: "
+                               warning(_("Submodule in commit %s at path: "
                                        "'%s' collides with a submodule named "
-                                       "the same. Skipping it.",
+                                       "the same. Skipping it."),
                                        oid_to_hex(commit_oid), p->two->path);
                                name = NULL;
                        }
@@ -844,7 +844,7 @@ static void collect_changed_submodules(struct repository *r,
        repo_init_revisions(r, &rev, NULL);
        setup_revisions(argv->argc, argv->argv, &rev, NULL);
        if (prepare_revision_walk(&rev))
-               die("revision walk setup failed");
+               die(_("revision walk setup failed"));
 
        while ((commit = get_revision(&rev))) {
                struct rev_info diff_rev;
@@ -992,7 +992,7 @@ static int submodule_needs_pushing(struct repository *r,
                cp.out = -1;
                cp.dir = path;
                if (start_command(&cp))
-                       die("Could not run 'git rev-list <commits> --not --remotes -n 1' command in submodule %s",
+                       die(_("Could not run 'git rev-list <commits> --not --remotes -n 1' command in submodule %s"),
                                        path);
                if (strbuf_read(&buf, cp.out, the_hash_algo->hexsz + 1))
                        needs_pushing = 1;
@@ -1115,7 +1115,7 @@ static void submodule_push_check(const char *path, const char *head,
         * child process.
         */
        if (run_command(&cp))
-               die("process for submodule '%s' failed", path);
+               die(_("process for submodule '%s' failed"), path);
 }
 
 int push_unpushed_submodules(struct repository *r,
@@ -1155,10 +1155,10 @@ int push_unpushed_submodules(struct repository *r,
        /* Actually push the submodules */
        for (i = 0; i < needs_pushing.nr; i++) {
                const char *path = needs_pushing.items[i].string;
-               fprintf(stderr, "Pushing submodule '%s'\n", path);
+               fprintf(stderr, _("Pushing submodule '%s'\n"), path);
                if (!push_submodule(path, remote, rs,
                                    push_options, dry_run)) {
-                       fprintf(stderr, "Unable to push submodule '%s'\n", path);
+                       fprintf(stderr, _("Unable to push submodule '%s'\n"), path);
                        ret = 0;
                }
        }
@@ -1280,10 +1280,12 @@ struct submodule_parallel_fetch {
        /* Pending fetches by OIDs */
        struct fetch_task **oid_fetch_tasks;
        int oid_fetch_tasks_nr, oid_fetch_tasks_alloc;
+
+       struct strbuf submodules_with_errors;
 };
 #define SPF_INIT {0, ARGV_ARRAY_INIT, NULL, NULL, 0, 0, 0, 0, \
                  STRING_LIST_INIT_DUP, \
-                 NULL, 0, 0}
+                 NULL, 0, 0, STRBUF_INIT}
 
 static int get_fetch_recurse_config(const struct submodule *submodule,
                                    struct submodule_parallel_fetch *spf)
@@ -1448,7 +1450,7 @@ static int get_next_submodule(struct child_process *cp,
                        prepare_submodule_repo_env_in_gitdir(&cp->env_array);
                        cp->git_cmd = 1;
                        if (!spf->quiet)
-                               strbuf_addf(err, "Fetching submodule %s%s\n",
+                               strbuf_addf(err, _("Fetching submodule %s%s\n"),
                                            spf->prefix, ce->name);
                        argv_array_init(&cp->args);
                        argv_array_pushv(&cp->args, spf->args.argv);
@@ -1478,7 +1480,7 @@ static int get_next_submodule(struct child_process *cp,
                            !is_empty_dir(ce->name)) {
                                spf->result = 1;
                                strbuf_addf(err,
-                                           _("Could not access submodule '%s'"),
+                                           _("Could not access submodule '%s'\n"),
                                            ce->name);
                        }
                }
@@ -1547,7 +1549,10 @@ static int fetch_finish(int retvalue, struct strbuf *err,
        struct string_list_item *it;
        struct oid_array *commits;
 
-       if (retvalue)
+       if (!task || !task->sub)
+               BUG("callback cookie bogus");
+
+       if (retvalue) {
                /*
                 * NEEDSWORK: This indicates that the overall fetch
                 * failed, even though there may be a subsequent fetch
@@ -1557,8 +1562,9 @@ static int fetch_finish(int retvalue, struct strbuf *err,
                 */
                spf->result = 1;
 
-       if (!task || !task->sub)
-               BUG("callback cookie bogus");
+               strbuf_addf(&spf->submodules_with_errors, "\t%s\n",
+                           task->sub->name);
+       }
 
        /* Is this the second time we process this submodule? */
        if (task->commits)
@@ -1610,7 +1616,7 @@ int fetch_populated_submodules(struct repository *r,
                goto out;
 
        if (repo_read_index(r) < 0)
-               die("index file corrupt");
+               die(_("index file corrupt"));
 
        argv_array_push(&spf.args, "fetch");
        for (i = 0; i < options->argc; i++)
@@ -1627,6 +1633,11 @@ int fetch_populated_submodules(struct repository *r,
                                   &spf,
                                   "submodule", "parallel/fetch");
 
+       if (spf.submodules_with_errors.len > 0)
+               fprintf(stderr, _("Errors during submodule fetch:\n%s"),
+                       spf.submodules_with_errors.buf);
+
+
        argv_array_clear(&spf.args);
 out:
        free_submodules_oids(&spf.changed_submodule_names);
@@ -1665,7 +1676,7 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked)
        cp.out = -1;
        cp.dir = path;
        if (start_command(&cp))
-               die("Could not run 'git status --porcelain=2' in submodule %s", path);
+               die(_("Could not run 'git status --porcelain=2' in submodule %s"), path);
 
        fp = xfdopen(cp.out, "r");
        while (strbuf_getwholeline(&buf, fp, '\n') != EOF) {
@@ -1706,7 +1717,7 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked)
        fclose(fp);
 
        if (finish_command(&cp) && !ignore_cp_exit_code)
-               die("'git status --porcelain=2' failed in submodule %s", path);
+               die(_("'git status --porcelain=2' failed in submodule %s"), path);
 
        strbuf_release(&buf);
        return dirty_submodule;
@@ -1811,7 +1822,7 @@ out:
 void submodule_unset_core_worktree(const struct submodule *sub)
 {
        char *config_path = xstrfmt("%s/modules/%s/config",
-                                   get_git_common_dir(), sub->name);
+                                   get_git_dir(), sub->name);
 
        if (git_config_set_in_file_gently(config_path, "core.worktree", NULL))
                warning(_("Could not unset core.worktree setting in submodule '%s'"),
@@ -1841,7 +1852,7 @@ static int submodule_has_dirty_index(const struct submodule *sub)
        cp.no_stdout = 1;
        cp.dir = sub->path;
        if (start_command(&cp))
-               die("could not recurse into submodule '%s'", sub->path);
+               die(_("could not recurse into submodule '%s'"), sub->path);
 
        return finish_command(&cp);
 }
@@ -1862,7 +1873,7 @@ static void submodule_reset_index(const char *path)
        argv_array_push(&cp.args, empty_tree_oid_hex());
 
        if (run_command(&cp))
-               die("could not reset submodule index");
+               die(_("could not reset submodule index"));
 }
 
 /**
@@ -1914,7 +1925,7 @@ int submodule_move_head(const char *path,
                                        ABSORB_GITDIR_RECURSE_SUBMODULES);
                } else {
                        char *gitdir = xstrfmt("%s/modules/%s",
-                                   get_git_common_dir(), sub->name);
+                                   get_git_dir(), sub->name);
                        connect_work_tree_and_git_dir(path, gitdir, 0);
                        free(gitdir);
 
@@ -1924,7 +1935,7 @@ int submodule_move_head(const char *path,
 
                if (old_head && (flags & SUBMODULE_MOVE_HEAD_FORCE)) {
                        char *gitdir = xstrfmt("%s/modules/%s",
-                                   get_git_common_dir(), sub->name);
+                                   get_git_dir(), sub->name);
                        connect_work_tree_and_git_dir(path, gitdir, 1);
                        free(gitdir);
                }
index caa125ba9a7954abb8af0fa01d0080a3409eed9e..9afd61e3ca0d19e569c5d31855fbbc79c83c9d4e 100644 (file)
--- a/t/README
+++ b/t/README
@@ -352,8 +352,8 @@ details.
 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_PROTOCOL_VERSION=<n>, when set, overrides the
-'protocol.version' setting to n if it is less than n.
+GIT_TEST_PROTOCOL_VERSION=<n>, when set, makes 'protocol.version'
+default to n.
 
 GIT_TEST_FULL_IN_PACK_ARRAY=<boolean> exercises the uncommon
 pack-objects code path where there are more than 1024 packs even if
index 38bfeebd881ae8930d965536a42305771b066477..fd3303552bec0d3ca243845634179eb35890866a 100755 (executable)
@@ -46,7 +46,7 @@ while (<>) {
        /(?:\$\(seq|^\s*seq\b)/ and err 'seq is not portable (use test_seq)';
        /\bgrep\b.*--file\b/ and err 'grep --file FILE is not portable (use grep -f FILE)';
        /\bexport\s+[A-Za-z0-9_]*=/ and err '"export FOO=bar" is not portable (use FOO=bar && export FOO)';
-       /^\s*([A-Z0-9_]+=(\w+|(["']).*?\3)\s+)+(\w+)/ and exists($func{$4}) and
+       /^\s*([A-Z0-9_]+=(\w*|(["']).*?\3)\s+)+(\w+)/ and exists($func{$4}) and
                err '"FOO=bar shell_func" assignment extends beyond "shell_func"';
        $line = '';
        # this resets our $. for each file
index 2786f47088e975608c839f193527e1e70e84cee9..975f0ac8905125f01ce37be5b7bc862cf058fbcf 100644 (file)
@@ -13,7 +13,7 @@ int cmd__dump_fsmonitor(int ac, const char **av)
                printf("no fsmonitor\n");
                return 0;
        }
-       printf("fsmonitor last update %"PRIuMAX"\n", (uintmax_t)istate->fsmonitor_last_update);
+       printf("fsmonitor last update %s\n", istate->fsmonitor_last_update);
 
        for (i = 0; i < istate->cache_nr; i++)
                printf((istate->cache[i]->ce_flags & CE_FSMONITOR_VALID) ? "+" : "-");
diff --git a/t/helper/test-parse-pathspec-file.c b/t/helper/test-parse-pathspec-file.c
new file mode 100644 (file)
index 0000000..02f4ccf
--- /dev/null
@@ -0,0 +1,33 @@
+#include "test-tool.h"
+#include "parse-options.h"
+#include "pathspec.h"
+#include "gettext.h"
+
+int cmd__parse_pathspec_file(int argc, const char **argv)
+{
+       struct pathspec pathspec;
+       const char *pathspec_from_file = 0;
+       int pathspec_file_nul = 0, i;
+
+       static const char *const usage[] = {
+               "test-tool parse-pathspec-file --pathspec-from-file [--pathspec-file-nul]",
+               NULL
+       };
+
+       struct option options[] = {
+               OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+               OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
+               OPT_END()
+       };
+
+       parse_options(argc, argv, 0, options, usage, 0);
+
+       parse_pathspec_file(&pathspec, 0, 0, 0, pathspec_from_file,
+                           pathspec_file_nul);
+
+       for (i = 0; i < pathspec.nr; i++)
+               printf("%s\n", pathspec.items[i].original);
+
+       clear_pathspec(&pathspec);
+       return 0;
+}
index f20989d4497b596685abfb72bad1f048e41b212c..c9a232d23897b70b051bfd76488ca0e971830b83 100644 (file)
@@ -39,6 +39,7 @@ static struct test_cmd cmds[] = {
        { "oidmap", cmd__oidmap },
        { "online-cpus", cmd__online_cpus },
        { "parse-options", cmd__parse_options },
+       { "parse-pathspec-file", cmd__parse_pathspec_file },
        { "path-utils", cmd__path_utils },
        { "pkt-line", cmd__pkt_line },
        { "prio-queue", cmd__prio_queue },
index 8ed2af71d1b2382def4215862a6b5585876a62cc..c8549fd87f23b6372a41b2ad5d7ba28df3e258c7 100644 (file)
@@ -29,6 +29,7 @@ int cmd__mktemp(int argc, const char **argv);
 int cmd__oidmap(int argc, const char **argv);
 int cmd__online_cpus(int argc, const char **argv);
 int cmd__parse_options(int argc, const char **argv);
+int cmd__parse_pathspec_file(int argc, const char** argv);
 int cmd__path_utils(int argc, const char **argv);
 int cmd__pkt_line(int argc, const char **argv);
 int cmd__prio_queue(int argc, const char **argv);
index 547b9f88e1235a9248f6da9d1fa24dffd7cb0cd3..5aff2abe8b5490156e7745beb5ad01d9cacc0d58 100644 (file)
@@ -175,7 +175,7 @@ stop_and_cleanup_p4d () {
 
 cleanup_git () {
        retry_until_success rm -r "$git"
-       test_must_fail test -d "$git" &&
+       test_path_is_missing "$git" &&
        retry_until_success mkdir "$git"
 }
 
index 8a81a249d0b49638c28e066a2bd757f3ef3f699d..3e440c078d5752620a9ce9dc91f61e9441cb3665 100755 (executable)
@@ -155,7 +155,7 @@ test_expect_success 'pretend we have a fully passing test suite' "
 "
 
 test_expect_success 'pretend we have a partially passing test suite' "
-       test_must_fail run_sub_test_lib_test \
+       run_sub_test_lib_test_err \
                partial-pass '2/3 tests passing' <<-\\EOF &&
        test_expect_success 'passing test #1' 'true'
        test_expect_success 'failing test #2' 'false'
@@ -219,7 +219,7 @@ test_expect_success 'pretend we have fixed one of two known breakages (run in su
 "
 
 test_expect_success 'pretend we have a pass, fail, and known breakage' "
-       test_must_fail run_sub_test_lib_test \
+       run_sub_test_lib_test_err \
                mixed-results1 'mixed results #1' <<-\\EOF &&
        test_expect_success 'passing test' 'true'
        test_expect_success 'failing test' 'false'
@@ -238,7 +238,7 @@ test_expect_success 'pretend we have a pass, fail, and known breakage' "
 "
 
 test_expect_success 'pretend we have a mix of all possible results' "
-       test_must_fail run_sub_test_lib_test \
+       run_sub_test_lib_test_err \
                mixed-results2 'mixed results #2' <<-\\EOF &&
        test_expect_success 'passing test' 'true'
        test_expect_success 'passing test' 'true'
@@ -274,7 +274,7 @@ test_expect_success 'pretend we have a mix of all possible results' "
 "
 
 test_expect_success C_LOCALE_OUTPUT 'test --verbose' '
-       test_must_fail run_sub_test_lib_test \
+       run_sub_test_lib_test_err \
                t1234-verbose "test verbose" --verbose <<-\EOF &&
        test_expect_success "passing test" true
        test_expect_success "test with output" "echo foo"
@@ -301,7 +301,7 @@ test_expect_success C_LOCALE_OUTPUT 'test --verbose' '
 '
 
 test_expect_success 'test --verbose-only' '
-       test_must_fail run_sub_test_lib_test \
+       run_sub_test_lib_test_err \
                t2345-verbose-only-2 "test verbose-only=2" \
                --verbose-only=2 <<-\EOF &&
        test_expect_success "passing test" true
@@ -834,7 +834,7 @@ then
 fi
 
 test_expect_success 'tests clean up even on failures' "
-       test_must_fail run_sub_test_lib_test \
+       run_sub_test_lib_test_err \
                failing-cleanup 'Failing tests with cleanup commands' <<-\\EOF &&
        test_expect_success 'tests clean up even after a failure' '
                touch clean-after-failure &&
@@ -863,7 +863,7 @@ test_expect_success 'tests clean up even on failures' "
 "
 
 test_expect_success 'test_atexit is run' "
-       test_must_fail run_sub_test_lib_test \
+       run_sub_test_lib_test_err \
                atexit-cleanup 'Run atexit commands' -i <<-\\EOF &&
        test_expect_success 'tests clean up even after a failure' '
                > ../../clean-atexit &&
index 71e63d8b509d34fa4b860bac9ba1531e674799d7..b660593c20f8d4012e8c81e2276004708c2a1259 100755 (executable)
@@ -5,19 +5,16 @@ test_description=gitattributes
 . ./test-lib.sh
 
 attr_check () {
-       path="$1" expect="$2"
+       path="$1" expect="$2" git_opts="$3" &&
 
-       git $3 check-attr test -- "$path" >actual 2>err &&
-       echo "$path: test: $2" >expect &&
+       git $git_opts check-attr test -- "$path" >actual 2>err &&
+       echo "$path: test: $expect" >expect &&
        test_cmp expect actual &&
-       test_line_count = 0 err
+       test_must_be_empty err
 }
 
 attr_check_quote () {
-
-       path="$1"
-       quoted_path="$2"
-       expect="$3"
+       path="$1" quoted_path="$2" expect="$3" &&
 
        git check-attr test -- "$path" >actual &&
        echo "\"$quoted_path\": test: $expect" >expect &&
@@ -27,7 +24,7 @@ attr_check_quote () {
 
 test_expect_success 'open-quoted pathname' '
        echo "\"a test=a" >.gitattributes &&
-       test_must_fail attr_check a a
+       attr_check a unspecified
 '
 
 
@@ -112,20 +109,20 @@ test_expect_success 'attribute test' '
 
 test_expect_success 'attribute matching is case sensitive when core.ignorecase=0' '
 
-       test_must_fail attr_check F f "-c core.ignorecase=0" &&
-       test_must_fail attr_check a/F f "-c core.ignorecase=0" &&
-       test_must_fail attr_check a/c/F f "-c core.ignorecase=0" &&
-       test_must_fail attr_check a/G a/g "-c core.ignorecase=0" &&
-       test_must_fail attr_check a/B/g a/b/g "-c core.ignorecase=0" &&
-       test_must_fail attr_check a/b/G a/b/g "-c core.ignorecase=0" &&
-       test_must_fail attr_check a/b/H a/b/h "-c core.ignorecase=0" &&
-       test_must_fail attr_check a/b/D/g "a/b/d/*" "-c core.ignorecase=0" &&
-       test_must_fail attr_check oNoFf unset "-c core.ignorecase=0" &&
-       test_must_fail attr_check oFfOn set "-c core.ignorecase=0" &&
+       attr_check F unspecified "-c core.ignorecase=0" &&
+       attr_check a/F unspecified "-c core.ignorecase=0" &&
+       attr_check a/c/F unspecified "-c core.ignorecase=0" &&
+       attr_check a/G unspecified "-c core.ignorecase=0" &&
+       attr_check a/B/g a/g "-c core.ignorecase=0" &&
+       attr_check a/b/G unspecified "-c core.ignorecase=0" &&
+       attr_check a/b/H unspecified "-c core.ignorecase=0" &&
+       attr_check a/b/D/g a/g "-c core.ignorecase=0" &&
+       attr_check oNoFf unspecified "-c core.ignorecase=0" &&
+       attr_check oFfOn unspecified "-c core.ignorecase=0" &&
        attr_check NO unspecified "-c core.ignorecase=0" &&
-       test_must_fail attr_check a/b/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+       attr_check a/b/D/NO unspecified "-c core.ignorecase=0" &&
        attr_check a/b/d/YES a/b/d/* "-c core.ignorecase=0" &&
-       test_must_fail attr_check a/E/f "A/e/F" "-c core.ignorecase=0"
+       attr_check a/E/f f "-c core.ignorecase=0"
 
 '
 
@@ -149,8 +146,8 @@ test_expect_success 'attribute matching is case insensitive when core.ignorecase
 '
 
 test_expect_success CASE_INSENSITIVE_FS 'additional case insensitivity tests' '
-       test_must_fail attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=0" &&
-       test_must_fail attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+       attr_check a/B/D/g a/g "-c core.ignorecase=0" &&
+       attr_check A/B/D/NO unspecified "-c core.ignorecase=0" &&
        attr_check A/b/h a/b/h "-c core.ignorecase=1" &&
        attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=1" &&
        attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=1"
@@ -244,7 +241,7 @@ EOF
        git check-attr foo -- "a/b/f" >>actual 2>>err &&
        git check-attr foo -- "a/b/c/f" >>actual 2>>err &&
        test_cmp expect actual &&
-       test_line_count = 0 err
+       test_must_be_empty err
 '
 
 test_expect_success '"**" with no slashes test' '
@@ -265,7 +262,7 @@ EOF
        git check-attr foo -- "a/b/f" >>actual 2>>err &&
        git check-attr foo -- "a/b/c/f" >>actual 2>>err &&
        test_cmp expect actual &&
-       test_line_count = 0 err
+       test_must_be_empty err
 '
 
 test_expect_success 'using --git-dir and --work-tree' '
index 854da0ae16f8c437c239a0a4e16f1549d87521a2..b63ba62e5db4760813b04c26414c497b8aadbfbe 100755 (executable)
@@ -159,8 +159,8 @@ test_expect_success 'checkout with autocrlf=input' '
        rm -f tmp one dir/two three &&
        git config core.autocrlf input &&
        git read-tree --reset -u HEAD &&
-       test_must_fail has_cr one &&
-       test_must_fail has_cr dir/two &&
+       ! has_cr one &&
+       ! has_cr dir/two &&
        git update-index -- one dir/two &&
        test "$one" = $(git hash-object --stdin <one) &&
        test "$two" = $(git hash-object --stdin <dir/two) &&
@@ -237,9 +237,9 @@ test_expect_success '.gitattributes says two is binary' '
        git config core.autocrlf true &&
        git read-tree --reset -u HEAD &&
 
-       test_must_fail has_cr dir/two &&
+       ! has_cr dir/two &&
        verbose has_cr one &&
-       test_must_fail has_cr three
+       ! has_cr three
 '
 
 test_expect_success '.gitattributes says two is input' '
@@ -248,7 +248,7 @@ test_expect_success '.gitattributes says two is input' '
        echo "two crlf=input" >.gitattributes &&
        git read-tree --reset -u HEAD &&
 
-       test_must_fail has_cr dir/two
+       ! has_cr dir/two
 '
 
 test_expect_success '.gitattributes says two and three are text' '
@@ -270,7 +270,7 @@ test_expect_success 'in-tree .gitattributes (1)' '
        rm -rf tmp one dir .gitattributes patch.file three &&
        git read-tree --reset -u HEAD &&
 
-       test_must_fail has_cr one &&
+       ! has_cr one &&
        verbose has_cr three
 '
 
@@ -280,7 +280,7 @@ test_expect_success 'in-tree .gitattributes (2)' '
        git read-tree --reset HEAD &&
        git checkout-index -f -q -u -a &&
 
-       test_must_fail has_cr one &&
+       ! has_cr one &&
        verbose has_cr three
 '
 
@@ -291,7 +291,7 @@ test_expect_success 'in-tree .gitattributes (3)' '
        git checkout-index -u .gitattributes &&
        git checkout-index -u one dir/two three &&
 
-       test_must_fail has_cr one &&
+       ! has_cr one &&
        verbose has_cr three
 '
 
@@ -302,7 +302,7 @@ test_expect_success 'in-tree .gitattributes (4)' '
        git checkout-index -u one dir/two three &&
        git checkout-index -u .gitattributes &&
 
-       test_must_fail has_cr one &&
+       ! has_cr one &&
        verbose has_cr three
 '
 
index 705a136ed92c99cda688f5e267204e59b0e532a9..9d7c7fdaa2af1d1f3b6119b4a88827fa5d4c06b6 100755 (executable)
@@ -242,7 +242,7 @@ test_expect_success 'Alias options do not contribute to abbreviation' '
 '
 
 cat >typo.err <<\EOF
-error: did you mean `--boolean` (with two dashes ?)
+error: did you mean `--boolean` (with two dashes)?
 EOF
 
 test_expect_success 'detect possible typos' '
@@ -252,7 +252,7 @@ test_expect_success 'detect possible typos' '
 '
 
 cat >typo.err <<\EOF
-error: did you mean `--ambiguous` (with two dashes ?)
+error: did you mean `--ambiguous` (with two dashes)?
 EOF
 
 test_expect_success 'detect possible typos' '
diff --git a/t/t0067-parse_pathspec_file.sh b/t/t0067-parse_pathspec_file.sh
new file mode 100755 (executable)
index 0000000..7bab49f
--- /dev/null
@@ -0,0 +1,108 @@
+#!/bin/sh
+
+test_description='Test parse_pathspec_file()'
+
+. ./test-lib.sh
+
+test_expect_success 'one item from stdin' '
+       cat >expect <<-\EOF &&
+       fileA.t
+       EOF
+
+       echo fileA.t |
+       test-tool parse-pathspec-file --pathspec-from-file=- >actual &&
+
+       test_cmp expect actual
+'
+
+test_expect_success 'one item from file' '
+       cat >expect <<-\EOF &&
+       fileA.t
+       EOF
+
+       echo fileA.t >list &&
+       test-tool parse-pathspec-file --pathspec-from-file=list >actual &&
+
+       test_cmp expect actual
+'
+
+test_expect_success 'NUL delimiters' '
+       cat >expect <<-\EOF &&
+       fileA.t
+       fileB.t
+       EOF
+
+       printf "fileA.t\0fileB.t\0" |
+       test-tool parse-pathspec-file --pathspec-from-file=- --pathspec-file-nul >actual &&
+
+       test_cmp expect actual
+'
+
+test_expect_success 'LF delimiters' '
+       cat >expect <<-\EOF &&
+       fileA.t
+       fileB.t
+       EOF
+
+       printf "fileA.t\nfileB.t\n" |
+       test-tool parse-pathspec-file --pathspec-from-file=- >actual &&
+
+       test_cmp expect actual
+'
+
+test_expect_success 'no trailing delimiter' '
+       cat >expect <<-\EOF &&
+       fileA.t
+       fileB.t
+       EOF
+
+       printf "fileA.t\nfileB.t" |
+       test-tool parse-pathspec-file --pathspec-from-file=- >actual &&
+
+       test_cmp expect actual
+'
+
+test_expect_success 'CRLF delimiters' '
+       cat >expect <<-\EOF &&
+       fileA.t
+       fileB.t
+       EOF
+
+       printf "fileA.t\r\nfileB.t\r\n" |
+       test-tool parse-pathspec-file --pathspec-from-file=- >actual &&
+
+       test_cmp expect actual
+'
+
+test_expect_success 'quotes' '
+       cat >expect <<-\EOF &&
+       fileA.t
+       EOF
+
+       cat >list <<-\EOF &&
+       "file\101.t"
+       EOF
+
+       test-tool parse-pathspec-file --pathspec-from-file=list >actual &&
+
+       test_cmp expect actual
+'
+
+test_expect_success '--pathspec-file-nul takes quotes literally' '
+       # Note: there is an extra newline because --pathspec-file-nul takes
+       # input \n literally, too
+       cat >expect <<-\EOF &&
+       "file\101.t"
+
+       EOF
+
+       cat >list <<-\EOF &&
+       "file\101.t"
+       EOF
+
+       test-tool parse-pathspec-file --pathspec-from-file=list --pathspec-file-nul >actual &&
+
+       test_cmp expect actual
+'
+
+test_done
index ff7f8f7a1fac6674286233b43c63d62cfcef448f..7d982096fbf0a25262b975f57f625895eac0aa33 100755 (executable)
@@ -12,6 +12,13 @@ list_files() {
        (cd "$1" && printf '%s\n' *)
 }
 
+check_files() {
+       list_files "$1" >actual &&
+       shift &&
+       printf "%s\n" $@ >expect &&
+       test_cmp expect actual
+}
+
 test_expect_success 'setup' '
        git init repo &&
        (
@@ -39,11 +46,11 @@ test_expect_success 'git sparse-checkout list (empty)' '
 
 test_expect_success 'git sparse-checkout list (populated)' '
        test_when_finished rm -f repo/.git/info/sparse-checkout &&
-       cat >repo/.git/info/sparse-checkout <<-EOF &&
-               /folder1/*
-               /deep/
-               **/a
-               !*bin*
+       cat >repo/.git/info/sparse-checkout <<-\EOF &&
+       /folder1/*
+       /deep/
+       **/a
+       !*bin*
        EOF
        cp repo/.git/info/sparse-checkout expect &&
        git -C repo sparse-checkout list >list &&
@@ -52,22 +59,20 @@ test_expect_success 'git sparse-checkout list (populated)' '
 
 test_expect_success 'git sparse-checkout init' '
        git -C repo sparse-checkout init &&
-       cat >expect <<-EOF &&
-               /*
-               !/*/
+       cat >expect <<-\EOF &&
+       /*
+       !/*/
        EOF
        test_cmp expect repo/.git/info/sparse-checkout &&
        test_cmp_config -C repo true core.sparsecheckout &&
-       list_files repo >dir  &&
-       echo a >expect &&
-       test_cmp expect dir
+       check_files repo a
 '
 
 test_expect_success 'git sparse-checkout list after init' '
        git -C repo sparse-checkout list >actual &&
-       cat >expect <<-EOF &&
-               /*
-               !/*/
+       cat >expect <<-\EOF &&
+       /*
+       !/*/
        EOF
        test_cmp expect actual
 '
@@ -75,32 +80,24 @@ test_expect_success 'git sparse-checkout list after init' '
 test_expect_success 'init with existing sparse-checkout' '
        echo "*folder*" >> repo/.git/info/sparse-checkout &&
        git -C repo sparse-checkout init &&
-       cat >expect <<-EOF &&
-               /*
-               !/*/
-               *folder*
+       cat >expect <<-\EOF &&
+       /*
+       !/*/
+       *folder*
        EOF
        test_cmp expect repo/.git/info/sparse-checkout &&
-       list_files repo >dir  &&
-       cat >expect <<-EOF &&
-               a
-               folder1
-               folder2
-       EOF
-       test_cmp expect dir
+       check_files repo a folder1 folder2
 '
 
 test_expect_success 'clone --sparse' '
-       git clone --sparse repo clone &&
+       git clone --sparse "file://$(pwd)/repo" clone &&
        git -C clone sparse-checkout list >actual &&
-       cat >expect <<-EOF &&
-               /*
-               !/*/
+       cat >expect <<-\EOF &&
+       /*
+       !/*/
        EOF
        test_cmp expect actual &&
-       list_files clone >dir &&
-       echo a >expect &&
-       test_cmp expect dir
+       check_files clone a
 '
 
 test_expect_success 'set enables config' '
@@ -119,41 +116,29 @@ test_expect_success 'set enables config' '
 
 test_expect_success 'set sparse-checkout using builtin' '
        git -C repo sparse-checkout set "/*" "!/*/" "*folder*" &&
-       cat >expect <<-EOF &&
-               /*
-               !/*/
-               *folder*
+       cat >expect <<-\EOF &&
+       /*
+       !/*/
+       *folder*
        EOF
        git -C repo sparse-checkout list >actual &&
        test_cmp expect actual &&
        test_cmp expect repo/.git/info/sparse-checkout &&
-       list_files repo >dir  &&
-       cat >expect <<-EOF &&
-               a
-               folder1
-               folder2
-       EOF
-       test_cmp expect dir
+       check_files repo a folder1 folder2
 '
 
 test_expect_success 'set sparse-checkout using --stdin' '
-       cat >expect <<-EOF &&
-               /*
-               !/*/
-               /folder1/
-               /folder2/
+       cat >expect <<-\EOF &&
+       /*
+       !/*/
+       /folder1/
+       /folder2/
        EOF
        git -C repo sparse-checkout set --stdin <expect &&
        git -C repo sparse-checkout list >actual &&
        test_cmp expect actual &&
        test_cmp expect repo/.git/info/sparse-checkout &&
-       list_files repo >dir  &&
-       cat >expect <<-EOF &&
-               a
-               folder1
-               folder2
-       EOF
-       test_cmp expect dir
+       check_files repo "a folder1 folder2"
 '
 
 test_expect_success 'cone mode: match patterns' '
@@ -162,13 +147,7 @@ test_expect_success 'cone mode: match patterns' '
        git -C repo read-tree -mu HEAD 2>err &&
        test_i18ngrep ! "disabling cone patterns" err &&
        git -C repo reset --hard &&
-       list_files repo >dir  &&
-       cat >expect <<-EOF &&
-               a
-               folder1
-               folder2
-       EOF
-       test_cmp expect dir
+       check_files repo a folder1 folder2
 '
 
 test_expect_success 'cone mode: warn on bad pattern' '
@@ -185,14 +164,7 @@ test_expect_success 'sparse-checkout disable' '
        test_path_is_file repo/.git/info/sparse-checkout &&
        git -C repo config --list >config &&
        test_must_fail git config core.sparseCheckout &&
-       list_files repo >dir &&
-       cat >expect <<-EOF &&
-               a
-               deep
-               folder1
-               folder2
-       EOF
-       test_cmp expect dir
+       check_files repo a deep folder1 folder2
 '
 
 test_expect_success 'cone mode: init and set' '
@@ -204,52 +176,31 @@ test_expect_success 'cone mode: init and set' '
        test_cmp expect dir &&
        git -C repo sparse-checkout set deep/deeper1/deepest/ 2>err &&
        test_must_be_empty err &&
-       list_files repo >dir  &&
-       cat >expect <<-EOF &&
-               a
-               deep
-       EOF
-       test_cmp expect dir &&
-       list_files repo/deep >dir  &&
-       cat >expect <<-EOF &&
-               a
-               deeper1
-       EOF
-       test_cmp expect dir &&
-       list_files repo/deep/deeper1 >dir  &&
-       cat >expect <<-EOF &&
-               a
-               deepest
-       EOF
-       test_cmp expect dir &&
-       cat >expect <<-EOF &&
-               /*
-               !/*/
-               /deep/
-               !/deep/*/
-               /deep/deeper1/
-               !/deep/deeper1/*/
-               /deep/deeper1/deepest/
+       check_files repo a deep &&
+       check_files repo/deep a deeper1 &&
+       check_files repo/deep/deeper1 a deepest &&
+       cat >expect <<-\EOF &&
+       /*
+       !/*/
+       /deep/
+       !/deep/*/
+       /deep/deeper1/
+       !/deep/deeper1/*/
+       /deep/deeper1/deepest/
        EOF
        test_cmp expect repo/.git/info/sparse-checkout &&
-       git -C repo sparse-checkout set --stdin 2>err <<-EOF &&
-               folder1
-               folder2
+       git -C repo sparse-checkout set --stdin 2>err <<-\EOF &&
+       folder1
+       folder2
        EOF
        test_must_be_empty err &&
-       cat >expect <<-EOF &&
-               a
-               folder1
-               folder2
-       EOF
-       list_files repo >dir &&
-       test_cmp expect dir
+       check_files repo a folder1 folder2
 '
 
 test_expect_success 'cone mode: list' '
-       cat >expect <<-EOF &&
-               folder1
-               folder2
+       cat >expect <<-\EOF &&
+       folder1
+       folder2
        EOF
        git -C repo sparse-checkout set --stdin <expect &&
        git -C repo sparse-checkout list >actual 2>err &&
@@ -260,10 +211,10 @@ test_expect_success 'cone mode: list' '
 test_expect_success 'cone mode: set with nested folders' '
        git -C repo sparse-checkout set deep deep/deeper1/deepest 2>err &&
        test_line_count = 0 err &&
-       cat >expect <<-EOF &&
-               /*
-               !/*/
-               /deep/
+       cat >expect <<-\EOF &&
+       /*
+       !/*/
+       /deep/
        EOF
        test_cmp repo/.git/info/sparse-checkout expect
 '
@@ -275,13 +226,7 @@ test_expect_success 'revert to old sparse-checkout on bad update' '
        test_must_fail git -C repo sparse-checkout set deep/deeper1 2>err &&
        test_i18ngrep "cannot set sparse-checkout patterns" err &&
        test_cmp repo/.git/info/sparse-checkout expect &&
-       list_files repo/deep >dir &&
-       cat >expect <<-EOF &&
-               a
-               deeper1
-               deeper2
-       EOF
-       test_cmp dir expect
+       check_files repo/deep a deeper1 deeper2
 '
 
 test_expect_success 'revert to old sparse-checkout on empty update' '
@@ -326,18 +271,13 @@ test_expect_success 'sparse-checkout (init|set|disable) fails with dirty status'
 test_expect_success 'cone mode: set with core.ignoreCase=true' '
        git -C repo sparse-checkout init --cone &&
        git -C repo -c core.ignoreCase=true sparse-checkout set folder1 &&
-       cat >expect <<-EOF &&
-               /*
-               !/*/
-               /folder1/
+       cat >expect <<-\EOF &&
+       /*
+       !/*/
+       /folder1/
        EOF
        test_cmp expect repo/.git/info/sparse-checkout &&
-       list_files repo >dir &&
-       cat >expect <<-EOF &&
-               a
-               folder1
-       EOF
-       test_cmp expect dir
+       check_files repo a folder1
 '
 
 test_expect_success 'interaction with submodules' '
@@ -351,21 +291,151 @@ test_expect_success 'interaction with submodules' '
                git sparse-checkout init --cone &&
                git sparse-checkout set folder1
        ) &&
-       list_files super >dir &&
+       check_files super a folder1 modules &&
+       check_files super/modules/child a deep folder1 folder2
+'
+
+test_expect_success 'different sparse-checkouts with worktrees' '
+       git -C repo worktree add --detach ../worktree &&
+       check_files worktree "a deep folder1 folder2" &&
+       git -C worktree sparse-checkout init --cone &&
+       git -C repo sparse-checkout set folder1 &&
+       git -C worktree sparse-checkout set deep/deeper1 &&
+       check_files repo a folder1 &&
+       check_files worktree a deep
+'
+
+test_expect_success 'set using filename keeps file on-disk' '
+       git -C repo sparse-checkout set a deep &&
        cat >expect <<-\EOF &&
-               a
-               folder1
-               modules
+       /*
+       !/*/
+       /a/
+       /deep/
        EOF
-       test_cmp expect dir &&
-       list_files super/modules/child >dir &&
+       test_cmp expect repo/.git/info/sparse-checkout &&
+       check_files repo a deep
+'
+
+check_read_tree_errors () {
+       REPO=$1
+       FILES=$2
+       ERRORS=$3
+       git -C $REPO -c core.sparseCheckoutCone=false read-tree -mu HEAD 2>err &&
+       test_must_be_empty err &&
+       check_files $REPO "$FILES" &&
+       git -C $REPO read-tree -mu HEAD 2>err &&
+       if test -z "$ERRORS"
+       then
+               test_must_be_empty err
+       else
+               test_i18ngrep "$ERRORS" err
+       fi &&
+       check_files $REPO $FILES
+}
+
+test_expect_success 'pattern-checks: /A/**' '
+       cat >repo/.git/info/sparse-checkout <<-\EOF &&
+       /*
+       !/*/
+       /folder1/**
+       EOF
+       check_read_tree_errors repo "a folder1" "disabling cone pattern matching"
+'
+
+test_expect_success 'pattern-checks: /A/**/B/' '
+       cat >repo/.git/info/sparse-checkout <<-\EOF &&
+       /*
+       !/*/
+       /deep/**/deepest
+       EOF
+       check_read_tree_errors repo "a deep" "disabling cone pattern matching" &&
+       check_files repo/deep "deeper1" &&
+       check_files repo/deep/deeper1 "deepest"
+'
+
+test_expect_success 'pattern-checks: too short' '
+       cat >repo/.git/info/sparse-checkout <<-\EOF &&
+       /*
+       !/*/
+       /a
+       EOF
+       check_read_tree_errors repo "a" "disabling cone pattern matching"
+'
+
+test_expect_success 'pattern-checks: trailing "*"' '
+       cat >repo/.git/info/sparse-checkout <<-\EOF &&
+       /*
+       !/*/
+       /a*
+       EOF
+       check_read_tree_errors repo "a" "disabling cone pattern matching"
+'
+
+test_expect_success 'pattern-checks: starting "*"' '
+       cat >repo/.git/info/sparse-checkout <<-\EOF &&
+       /*
+       !/*/
+       *eep/
+       EOF
+       check_read_tree_errors repo "a deep" "disabling cone pattern matching"
+'
+
+test_expect_success 'pattern-checks: contained glob characters' '
+       for c in "[a]" "\\" "?" "*"
+       do
+               cat >repo/.git/info/sparse-checkout <<-EOF &&
+               /*
+               !/*/
+               something$c-else/
+               EOF
+               check_read_tree_errors repo "a" "disabling cone pattern matching"
+       done
+'
+
+test_expect_success BSLASHPSPEC 'pattern-checks: escaped characters' '
+       git clone repo escaped &&
+       TREEOID=$(git -C escaped rev-parse HEAD:folder1) &&
+       NEWTREE=$(git -C escaped mktree <<-EOF
+       $(git -C escaped ls-tree HEAD)
+       040000 tree $TREEOID    zbad\\dir
+       040000 tree $TREEOID    zdoes*exist
+       040000 tree $TREEOID    zglob[!a]?
+       EOF
+       ) &&
+       COMMIT=$(git -C escaped commit-tree $NEWTREE -p HEAD) &&
+       git -C escaped reset --hard $COMMIT &&
+       check_files escaped "a deep folder1 folder2 zbad\\dir zdoes*exist" zglob[!a]? &&
+       git -C escaped sparse-checkout init --cone &&
+       git -C escaped sparse-checkout set zbad\\dir/bogus "zdoes*not*exist" "zdoes*exist" "zglob[!a]?" &&
+       cat >expect <<-\EOF &&
+       /*
+       !/*/
+       /zbad\\dir/
+       !/zbad\\dir/*/
+       /zbad\\dir/bogus/
+       /zdoes\*exist/
+       /zdoes\*not\*exist/
+       /zglob\[!a]\?/
+       EOF
+       test_cmp expect escaped/.git/info/sparse-checkout &&
+       check_read_tree_errors escaped "a zbad\\dir zdoes*exist zglob[!a]?" &&
+       git -C escaped ls-tree -d --name-only HEAD >list-expect &&
+       git -C escaped sparse-checkout set --stdin <list-expect &&
        cat >expect <<-\EOF &&
-               a
-               deep
-               folder1
-               folder2
+       /*
+       !/*/
+       /deep/
+       /folder1/
+       /folder2/
+       /zbad\\dir/
+       /zdoes\*exist/
+       /zglob\[!a]\?/
        EOF
-       test_cmp expect dir
+       test_cmp expect escaped/.git/info/sparse-checkout &&
+       check_files escaped "a deep folder1 folder2 zbad\\dir zdoes*exist" zglob[!a]? &&
+       git -C escaped sparse-checkout list >list-actual &&
+       test_cmp list-expect list-actual
 '
 
 test_done
index 21e139a313bed1d91121044f64a5e7ce5078002c..dd87b43be1a6b3331a2ddee4b7ad87ab1c9041bd 100755 (executable)
@@ -153,7 +153,7 @@ test_expect_success 'Checking attributes in both XDG and local attributes files'
 
 
 test_expect_success 'Checking attributes in a non-XDG global attributes file' '
-       test_might_fail rm .gitattributes &&
+       rm -f .gitattributes &&
        echo "f attr_f=test" >"$HOME"/my_gitattributes &&
        git config core.attributesfile "$HOME"/my_gitattributes &&
        echo "f: attr_f: test" >expected &&
@@ -165,7 +165,7 @@ test_expect_success 'Checking attributes in a non-XDG global attributes file' '
 test_expect_success 'write: xdg file exists and ~/.gitconfig doesn'\''t' '
        mkdir -p "$HOME"/.config/git &&
        >"$HOME"/.config/git/config &&
-       test_might_fail rm "$HOME"/.gitconfig &&
+       rm -f "$HOME"/.gitconfig &&
        git config --global user.name "write_config" &&
        echo "[user]" >expected &&
        echo "  name = write_config" >>expected &&
@@ -183,8 +183,8 @@ test_expect_success 'write: xdg file exists and ~/.gitconfig exists' '
 
 
 test_expect_success 'write: ~/.config/git/ exists and config file doesn'\''t' '
-       test_might_fail rm "$HOME"/.gitconfig &&
-       test_might_fail rm "$HOME"/.config/git/config &&
+       rm -f "$HOME"/.gitconfig &&
+       rm -f "$HOME"/.config/git/config &&
        git config --global user.name "write_gitconfig" &&
        echo "[user]" >expected &&
        echo "  name = write_gitconfig" >>expected &&
index 37dc689d8c98fd776895e1ce6d18faee092001d3..002e6d3388e242c56800da6b9640819c90ed5615 100755 (executable)
@@ -74,7 +74,7 @@ test_expect_success 'can parse blob ending with CR' '
 '
 
 test_expect_success 'config --blob outside of a repository is an error' '
-       test_must_fail nongit git config --blob=foo --list
+       nongit test_must_fail git config --blob=foo --list
 '
 
 test_done
index b815cdd1b88052bdf98a0b371a25290461e58e02..a6224ef65fe90a32fc7a603db25ef4d923cc299a 100755 (executable)
@@ -361,55 +361,67 @@ ld="Thu, 26 May 2005 18:43:00 -0500"
 test_expect_success 'Query "master@{May 25 2005}" (before history)' '
        test_when_finished "rm -f o e" &&
        git rev-parse --verify "master@{May 25 2005}" >o 2>e &&
-       test $C = $(cat o) &&
-       test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"
+       echo "$C" >expect &&
+       test_cmp expect o &&
+       echo "warning: log for '\''master'\'' only goes back to $ed" >expect &&
+       test_i18ncmp expect e
 '
 test_expect_success 'Query master@{2005-05-25} (before history)' '
        test_when_finished "rm -f o e" &&
        git rev-parse --verify master@{2005-05-25} >o 2>e &&
-       test $C = $(cat o) &&
-       test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"
+       echo "$C" >expect &&
+       test_cmp expect o &&
+       echo "warning: log for '\''master'\'' only goes back to $ed" >expect &&
+       test_i18ncmp expect e
 '
 test_expect_success 'Query "master@{May 26 2005 23:31:59}" (1 second before history)' '
        test_when_finished "rm -f o e" &&
        git rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
-       test $C = $(cat o) &&
-       test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"
+       echo "$C" >expect &&
+       test_cmp expect o &&
+       echo "warning: log for '\''master'\'' only goes back to $ed" >expect &&
+       test_i18ncmp expect e
 '
 test_expect_success 'Query "master@{May 26 2005 23:32:00}" (exactly history start)' '
        test_when_finished "rm -f o e" &&
        git rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
-       test $C = $(cat o) &&
+       echo "$C" >expect &&
+       test_cmp expect o &&
        test_must_be_empty e
 '
 test_expect_success 'Query "master@{May 26 2005 23:32:30}" (first non-creation change)' '
        test_when_finished "rm -f o e" &&
        git rev-parse --verify "master@{May 26 2005 23:32:30}" >o 2>e &&
-       test $A = $(cat o) &&
+       echo "$A" >expect &&
+       test_cmp expect o &&
        test_must_be_empty e
 '
 test_expect_success 'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' '
        test_when_finished "rm -f o e" &&
        git rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
-       test $B = $(cat o) &&
+       echo "$B" >expect &&
+       test_cmp expect o &&
        test_i18ngrep -F "warning: log for ref $m has gap after $gd" e
 '
 test_expect_success 'Query "master@{2005-05-26 23:38:00}" (middle of history)' '
        test_when_finished "rm -f o e" &&
        git rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
-       test $Z = $(cat o) &&
+       echo "$Z" >expect &&
+       test_cmp expect o &&
        test_must_be_empty e
 '
 test_expect_success 'Query "master@{2005-05-26 23:43:00}" (exact end of history)' '
        test_when_finished "rm -f o e" &&
        git rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
-       test $E = $(cat o) &&
+       echo "$E" >expect &&
+       test_cmp expect o &&
        test_must_be_empty e
 '
 test_expect_success 'Query "master@{2005-05-28}" (past end of history)' '
        test_when_finished "rm -f o e" &&
        git rev-parse --verify "master@{2005-05-28}" >o 2>e &&
-       test $D = $(cat o) &&
+       echo "$D" >expect &&
+       test_cmp expect o &&
        test_i18ngrep -F "warning: log for ref $m unexpectedly ended on $ld" e
 '
 
index e5cb8a252dd1a7d8e65eece2fe4ee5e21d748b90..be12fb63506e2a8c242e876526dbe1d60e105f49 100755 (executable)
@@ -8,7 +8,7 @@ test_description='avoid rewriting packed-refs unnecessarily'
 # shouldn't upset readers, and it should be omitted if the file is
 # ever rewritten.
 mark_packed_refs () {
-       sed -e "s/^\(#.*\)/\1 t1409 /" <.git/packed-refs >.git/packed-refs.new &&
+       sed -e "s/^\(#.*\)/\1 t1409 /" .git/packed-refs >.git/packed-refs.new &&
        mv .git/packed-refs.new .git/packed-refs
 }
 
@@ -27,15 +27,15 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'do not create packed-refs file gratuitously' '
-       test_must_fail test -f .git/packed-refs &&
+       test_path_is_missing .git/packed-refs &&
        git update-ref refs/heads/foo $A &&
-       test_must_fail test -f .git/packed-refs &&
+       test_path_is_missing .git/packed-refs &&
        git update-ref refs/heads/foo $B &&
-       test_must_fail test -f .git/packed-refs &&
+       test_path_is_missing .git/packed-refs &&
        git update-ref refs/heads/foo $C $B &&
-       test_must_fail test -f .git/packed-refs &&
+       test_path_is_missing .git/packed-refs &&
        git update-ref -d refs/heads/foo &&
-       test_must_fail test -f .git/packed-refs
+       test_path_is_missing .git/packed-refs
 '
 
 test_expect_success 'check that marking the packed-refs file works' '
@@ -46,7 +46,7 @@ test_expect_success 'check that marking the packed-refs file works' '
        git for-each-ref >actual &&
        test_cmp expected actual &&
        git pack-refs --all &&
-       test_must_fail check_packed_refs_marked &&
+       ! check_packed_refs_marked &&
        git for-each-ref >actual2 &&
        test_cmp expected actual2
 '
@@ -80,7 +80,7 @@ test_expect_success 'touch packed-refs on delete of packed' '
        git pack-refs --all &&
        mark_packed_refs &&
        git update-ref -d refs/heads/packed-delete &&
-       test_must_fail check_packed_refs_marked
+       ! check_packed_refs_marked
 '
 
 test_expect_success 'leave packed-refs untouched on update of loose' '
index 3498d3d55e9e18d19c9a9accaa1637f338c1c4bc..b75558040ffd6ee9174ed37bd2ca746212b57013 100755 (executable)
@@ -350,7 +350,7 @@ test_expect_success 'Multi-worktree setup' '
        mkdir work &&
        mkdir -p repo.git/repos/foo &&
        cp repo.git/HEAD repo.git/index repo.git/repos/foo &&
-       test_might_fail cp repo.git/sharedindex.* repo.git/repos/foo &&
+       { cp repo.git/sharedindex.* repo.git/repos/foo || :; } &&
        sane_unset GIT_DIR GIT_CONFIG GIT_WORK_TREE
 '
 
index 6d951ca015912062fba4a938fdb560c10641ca6d..52edcbdcc3272ed34854017680430e14796a04a1 100755 (executable)
@@ -9,10 +9,10 @@ exec </dev/null
 test_did_you_mean ()
 {
        cat >expected <<-EOF &&
-       fatal: Path '$2$3' $4, but not ${5:-$SQ$3$SQ}.
-       Did you mean '$1:$2$3'${2:+ aka $SQ$1:./$3$SQ}?
+       fatal: path '$2$3' $4, but not ${5:-$SQ$3$SQ}
+       hint: Did you mean '$1:$2$3'${2:+ aka $SQ$1:./$3$SQ}?
        EOF
-       test_cmp expected error
+       test_i18ncmp expected error
 }
 
 HASH_file=
@@ -103,53 +103,53 @@ test_expect_success 'correct relative file objects (6)' '
 
 test_expect_success 'incorrect revision id' '
        test_must_fail git rev-parse foobar:file.txt 2>error &&
-       grep "Invalid object name '"'"'foobar'"'"'." error &&
-       test_must_fail git rev-parse foobar 2> error &&
+       test_i18ngrep "invalid object name .foobar." error &&
+       test_must_fail git rev-parse foobar 2>error &&
        test_i18ngrep "unknown revision or path not in the working tree." error
 '
 
 test_expect_success 'incorrect file in sha1:path' '
-       test_must_fail git rev-parse HEAD:nothing.txt 2> error &&
-       grep "fatal: Path '"'"'nothing.txt'"'"' does not exist in '"'"'HEAD'"'"'" error &&
-       test_must_fail git rev-parse HEAD:index-only.txt 2> error &&
-       grep "fatal: Path '"'"'index-only.txt'"'"' exists on disk, but not in '"'"'HEAD'"'"'." error &&
+       test_must_fail git rev-parse HEAD:nothing.txt 2>error &&
+       test_i18ngrep "path .nothing.txt. does not exist in .HEAD." error &&
+       test_must_fail git rev-parse HEAD:index-only.txt 2>error &&
+       test_i18ngrep "path .index-only.txt. exists on disk, but not in .HEAD." error &&
        (cd subdir &&
-        test_must_fail git rev-parse HEAD:file2.txt 2> error &&
+        test_must_fail git rev-parse HEAD:file2.txt 2>error &&
         test_did_you_mean HEAD subdir/ file2.txt exists )
 '
 
 test_expect_success 'incorrect file in :path and :N:path' '
-       test_must_fail git rev-parse :nothing.txt 2> error &&
-       grep "fatal: Path '"'"'nothing.txt'"'"' does not exist (neither on disk nor in the index)." error &&
-       test_must_fail git rev-parse :1:nothing.txt 2> error &&
-       grep "Path '"'"'nothing.txt'"'"' does not exist (neither on disk nor in the index)." error &&
-       test_must_fail git rev-parse :1:file.txt 2> error &&
+       test_must_fail git rev-parse :nothing.txt 2>error &&
+       test_i18ngrep "path .nothing.txt. does not exist (neither on disk nor in the index)" error &&
+       test_must_fail git rev-parse :1:nothing.txt 2>error &&
+       test_i18ngrep "path .nothing.txt. does not exist (neither on disk nor in the index)" error &&
+       test_must_fail git rev-parse :1:file.txt 2>error &&
        test_did_you_mean ":0" "" file.txt "is in the index" "at stage 1" &&
        (cd subdir &&
-        test_must_fail git rev-parse :1:file.txt 2> error &&
+        test_must_fail git rev-parse :1:file.txt 2>error &&
         test_did_you_mean ":0" "" file.txt "is in the index" "at stage 1" &&
-        test_must_fail git rev-parse :file2.txt 2> error &&
+        test_must_fail git rev-parse :file2.txt 2>error &&
         test_did_you_mean ":0" subdir/ file2.txt "is in the index" &&
-        test_must_fail git rev-parse :2:file2.txt 2> error &&
+        test_must_fail git rev-parse :2:file2.txt 2>error &&
         test_did_you_mean :0 subdir/ file2.txt "is in the index") &&
-       test_must_fail git rev-parse :disk-only.txt 2> error &&
-       grep "fatal: Path '"'"'disk-only.txt'"'"' exists on disk, but not in the index." error
+       test_must_fail git rev-parse :disk-only.txt 2>error &&
+       test_i18ngrep "path .disk-only.txt. exists on disk, but not in the index" error
 '
 
 test_expect_success 'invalid @{n} reference' '
        test_must_fail git rev-parse master@{99999} >output 2>error &&
        test_must_be_empty output &&
-       grep "fatal: Log for [^ ]* only has [0-9][0-9]* entries." error  &&
+       test_i18ngrep "log for [^ ]* only has [0-9][0-9]* entries" error  &&
        test_must_fail git rev-parse --verify master@{99999} >output 2>error &&
        test_must_be_empty output &&
-       grep "fatal: Log for [^ ]* only has [0-9][0-9]* entries." error
+       test_i18ngrep "log for [^ ]* only has [0-9][0-9]* entries" error
 '
 
 test_expect_success 'relative path not found' '
        (
                cd subdir &&
                test_must_fail git rev-parse HEAD:./nonexistent.txt 2>error &&
-               grep subdir/nonexistent.txt error
+               test_i18ngrep subdir/nonexistent.txt error
        )
 '
 
@@ -162,7 +162,7 @@ test_expect_success 'relative path outside worktree' '
 test_expect_success 'relative path when cwd is outside worktree' '
        test_must_fail git --git-dir=.git --work-tree=subdir rev-parse HEAD:./file.txt >output 2>error &&
        test_must_be_empty output &&
-       grep "relative path syntax can.t be used outside working tree." error
+       test_i18ngrep "relative path syntax can.t be used outside working tree" error
 '
 
 test_expect_success '<commit>:file correctly diagnosed after a pathname' '
@@ -222,4 +222,18 @@ test_expect_success 'reject Nth ancestor if N is too high' '
        test_must_fail git rev-parse HEAD~100000000000000000000000000000000
 '
 
+test_expect_success 'pathspecs with wildcards are not ambiguous' '
+       echo "*.c" >expect &&
+       git rev-parse "*.c" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'backslash does not trigger wildcard rule' '
+       test_must_fail git rev-parse "foo\\bar"
+'
+
+test_expect_success 'escaped char does not trigger wildcard rule' '
+       test_must_fail git rev-parse "foo\\*bar"
+'
+
 test_done
index 8b4cf8a6e3c2c9363210cd7a2bd619acd9e1c0b7..dfc0d96d8a8a834743781a08a8ea1c5f9dd63bda 100755 (executable)
@@ -28,14 +28,9 @@ test_expect_success 'setup' '
        )
 '
 
-full_name () {
-       (cd clone &&
-        git rev-parse --symbolic-full-name "$@")
-}
-
 commit_subject () {
        (cd clone &&
-        git show -s --pretty=format:%s "$@")
+        git show -s --pretty=tformat:%s "$@")
 }
 
 error_message () {
@@ -44,63 +39,78 @@ error_message () {
 }
 
 test_expect_success '@{upstream} resolves to correct full name' '
-       test refs/remotes/origin/master = "$(full_name @{upstream})" &&
-       test refs/remotes/origin/master = "$(full_name @{UPSTREAM})" &&
-       test refs/remotes/origin/master = "$(full_name @{UpSTReam})"
+       echo refs/remotes/origin/master >expect &&
+       git -C clone rev-parse --symbolic-full-name @{upstream} >actual &&
+       test_cmp expect actual &&
+       git -C clone rev-parse --symbolic-full-name @{UPSTREAM} >actual &&
+       test_cmp expect actual &&
+       git -C clone rev-parse --symbolic-full-name @{UpSTReam} >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success '@{u} resolves to correct full name' '
-       test refs/remotes/origin/master = "$(full_name @{u})" &&
-       test refs/remotes/origin/master = "$(full_name @{U})"
+       echo refs/remotes/origin/master >expect &&
+       git -C clone rev-parse --symbolic-full-name @{u} >actual &&
+       test_cmp expect actual &&
+       git -C clone rev-parse --symbolic-full-name @{U} >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success 'my-side@{upstream} resolves to correct full name' '
-       test refs/remotes/origin/side = "$(full_name my-side@{u})"
+       echo refs/remotes/origin/side >expect &&
+       git -C clone rev-parse --symbolic-full-name my-side@{u} >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success 'upstream of branch with @ in middle' '
-       full_name fun@ny@{u} >actual &&
+       git -C clone rev-parse --symbolic-full-name fun@ny@{u} >actual &&
        echo refs/remotes/origin/side >expect &&
        test_cmp expect actual &&
-       full_name fun@ny@{U} >actual &&
+       git -C clone rev-parse --symbolic-full-name fun@ny@{U} >actual &&
        test_cmp expect actual
 '
 
 test_expect_success 'upstream of branch with @ at start' '
-       full_name @funny@{u} >actual &&
+       git -C clone rev-parse --symbolic-full-name @funny@{u} >actual &&
        echo refs/remotes/origin/side >expect &&
        test_cmp expect actual
 '
 
 test_expect_success 'upstream of branch with @ at end' '
-       full_name funny@@{u} >actual &&
+       git -C clone rev-parse --symbolic-full-name funny@@{u} >actual &&
        echo refs/remotes/origin/side >expect &&
        test_cmp expect actual
 '
 
 test_expect_success 'refs/heads/my-side@{upstream} does not resolve to my-side{upstream}' '
-       test_must_fail full_name refs/heads/my-side@{upstream}
+       test_must_fail git -C clone rev-parse --symbolic-full-name refs/heads/my-side@{upstream}
 '
 
 test_expect_success 'my-side@{u} resolves to correct commit' '
        git checkout side &&
        test_commit 5 &&
        (cd clone && git fetch) &&
-       test 2 = "$(commit_subject my-side)" &&
-       test 5 = "$(commit_subject my-side@{u})"
+       echo 2 >expect &&
+       commit_subject my-side >actual &&
+       test_cmp expect actual &&
+       echo 5 >expect &&
+       commit_subject my-side@{u} >actual
 '
 
 test_expect_success 'not-tracking@{u} fails' '
-       test_must_fail full_name non-tracking@{u} &&
+       test_must_fail git -C clone rev-parse --symbolic-full-name non-tracking@{u} &&
        (cd clone && git checkout --no-track -b non-tracking) &&
-       test_must_fail full_name non-tracking@{u}
+       test_must_fail git -C clone rev-parse --symbolic-full-name non-tracking@{u}
 '
 
 test_expect_success '<branch>@{u}@{1} resolves correctly' '
        test_commit 6 &&
        (cd clone && git fetch) &&
-       test 5 = $(commit_subject my-side@{u}@{1}) &&
-       test 5 = $(commit_subject my-side@{U}@{1})
+       echo 5 >expect &&
+       commit_subject my-side@{u}@{1} >actual &&
+       test_cmp expect actual &&
+       commit_subject my-side@{U}@{1} >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success '@{u} without specifying branch fails on a detached HEAD' '
@@ -149,7 +159,9 @@ test_expect_success 'checkout other@{u}' '
 '
 
 test_expect_success 'branch@{u} works when tracking a local branch' '
-       test refs/heads/master = "$(full_name local-master@{u})"
+       echo refs/heads/master >expect &&
+       git -C clone rev-parse --symbolic-full-name local-master@{u} >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success 'branch@{u} error message when no upstream' '
@@ -203,35 +215,37 @@ test_expect_success 'pull works when tracking a local branch' '
 
 # makes sense if the previous one succeeded
 test_expect_success '@{u} works when tracking a local branch' '
-       test refs/heads/master = "$(full_name @{u})"
+       echo refs/heads/master >expect &&
+       git -C clone rev-parse --symbolic-full-name @{u} >actual &&
+       test_cmp expect actual
 '
 
-commit=$(git rev-parse HEAD)
-cat >expect <<EOF
-commit $commit
-Reflog: master@{0} (C O Mitter <committer@example.com>)
-Reflog message: branch: Created from HEAD
-Author: A U Thor <author@example.com>
-Date:   Thu Apr 7 15:15:13 2005 -0700
-
-    3
-EOF
 test_expect_success 'log -g other@{u}' '
+       commit=$(git rev-parse HEAD) &&
+       cat >expect <<-EOF &&
+       commit $commit
+       Reflog: master@{0} (C O Mitter <committer@example.com>)
+       Reflog message: branch: Created from HEAD
+       Author: A U Thor <author@example.com>
+       Date:   Thu Apr 7 15:15:13 2005 -0700
+
+           3
+       EOF
        git log -1 -g other@{u} >actual &&
        test_cmp expect actual
 '
 
-cat >expect <<EOF
-commit $commit
-Reflog: master@{Thu Apr 7 15:17:13 2005 -0700} (C O Mitter <committer@example.com>)
-Reflog message: branch: Created from HEAD
-Author: A U Thor <author@example.com>
-Date:   Thu Apr 7 15:15:13 2005 -0700
-
-    3
-EOF
-
 test_expect_success 'log -g other@{u}@{now}' '
+       commit=$(git rev-parse HEAD) &&
+       cat >expect <<-EOF &&
+       commit $commit
+       Reflog: master@{Thu Apr 7 15:17:13 2005 -0700} (C O Mitter <committer@example.com>)
+       Reflog message: branch: Created from HEAD
+       Author: A U Thor <author@example.com>
+       Date:   Thu Apr 7 15:15:13 2005 -0700
+
+           3
+       EOF
        git log -1 -g other@{u}@{now} >actual &&
        test_cmp expect actual
 '
index 822381dd9df67f7a392e7697c21d9aade2eb56e1..bbca7ef8da6df7903a4bb64ecad43a3eb24526c4 100755 (executable)
@@ -1,50 +1,76 @@
 #!/bin/sh
 
-test_description='checkout '
+test_description='checkout'
 
 . ./test-lib.sh
 
-# Arguments: <branch> <sha> [<checkout options>]
+# Arguments: [!] <branch> <oid> [<checkout options>]
 #
 # Runs "git checkout" to switch to <branch>, testing that
 #
 #   1) we are on the specified branch, <branch>;
-#   2) HEAD is <sha>; if <sha> is not specified, the old HEAD is used.
+#   2) HEAD is <oid>; if <oid> is not specified, the old HEAD is used.
 #
 # If <checkout options> is not specified, "git checkout" is run with -b.
-do_checkout() {
+#
+# If the first argument is `!`, "git checkout" is expected to fail when
+# it is run.
+do_checkout () {
+       should_fail= &&
+       if test "x$1" = "x!"
+       then
+               should_fail=yes &&
+               shift
+       fi &&
        exp_branch=$1 &&
        exp_ref="refs/heads/$exp_branch" &&
 
-       # if <sha> is not specified, use HEAD.
-       exp_sha=${2:-$(git rev-parse --verify HEAD)} &&
+       # if <oid> is not specified, use HEAD.
+       exp_oid=${2:-$(git rev-parse --verify HEAD)} &&
 
        # default options for git checkout: -b
-       if [ -z "$3" ]; then
+       if test -z "$3"
+       then
                opts="-b"
        else
                opts="$3"
        fi
 
-       git checkout $opts $exp_branch $exp_sha &&
+       if test -n "$should_fail"
+       then
+               test_must_fail git checkout $opts $exp_branch $exp_oid
+       else
+               git checkout $opts $exp_branch $exp_oid &&
+               echo "$exp_ref" >ref.expect &&
+               git rev-parse --symbolic-full-name HEAD >ref.actual &&
+               test_cmp ref.expect ref.actual &&
+               echo "$exp_oid" >oid.expect &&
+               git rev-parse --verify HEAD >oid.actual &&
+               test_cmp oid.expect oid.actual
+       fi
+}
 
-       test $exp_ref = $(git rev-parse --symbolic-full-name HEAD) &&
-       test $exp_sha = $(git rev-parse --verify HEAD)
+test_dirty_unmergeable () {
+       test_expect_code 1 git diff --exit-code
 }
 
-test_dirty_unmergeable() {
-       ! git diff --exit-code >/dev/null
+test_dirty_unmergeable_discards_changes () {
+       git diff --exit-code
 }
 
-setup_dirty_unmergeable() {
+setup_dirty_unmergeable () {
        echo >>file1 change2
 }
 
-test_dirty_mergeable() {
-       ! git diff --cached --exit-code >/dev/null
+test_dirty_mergeable () {
+       test_expect_code 1 git diff --cached --exit-code
+}
+
+test_dirty_mergeable_discards_changes () {
+       git diff --cached --exit-code
 }
 
-setup_dirty_mergeable() {
+setup_dirty_mergeable () {
        echo >file2 file2 &&
        git add file2
 }
@@ -82,7 +108,7 @@ test_expect_success 'checkout -b to a new branch, set to an explicit ref' '
 
 test_expect_success 'checkout -b to a new branch with unmergeable changes fails' '
        setup_dirty_unmergeable &&
-       test_must_fail do_checkout branch2 $HEAD1 &&
+       do_checkout ! branch2 $HEAD1 &&
        test_dirty_unmergeable
 '
 
@@ -93,7 +119,7 @@ test_expect_success 'checkout -f -b to a new branch with unmergeable changes dis
 
        # still dirty and on branch1
        do_checkout branch2 $HEAD1 "-f -b" &&
-       test_must_fail test_dirty_unmergeable
+       test_dirty_unmergeable_discards_changes
 '
 
 test_expect_success 'checkout -b to a new branch preserves mergeable changes' '
@@ -111,12 +137,12 @@ test_expect_success 'checkout -f -b to a new branch with mergeable changes disca
        test_when_finished git reset --hard HEAD &&
        setup_dirty_mergeable &&
        do_checkout branch2 $HEAD1 "-f -b" &&
-       test_must_fail test_dirty_mergeable
+       test_dirty_mergeable_discards_changes
 '
 
 test_expect_success 'checkout -b to an existing branch fails' '
        test_when_finished git reset --hard HEAD &&
-       test_must_fail do_checkout branch2 $HEAD2
+       do_checkout ! branch2 $HEAD2
 '
 
 test_expect_success 'checkout -b to @{-1} fails with the right branch name' '
@@ -140,7 +166,8 @@ test_expect_success 'checkout -B to a merge base' '
 '
 
 test_expect_success 'checkout -B to an existing branch from detached HEAD resets branch to HEAD' '
-       git checkout $(git rev-parse --verify HEAD) &&
+       head=$(git rev-parse --verify HEAD) &&
+       git checkout "$head" &&
 
        do_checkout branch2 "" -B
 '
@@ -155,14 +182,14 @@ test_expect_success 'checkout -B to an existing branch with unmergeable changes
        git checkout branch1 &&
 
        setup_dirty_unmergeable &&
-       test_must_fail do_checkout branch2 $HEAD1 -B &&
+       do_checkout ! branch2 $HEAD1 -B &&
        test_dirty_unmergeable
 '
 
 test_expect_success 'checkout -f -B to an existing branch with unmergeable changes discards changes' '
        # still dirty and on branch1
        do_checkout branch2 $HEAD1 "-f -B" &&
-       test_must_fail test_dirty_unmergeable
+       test_dirty_unmergeable_discards_changes
 '
 
 test_expect_success 'checkout -B to an existing branch preserves mergeable changes' '
@@ -179,7 +206,7 @@ test_expect_success 'checkout -f -B to an existing branch with mergeable changes
 
        setup_dirty_mergeable &&
        do_checkout branch2 $HEAD1 "-f -B" &&
-       test_must_fail test_dirty_mergeable
+       test_dirty_mergeable_discards_changes
 '
 
 test_expect_success 'checkout -b <describe>' '
index fa0718c730c322bfe5f9e6642c4136a7533be5dc..accfa9aa4bd82a5f13d3ce5e3755f33f47b38e3e 100755 (executable)
@@ -37,7 +37,9 @@ test_expect_success 'setup' '
                git checkout -b foo &&
                test_commit a_foo &&
                git checkout -b bar &&
-               test_commit a_bar
+               test_commit a_bar &&
+               git checkout -b ambiguous_branch_and_file &&
+               test_commit a_ambiguous_branch_and_file
        ) &&
        git init repo_b &&
        (
@@ -46,7 +48,9 @@ test_expect_success 'setup' '
                git checkout -b foo &&
                test_commit b_foo &&
                git checkout -b baz &&
-               test_commit b_baz
+               test_commit b_baz &&
+               git checkout -b ambiguous_branch_and_file &&
+               test_commit b_ambiguous_branch_and_file
        ) &&
        git remote add repo_a repo_a &&
        git remote add repo_b repo_b &&
@@ -75,6 +79,26 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' '
        test_branch master
 '
 
+test_expect_success 'when arg matches multiple remotes, do not fallback to interpreting as pathspec' '
+       # create a file with name matching remote branch name
+       git checkout -b t_ambiguous_branch_and_file &&
+       >ambiguous_branch_and_file &&
+       git add ambiguous_branch_and_file &&
+       git commit -m "ambiguous_branch_and_file" &&
+
+       # modify file to verify that it will not be touched by checkout
+       test_when_finished "git checkout -- ambiguous_branch_and_file" &&
+       echo "file contents" >ambiguous_branch_and_file &&
+       cp ambiguous_branch_and_file expect &&
+
+       test_must_fail git checkout ambiguous_branch_and_file 2>err &&
+
+       test_i18ngrep "matched multiple (2) remote tracking branches" err &&
+
+       # file must not be altered
+       test_cmp expect ambiguous_branch_and_file
+'
+
 test_expect_success 'checkout of branch from multiple remotes fails with advice' '
        git checkout -B master &&
        test_might_fail git branch -D foo &&
index f62fd274404e1e5e5265bdc2b6b367a1d984a7f8..43d31d7948536d2219104c2e6694d447101599a2 100755 (executable)
@@ -109,7 +109,11 @@ test_expect_success 'CRLF delimiters' '
 test_expect_success 'quotes' '
        restore_checkpoint &&
 
-       printf "\"file\\101.t\"" | git checkout --pathspec-from-file=- HEAD^1 &&
+       cat >list <<-\EOF &&
+       "file\101.t"
+       EOF
+
+       git checkout --pathspec-from-file=list HEAD^1 &&
 
        cat >expect <<-\EOF &&
        M  fileA.t
@@ -120,7 +124,10 @@ test_expect_success 'quotes' '
 test_expect_success 'quotes not compatible with --pathspec-file-nul' '
        restore_checkpoint &&
 
-       printf "\"file\\101.t\"" >list &&
+       cat >list <<-\EOF &&
+       "file\101.t"
+       EOF
+
        test_must_fail git checkout --pathspec-from-file=list --pathspec-file-nul HEAD^1
 '
 
@@ -136,4 +143,21 @@ test_expect_success 'only touches what was listed' '
        verify_expect
 '
 
+test_expect_success 'error conditions' '
+       restore_checkpoint &&
+       echo fileA.t >list &&
+
+       test_must_fail git checkout --pathspec-from-file=list --detach 2>err &&
+       test_i18ngrep -e "--pathspec-from-file is incompatible with --detach" err &&
+
+       test_must_fail git checkout --pathspec-from-file=list --patch 2>err &&
+       test_i18ngrep -e "--pathspec-from-file is incompatible with --patch" err &&
+
+       test_must_fail git checkout --pathspec-from-file=list -- fileA.t 2>err &&
+       test_i18ngrep -e "--pathspec-from-file is incompatible with pathspec arguments" err &&
+
+       test_must_fail git checkout --pathspec-file-nul 2>err &&
+       test_i18ngrep -e "--pathspec-file-nul requires --pathspec-from-file" err
+'
+
 test_done
index 21c3f84459dfe29053bbb0d9a43160a6df33f737..076d0df7fc0602e8d47c03ffa6ddb6dff0b985a9 100755 (executable)
@@ -106,4 +106,21 @@ test_expect_success 'restore --staged adds deleted intent-to-add file back to in
        git diff --cached --exit-code
 '
 
+test_expect_success 'restore --staged invalidates cache tree for deletions' '
+       test_when_finished git reset --hard &&
+       >new1 &&
+       >new2 &&
+       git add new1 new2 &&
+
+       # It is important to commit and then reset here, so that the index
+       # contains a valid cache-tree for the "both" tree.
+       git commit -m both &&
+       git reset --soft HEAD^ &&
+
+       git restore --staged new1 &&
+       git commit -m "just new2" &&
+       git rev-parse HEAD:new2 &&
+       test_must_fail git rev-parse HEAD:new1
+'
+
 test_done
index db58e83735080269a85f80716678ab19838c434b..0d47946e8a9b06f044542e373f3de477e53e3261 100755 (executable)
@@ -109,7 +109,11 @@ test_expect_success 'CRLF delimiters' '
 test_expect_success 'quotes' '
        restore_checkpoint &&
 
-       printf "\"file\\101.t\"" | git restore --pathspec-from-file=- --source=HEAD^1 &&
+       cat >list <<-\EOF &&
+       "file\101.t"
+       EOF
+
+       git restore --pathspec-from-file=list --source=HEAD^1 &&
 
        cat >expect <<-\EOF &&
         M fileA.t
@@ -120,7 +124,10 @@ test_expect_success 'quotes' '
 test_expect_success 'quotes not compatible with --pathspec-file-nul' '
        restore_checkpoint &&
 
-       printf "\"file\\101.t\"" >list &&
+       cat >list <<-\EOF &&
+       "file\101.t"
+       EOF
+
        test_must_fail git restore --pathspec-from-file=list --pathspec-file-nul --source=HEAD^1
 '
 
@@ -136,4 +143,22 @@ test_expect_success 'only touches what was listed' '
        verify_expect
 '
 
+test_expect_success 'error conditions' '
+       restore_checkpoint &&
+       echo fileA.t >list &&
+       >empty_list &&
+
+       test_must_fail git restore --pathspec-from-file=list --patch --source=HEAD^1 2>err &&
+       test_i18ngrep -e "--pathspec-from-file is incompatible with --patch" err &&
+
+       test_must_fail git restore --pathspec-from-file=list --source=HEAD^1 -- fileA.t 2>err &&
+       test_i18ngrep -e "--pathspec-from-file is incompatible with pathspec arguments" err &&
+
+       test_must_fail git restore --pathspec-file-nul --source=HEAD^1 2>err &&
+       test_i18ngrep -e "--pathspec-file-nul requires --pathspec-from-file" err &&
+
+       test_must_fail git restore --pathspec-from-file=empty_list --source=HEAD^1 2>err &&
+       test_i18ngrep -e "you must specify path(s) to restore" err
+'
+
 test_done
diff --git a/t/t2405-worktree-submodule.sh b/t/t2405-worktree-submodule.sh
new file mode 100755 (executable)
index 0000000..e1b2bfd
--- /dev/null
@@ -0,0 +1,90 @@
+#!/bin/sh
+
+test_description='Combination of submodules and multiple worktrees'
+
+. ./test-lib.sh
+
+base_path=$(pwd -P)
+
+test_expect_success 'setup: create origin repos'  '
+       git init origin/sub &&
+       test_commit -C origin/sub file1 &&
+       git init origin/main &&
+       test_commit -C origin/main first &&
+       git -C origin/main submodule add ../sub &&
+       git -C origin/main commit -m "add sub" &&
+       test_commit -C origin/sub "file1 updated" file1 file1updated file1updated &&
+       git -C origin/main/sub pull &&
+       git -C origin/main add sub &&
+       git -C origin/main commit -m "sub updated"
+'
+
+test_expect_success 'setup: clone superproject to create main worktree' '
+       git clone --recursive "$base_path/origin/main" main
+'
+
+rev1_hash_main=$(git --git-dir=origin/main/.git show --pretty=format:%h -q "HEAD~1")
+rev1_hash_sub=$(git --git-dir=origin/sub/.git show --pretty=format:%h -q "HEAD~1")
+
+test_expect_success 'add superproject worktree' '
+       git -C main worktree add "$base_path/worktree" "$rev1_hash_main"
+'
+
+test_expect_failure 'submodule is checked out just after worktree add' '
+       git -C worktree diff --submodule master"^!" >out &&
+       grep "file1 updated" out
+'
+
+test_expect_success 'add superproject worktree and initialize submodules' '
+       git -C main worktree add "$base_path/worktree-submodule-update" "$rev1_hash_main" &&
+       git -C worktree-submodule-update submodule update
+'
+
+test_expect_success 'submodule is checked out just after submodule update in linked worktree' '
+       git -C worktree-submodule-update diff --submodule master"^!" >out &&
+       grep "file1 updated" out
+'
+
+test_expect_success 'add superproject worktree and manually add submodule worktree' '
+       git -C main worktree add "$base_path/linked_submodule" "$rev1_hash_main" &&
+       git -C main/sub worktree add "$base_path/linked_submodule/sub" "$rev1_hash_sub"
+'
+
+test_expect_success 'submodule is checked out after manually adding submodule worktree' '
+       git -C linked_submodule diff --submodule master"^!" >out &&
+       grep "file1 updated" out
+'
+
+test_expect_success 'checkout --recurse-submodules uses $GIT_DIR for submodules in a linked worktree' '
+       git -C main worktree add "$base_path/checkout-recurse" --detach  &&
+       git -C checkout-recurse submodule update --init &&
+       echo "gitdir: ../../main/.git/worktrees/checkout-recurse/modules/sub" >expect-gitfile &&
+       cat checkout-recurse/sub/.git >actual-gitfile &&
+       test_cmp expect-gitfile actual-gitfile &&
+       git -C main/sub rev-parse HEAD >expect-head-main &&
+       git -C checkout-recurse checkout --recurse-submodules HEAD~1 &&
+       cat checkout-recurse/sub/.git >actual-gitfile &&
+       git -C main/sub rev-parse HEAD >actual-head-main &&
+       test_cmp expect-gitfile actual-gitfile &&
+       test_cmp expect-head-main actual-head-main
+'
+
+test_expect_success 'core.worktree is removed in $GIT_DIR/modules/<name>/config, not in $GIT_COMMON_DIR/modules/<name>/config' '
+       echo "../../../sub" >expect-main &&
+       git -C main/sub config --get core.worktree >actual-main &&
+       test_cmp expect-main actual-main &&
+       echo "../../../../../../checkout-recurse/sub" >expect-linked &&
+       git -C checkout-recurse/sub config --get core.worktree >actual-linked &&
+       test_cmp expect-linked actual-linked &&
+       git -C checkout-recurse checkout --recurse-submodules first &&
+       test_expect_code 1 git -C main/.git/worktrees/checkout-recurse/modules/sub config --get core.worktree >linked-config &&
+       test_must_be_empty linked-config &&
+       git -C main/sub config --get core.worktree >actual-main &&
+       test_cmp expect-main actual-main
+'
+
+test_expect_success 'unsetting core.worktree does not prevent running commands directly against the submodule repository' '
+       git -C main/.git/worktrees/checkout-recurse/modules/sub log
+'
+
+test_done
index 2170758e38d786709425f3f39f2c481352e82e8e..d48d211a95403c4f995982a0a8bca2c1f1eaa17b 100755 (executable)
@@ -604,7 +604,7 @@ test_expect_success 'merge removes empty directories' '
        git commit -mremoved-d/e &&
        git checkout master &&
        git merge -s recursive rm &&
-       test_must_fail test -d d
+       test_path_is_missing d
 '
 
 test_expect_success 'merge-recursive simple w/submodule' '
index 2dea846e259dbb8cd163b82f6e551c9823cbd180..d746f4ba550879685a4e7ad357c39b417dbf6d32 100755 (executable)
@@ -32,6 +32,12 @@ verify_notes () {
        test_cmp "expect_log_$notes_ref" "output_log_$notes_ref"
 }
 
+notes_merge_files_gone () {
+       # No .git/NOTES_MERGE_* files left
+       { ls .git/NOTES_MERGE_* >output || :; } &&
+       test_must_be_empty output
+}
+
 cat <<EOF | sort >expect_notes_x
 6e8e3febca3c2bb896704335cc4d0c34cb2f8715 $commit_sha4
 e5388c10860456ee60673025345fe2e153eb8cf8 $commit_sha3
@@ -335,9 +341,7 @@ EOF
 y and z notes on 4th commit
 EOF
        git notes merge --commit &&
-       # No .git/NOTES_MERGE_* files left
-       test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
-       test_must_be_empty output &&
+       notes_merge_files_gone &&
        # Merge commit has pre-merge y and pre-merge z as parents
        test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
        test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" &&
@@ -397,9 +401,7 @@ test_expect_success 'redo merge of z into m (== y) with default ("manual") resol
 
 test_expect_success 'abort notes merge' '
        git notes merge --abort &&
-       # No .git/NOTES_MERGE_* files left
-       test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
-       test_must_be_empty output &&
+       notes_merge_files_gone &&
        # m has not moved (still == y)
        test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" &&
        # Verify that other notes refs has not changed (w, x, y and z)
@@ -464,9 +466,7 @@ EOF
        echo "new note on 5th commit" > .git/NOTES_MERGE_WORKTREE/$commit_sha5 &&
        # Finalize merge
        git notes merge --commit &&
-       # No .git/NOTES_MERGE_* files left
-       test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
-       test_must_be_empty output &&
+       notes_merge_files_gone &&
        # Merge commit has pre-merge y and pre-merge z as parents
        test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
        test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" &&
@@ -553,9 +553,7 @@ EOF
 
 test_expect_success 'resolve situation by aborting the notes merge' '
        git notes merge --abort &&
-       # No .git/NOTES_MERGE_* files left
-       test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
-       test_must_be_empty output &&
+       notes_merge_files_gone &&
        # m has not moved (still == w)
        test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" &&
        # Verify that other notes refs has not changed (w, x, y and z)
index ae6e55ce79ab67320a7dfb34efc6e677456e810d..d79a3ef50546dde3dc381a11300dddf0bdf54cee 100755 (executable)
@@ -1264,13 +1264,26 @@ test_expect_success SHA1 'short SHA-1 setup' '
 test_expect_success SHA1 'short SHA-1 collide' '
        test_when_finished "reset_rebase && git checkout master" &&
        git checkout collide &&
+       colliding_sha1=6bcda37 &&
+       test $colliding_sha1 = "$(git rev-parse HEAD | cut -c 1-7)" &&
        (
                unset test_tick &&
                test_tick &&
                set_fake_editor &&
                FAKE_COMMIT_MESSAGE="collide2 ac4f2ee" \
-               FAKE_LINES="reword 1 2" git rebase -i HEAD~2
-       )
+               FAKE_LINES="reword 1 break 2" git rebase -i HEAD~2 &&
+               test $colliding_sha1 = "$(git rev-parse HEAD | cut -c 1-7)" &&
+               grep "^pick $colliding_sha1 " \
+                       .git/rebase-merge/git-rebase-todo.tmp &&
+               grep "^pick [0-9a-f]\{40\}" \
+                       .git/rebase-merge/git-rebase-todo &&
+               grep "^pick [0-9a-f]\{40\}" \
+                       .git/rebase-merge/git-rebase-todo.backup &&
+               git rebase --continue
+       ) &&
+       collide2="$(git rev-parse HEAD~1 | cut -c 1-4)" &&
+       collide3="$(git rev-parse collide3 | cut -c 1-4)" &&
+       test "$collide2" = "$collide3"
 '
 
 test_expect_success 'respect core.abbrev' '
@@ -1450,6 +1463,127 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '
        test B = $(git cat-file commit HEAD^ | sed -ne \$p)
 '
 
+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = ignore' '
+       test_config rebase.missingCommitsCheck ignore &&
+       rebase_setup_and_clean missing-commit &&
+       (
+               set_fake_editor &&
+               FAKE_LINES="break 1 2 3 4 5" git rebase -i --root &&
+               FAKE_LINES="1 2 3 4" git rebase --edit-todo &&
+               git rebase --continue 2>actual
+       ) &&
+       test D = $(git cat-file commit HEAD | sed -ne \$p) &&
+       test_i18ngrep \
+               "Successfully rebased and updated refs/heads/missing-commit" \
+               actual
+'
+
+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '
+       cat >expect <<-EOF &&
+       error: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)
+       Warning: some commits may have been dropped accidentally.
+       Dropped commits (newer to older):
+        - $(git rev-list --pretty=oneline --abbrev-commit -1 master)
+        - $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)
+       To avoid this message, use "drop" to explicitly remove a commit.
+       EOF
+       head -n4 expect >expect.2 &&
+       tail -n1 expect >>expect.2 &&
+       tail -n4 expect.2 >expect.3 &&
+       test_config rebase.missingCommitsCheck warn &&
+       rebase_setup_and_clean missing-commit &&
+       (
+               set_fake_editor &&
+               test_must_fail env FAKE_LINES="bad 1 2 3 4 5" \
+                       git rebase -i --root &&
+               cp .git/rebase-merge/git-rebase-todo.backup orig &&
+               FAKE_LINES="2 3 4" git rebase --edit-todo 2>actual.2 &&
+               head -n6 actual.2 >actual &&
+               test_i18ncmp expect actual &&
+               cp orig .git/rebase-merge/git-rebase-todo &&
+               FAKE_LINES="1 2 3 4" git rebase --edit-todo 2>actual.2 &&
+               head -n4 actual.2 >actual &&
+               test_i18ncmp expect.3 actual &&
+               git rebase --continue 2>actual
+       ) &&
+       test D = $(git cat-file commit HEAD | sed -ne \$p) &&
+       test_i18ngrep \
+               "Successfully rebased and updated refs/heads/missing-commit" \
+               actual
+'
+
+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '
+       cat >expect <<-EOF &&
+       error: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)
+       Warning: some commits may have been dropped accidentally.
+       Dropped commits (newer to older):
+        - $(git rev-list --pretty=oneline --abbrev-commit -1 master)
+        - $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)
+       To avoid this message, use "drop" to explicitly remove a commit.
+
+       Use '\''git config rebase.missingCommitsCheck'\'' to change the level of warnings.
+       The possible behaviours are: ignore, warn, error.
+
+       You can fix this with '\''git rebase --edit-todo'\'' and then run '\''git rebase --continue'\''.
+       Or you can abort the rebase with '\''git rebase --abort'\''.
+       EOF
+       tail -n11 expect >expect.2 &&
+       head -n3 expect.2 >expect.3 &&
+       tail -n7 expect.2 >>expect.3 &&
+       test_config rebase.missingCommitsCheck error &&
+       rebase_setup_and_clean missing-commit &&
+       (
+               set_fake_editor &&
+               test_must_fail env FAKE_LINES="bad 1 2 3 4 5" \
+                       git rebase -i --root &&
+               cp .git/rebase-merge/git-rebase-todo.backup orig &&
+               test_must_fail env FAKE_LINES="2 3 4" \
+                       git rebase --edit-todo 2>actual &&
+               test_i18ncmp expect actual &&
+               test_must_fail git rebase --continue 2>actual &&
+               test_i18ncmp expect.2 actual &&
+               test_must_fail git rebase --edit-todo &&
+               cp orig .git/rebase-merge/git-rebase-todo &&
+               test_must_fail env FAKE_LINES="1 2 3 4" \
+                       git rebase --edit-todo 2>actual &&
+               test_i18ncmp expect.3 actual &&
+               test_must_fail git rebase --continue 2>actual &&
+               test_i18ncmp expect.3 actual &&
+               cp orig .git/rebase-merge/git-rebase-todo &&
+               FAKE_LINES="1 2 3 4 drop 5" git rebase --edit-todo &&
+               git rebase --continue 2>actual
+       ) &&
+       test D = $(git cat-file commit HEAD | sed -ne \$p) &&
+       test_i18ngrep \
+               "Successfully rebased and updated refs/heads/missing-commit" \
+               actual
+'
+
+test_expect_success 'rebase.missingCommitsCheck = error after resolving conflicts' '
+       test_config rebase.missingCommitsCheck error &&
+       (
+               set_fake_editor &&
+               FAKE_LINES="drop 1 break 2 3 4" git rebase -i A E
+       ) &&
+       git rebase --edit-todo &&
+       test_must_fail git rebase --continue &&
+       echo x >file1 &&
+       git add file1 &&
+       git rebase --continue
+'
+
+test_expect_success 'rebase.missingCommitsCheck = error when editing for a second time' '
+       test_config rebase.missingCommitsCheck error &&
+       (
+               set_fake_editor &&
+               FAKE_LINES="1 break 2 3" git rebase -i A D &&
+               cp .git/rebase-merge/git-rebase-todo todo &&
+               test_must_fail env FAKE_LINES=2 git rebase --edit-todo &&
+               GIT_SEQUENCE_EDITOR="cp todo" git rebase --edit-todo &&
+               git rebase --continue
+       )
+'
+
 test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' '
        rebase_setup_and_clean abbrevcmd &&
        test_commit "first" file1.txt "first line" first &&
index 22d218698e958add3126f40479ca559f52b2bda8..093de9005b76c35a2d9d7840549aa210dd38dff6 100755 (executable)
@@ -25,6 +25,13 @@ test_expect_success setup '
 '
 
 test_auto_fixup () {
+       no_squash= &&
+       if test "x$1" = 'x!'
+       then
+               no_squash=true
+               shift
+       fi &&
+
        git reset --hard base &&
        echo 1 >file1 &&
        git add -u &&
@@ -35,10 +42,19 @@ test_auto_fixup () {
        test_tick &&
        git rebase $2 -i HEAD^^^ &&
        git log --oneline >actual &&
-       test_line_count = 3 actual &&
-       git diff --exit-code $1 &&
-       test 1 = "$(git cat-file blob HEAD^:file1)" &&
-       test 1 = $(git cat-file commit HEAD^ | grep first | wc -l)
+       if test -n "$no_squash"
+       then
+               test_line_count = 4 actual
+       else
+               test_line_count = 3 actual &&
+               git diff --exit-code $1 &&
+               echo 1 >expect &&
+               git cat-file blob HEAD^:file1 >actual &&
+               test_cmp expect actual &&
+               git cat-file commit HEAD^ >commit &&
+               grep first commit >actual &&
+               test_line_count = 1 actual
+       fi
 }
 
 test_expect_success 'auto fixup (option)' '
@@ -48,12 +64,19 @@ test_expect_success 'auto fixup (option)' '
 test_expect_success 'auto fixup (config)' '
        git config rebase.autosquash true &&
        test_auto_fixup final-fixup-config-true &&
-       test_must_fail test_auto_fixup fixup-config-true-no --no-autosquash &&
+       test_auto_fixup ! fixup-config-true-no --no-autosquash &&
        git config rebase.autosquash false &&
-       test_must_fail test_auto_fixup final-fixup-config-false
+       test_auto_fixup ! final-fixup-config-false
 '
 
 test_auto_squash () {
+       no_squash= &&
+       if test "x$1" = 'x!'
+       then
+               no_squash=true
+               shift
+       fi &&
+
        git reset --hard base &&
        echo 1 >file1 &&
        git add -u &&
@@ -64,10 +87,19 @@ test_auto_squash () {
        test_tick &&
        git rebase $2 -i HEAD^^^ &&
        git log --oneline >actual &&
-       test_line_count = 3 actual &&
-       git diff --exit-code $1 &&
-       test 1 = "$(git cat-file blob HEAD^:file1)" &&
-       test 2 = $(git cat-file commit HEAD^ | grep first | wc -l)
+       if test -n "$no_squash"
+       then
+               test_line_count = 4 actual
+       else
+               test_line_count = 3 actual &&
+               git diff --exit-code $1 &&
+               echo 1 >expect &&
+               git cat-file blob HEAD^:file1 >actual &&
+               test_cmp expect actual &&
+               git cat-file commit HEAD^ >commit &&
+               grep first commit >actual &&
+               test_line_count = 2 actual
+       fi
 }
 
 test_expect_success 'auto squash (option)' '
@@ -77,9 +109,9 @@ test_expect_success 'auto squash (option)' '
 test_expect_success 'auto squash (config)' '
        git config rebase.autosquash true &&
        test_auto_squash final-squash-config-true &&
-       test_must_fail test_auto_squash squash-config-true-no --no-autosquash &&
+       test_auto_squash ! squash-config-true-no --no-autosquash &&
        git config rebase.autosquash false &&
-       test_must_fail test_auto_squash final-squash-config-false
+       test_auto_squash ! final-squash-config-false
 '
 
 test_expect_success 'misspelled auto squash' '
@@ -94,7 +126,8 @@ test_expect_success 'misspelled auto squash' '
        git log --oneline >actual &&
        test_line_count = 4 actual &&
        git diff --exit-code final-missquash &&
-       test 0 = $(git rev-list final-missquash...HEAD | wc -l)
+       git rev-list final-missquash...HEAD >list &&
+       test_must_be_empty list
 '
 
 test_expect_success 'auto squash that matches 2 commits' '
@@ -113,9 +146,15 @@ test_expect_success 'auto squash that matches 2 commits' '
        git log --oneline >actual &&
        test_line_count = 4 actual &&
        git diff --exit-code final-multisquash &&
-       test 1 = "$(git cat-file blob HEAD^^:file1)" &&
-       test 2 = $(git cat-file commit HEAD^^ | grep first | wc -l) &&
-       test 1 = $(git cat-file commit HEAD | grep first | wc -l)
+       echo 1 >expect &&
+       git cat-file blob HEAD^^:file1 >actual &&
+       test_cmp expect actual &&
+       git cat-file commit HEAD^^ >commit &&
+       grep first commit >actual &&
+       test_line_count = 2 actual &&
+       git cat-file commit HEAD >commit &&
+       grep first commit >actual &&
+       test_line_count = 1 actual
 '
 
 test_expect_success 'auto squash that matches a commit after the squash' '
@@ -134,25 +173,38 @@ test_expect_success 'auto squash that matches a commit after the squash' '
        git log --oneline >actual &&
        test_line_count = 5 actual &&
        git diff --exit-code final-presquash &&
-       test 0 = "$(git cat-file blob HEAD^^:file1)" &&
-       test 1 = "$(git cat-file blob HEAD^:file1)" &&
-       test 1 = $(git cat-file commit HEAD | grep third | wc -l) &&
-       test 1 = $(git cat-file commit HEAD^ | grep third | wc -l)
+       echo 0 >expect &&
+       git cat-file blob HEAD^^:file1 >actual &&
+       test_cmp expect actual &&
+       echo 1 >expect &&
+       git cat-file blob HEAD^:file1 >actual &&
+       test_cmp expect actual &&
+       git cat-file commit HEAD >commit &&
+       grep third commit >actual &&
+       test_line_count = 1 actual &&
+       git cat-file commit HEAD^ >commit &&
+       grep third commit >actual &&
+       test_line_count = 1 actual
 '
 test_expect_success 'auto squash that matches a sha1' '
        git reset --hard base &&
        echo 1 >file1 &&
        git add -u &&
        test_tick &&
-       git commit -m "squash! $(git rev-parse --short HEAD^)" &&
+       oid=$(git rev-parse --short HEAD^) &&
+       git commit -m "squash! $oid" &&
        git tag final-shasquash &&
        test_tick &&
        git rebase --autosquash -i HEAD^^^ &&
        git log --oneline >actual &&
        test_line_count = 3 actual &&
        git diff --exit-code final-shasquash &&
-       test 1 = "$(git cat-file blob HEAD^:file1)" &&
-       test 1 = $(git cat-file commit HEAD^ | grep squash | wc -l)
+       echo 1 >expect &&
+       git cat-file blob HEAD^:file1 >actual &&
+       test_cmp expect actual &&
+       git cat-file commit HEAD^ >commit &&
+       grep squash commit >actual &&
+       test_line_count = 1 actual
 '
 
 test_expect_success 'auto squash that matches longer sha1' '
@@ -160,15 +212,20 @@ test_expect_success 'auto squash that matches longer sha1' '
        echo 1 >file1 &&
        git add -u &&
        test_tick &&
-       git commit -m "squash! $(git rev-parse --short=11 HEAD^)" &&
+       oid=$(git rev-parse --short=11 HEAD^) &&
+       git commit -m "squash! $oid" &&
        git tag final-longshasquash &&
        test_tick &&
        git rebase --autosquash -i HEAD^^^ &&
        git log --oneline >actual &&
        test_line_count = 3 actual &&
        git diff --exit-code final-longshasquash &&
-       test 1 = "$(git cat-file blob HEAD^:file1)" &&
-       test 1 = $(git cat-file commit HEAD^ | grep squash | wc -l)
+       echo 1 >expect &&
+       git cat-file blob HEAD^:file1 >actual &&
+       test_cmp expect actual &&
+       git cat-file commit HEAD^ >commit &&
+       grep squash commit >actual &&
+       test_line_count = 1 actual
 '
 
 test_auto_commit_flags () {
@@ -183,8 +240,12 @@ test_auto_commit_flags () {
        git log --oneline >actual &&
        test_line_count = 3 actual &&
        git diff --exit-code final-commit-$1 &&
-       test 1 = "$(git cat-file blob HEAD^:file1)" &&
-       test $2 = $(git cat-file commit HEAD^ | grep first | wc -l)
+       echo 1 >expect &&
+       git cat-file blob HEAD^:file1 >actual &&
+       test_cmp expect actual &&
+       git cat-file commit HEAD^ >commit &&
+       grep first commit >actual &&
+       test_line_count = $2 actual
 }
 
 test_expect_success 'use commit --fixup' '
@@ -210,11 +271,15 @@ test_auto_fixup_fixup () {
        (
                set_cat_todo_editor &&
                test_must_fail git rebase --autosquash -i HEAD^^^^ >actual &&
+               head=$(git rev-parse --short HEAD) &&
+               parent1=$(git rev-parse --short HEAD^) &&
+               parent2=$(git rev-parse --short HEAD^^) &&
+               parent3=$(git rev-parse --short HEAD^^^) &&
                cat >expected <<-EOF &&
-               pick $(git rev-parse --short HEAD^^^) first commit
-               $1 $(git rev-parse --short HEAD^) $1! first
-               $1 $(git rev-parse --short HEAD) $1! $2! first
-               pick $(git rev-parse --short HEAD^^) second commit
+               pick $parent3 first commit
+               $1 $parent1 $1! first
+               $1 $head $1! $2! first
+               pick $parent2 second commit
                EOF
                test_cmp expected actual
        ) &&
@@ -222,13 +287,17 @@ test_auto_fixup_fixup () {
        git log --oneline >actual &&
        test_line_count = 3 actual
        git diff --exit-code "final-$1-$2" &&
-       test 2 = "$(git cat-file blob HEAD^:file1)" &&
+       echo 2 >expect &&
+       git cat-file blob HEAD^:file1 >actual &&
+       test_cmp expect actual &&
+       git cat-file commit HEAD^ >commit &&
+       grep first commit >actual &&
        if test "$1" = "fixup"
        then
-               test 1 = $(git cat-file commit HEAD^ | grep first | wc -l)
+               test_line_count = 1 actual
        elif test "$1" = "squash"
        then
-               test 3 = $(git cat-file commit HEAD^ | grep first | wc -l)
+               test_line_count = 3 actual
        else
                false
        fi
@@ -256,19 +325,25 @@ test_expect_success C_LOCALE_OUTPUT 'autosquash with custom inst format' '
        echo 2 >file1 &&
        git add -u &&
        test_tick &&
-       git commit -m "squash! $(git rev-parse --short HEAD^)" &&
+       oid=$(git rev-parse --short HEAD^) &&
+       git commit -m "squash! $oid" &&
        echo 1 >file1 &&
        git add -u &&
        test_tick &&
-       git commit -m "squash! $(git log -n 1 --format=%s HEAD~2)" &&
+       subject=$(git log -n 1 --format=%s HEAD~2) &&
+       git commit -m "squash! $subject" &&
        git tag final-squash-instFmt &&
        test_tick &&
        git rebase --autosquash -i HEAD~4 &&
        git log --oneline >actual &&
        test_line_count = 3 actual &&
        git diff --exit-code final-squash-instFmt &&
-       test 1 = "$(git cat-file blob HEAD^:file1)" &&
-       test 2 = $(git cat-file commit HEAD^ | grep squash | wc -l)
+       echo 1 >expect &&
+       git cat-file blob HEAD^:file1 >actual &&
+       test_cmp expect actual &&
+       git cat-file commit HEAD^ >commit &&
+       grep squash commit >actual &&
+       test_line_count = 2 actual
 '
 
 test_expect_success 'autosquash with empty custom instructionFormat' '
index 49f548cdb93d53d88723adcd1f414a4db4c14a73..94552669ae87c9480afa004e2655729e9450d8e6 100755 (executable)
@@ -80,7 +80,8 @@ do_tests () {
                git commit -q -m "change big file again" &&
                git checkout -q other^{} &&
                git rebase master &&
-               test_must_fail test -n "$(git rev-list master...HEAD~)"
+               git rev-list master...HEAD~ >revs &&
+               test_must_be_empty revs
        '
 
        test_expect_success $pr 'do not drop patch' '
index a267b2d144df4a84f18ba4907b317e757ba98f16..80a0d087065398732a8ca74bb87c4509b3c8b0b1 100755 (executable)
@@ -94,8 +94,10 @@ test_expect_success 'cherry-pick --rerere-autoupdate more than once' '
 
 test_expect_success 'cherry-pick conflict without rerere' '
        test_config rerere.enabled false &&
-       test_must_fail git cherry-pick master &&
-       test_must_fail test_cmp expect foo
+       test_must_fail git cherry-pick foo-master &&
+       grep ===== foo &&
+       grep foo-dev foo &&
+       grep foo-master foo
 '
 
 test_done
index 9b9b4ca8d4f2a2188c206a0e1e2de763d00a1ab6..9bd482ce3b8912b2016fe2997c50d59b5a5162c5 100755 (executable)
@@ -168,7 +168,7 @@ test_expect_success 'successful final commit clears cherry-pick state' '
        echo resolved >foo &&
        test_path_is_file .git/sequencer/todo &&
        git commit -a &&
-       test_must_fail test_path_exists .git/sequencer
+       test_path_is_missing .git/sequencer
 '
 
 test_expect_success 'reset after final pick clears cherry-pick state' '
@@ -178,7 +178,7 @@ test_expect_success 'reset after final pick clears cherry-pick state' '
        echo resolved >foo &&
        test_path_is_file .git/sequencer/todo &&
        git reset &&
-       test_must_fail test_path_exists .git/sequencer
+       test_path_is_missing .git/sequencer
 '
 
 test_expect_success 'failed cherry-pick produces dirty index' '
@@ -381,23 +381,23 @@ test_expect_success 'failed commit does not clear REVERT_HEAD' '
 '
 
 test_expect_success 'successful final commit clears revert state' '
-       pristine_detach picked-signed &&
+       pristine_detach picked-signed &&
 
-       test_must_fail git revert picked-signed base &&
-       echo resolved >foo &&
-       test_path_is_file .git/sequencer/todo &&
-       git commit -a &&
-       test_must_fail test_path_exists .git/sequencer
+       test_must_fail git revert picked-signed base &&
+       echo resolved >foo &&
+       test_path_is_file .git/sequencer/todo &&
+       git commit -a &&
+       test_path_is_missing .git/sequencer
 '
 
 test_expect_success 'reset after final pick clears revert state' '
-       pristine_detach picked-signed &&
+       pristine_detach picked-signed &&
 
-       test_must_fail git revert picked-signed base &&
-       echo resolved >foo &&
-       test_path_is_file .git/sequencer/todo &&
-       git reset &&
-       test_must_fail test_path_exists .git/sequencer
+       test_must_fail git revert picked-signed base &&
+       echo resolved >foo &&
+       test_path_is_file .git/sequencer/todo &&
+       git reset &&
+       test_path_is_missing .git/sequencer
 '
 
 test_expect_success 'revert conflict, diff3 -m style' '
index 0ea858d652fcb5aee58e8a71c6d68cdbcfb974ea..f2c0168941ad043f61a1e082799e75cefa3b5462 100755 (executable)
@@ -425,6 +425,13 @@ test_expect_success 'rm will error out on a modified .gitmodules file unless sta
        git status -s -uno >actual &&
        test_cmp expect actual
 '
+test_expect_success 'rm will not error out on .gitmodules file with zero stat data' '
+       git reset --hard &&
+       git submodule update &&
+       git read-tree HEAD &&
+       git rm submod &&
+       test_path_is_missing submod
+'
 
 test_expect_success 'rm issues a warning when section is not found in .gitmodules' '
        git reset --hard &&
index c325167b90318b2f85a9b53e3aea89eb989e6d64..88bc799807f7dd99ba98cda2b698ef83e027f7b7 100755 (executable)
@@ -326,7 +326,9 @@ test_expect_success 'git add --dry-run of an existing file output' "
 cat >expect.err <<\EOF
 The following paths are ignored by one of your .gitignore files:
 ignored-file
-Use -f if you really want to add them.
+hint: Use -f if you really want to add them.
+hint: Turn this message off by running
+hint: "git config advice.addIgnoredFile false"
 EOF
 cat >expect.out <<\EOF
 add 'track-this'
index 12ee321707a33bd8455fb30ea24c57d002c13b68..5bae6e50f1f3e76c1615bf5e3031e99f92d619b9 100755 (executable)
@@ -68,6 +68,15 @@ test_expect_success 'revert works (initial)' '
        ! grep . output
 '
 
+test_expect_success 'add untracked (multiple)' '
+       test_when_finished "git reset && rm [1-9]" &&
+       touch $(test_seq 9) &&
+       test_write_lines a "2-5 8-" | git add -i -- [1-9] &&
+       test_write_lines 2 3 4 5 8 9 >expected &&
+       git ls-files [1-9] >output &&
+       test_cmp expected output
+'
+
 test_expect_success 'setup (commit)' '
        echo baseline >file &&
        git add file &&
@@ -544,6 +553,19 @@ test_expect_success 'diffs can be colorized' '
        grep "$(printf "\\033")" output
 '
 
+test_expect_success 'colorized diffs respect diff.wsErrorHighlight' '
+       git reset --hard &&
+
+       echo "old " >test &&
+       git add test &&
+       echo "new " >test &&
+
+       printf y >y &&
+       force_color git -c diff.wsErrorHighlight=all add -p >output.raw 2>&1 <y &&
+       test_decode_color <output.raw >output &&
+       grep "old<" output
+'
+
 test_expect_success 'diffFilter filters diff' '
        git reset --hard &&
 
@@ -561,7 +583,7 @@ test_expect_success 'detect bogus diffFilter output' '
        git reset --hard &&
 
        echo content >test &&
-       test_config interactive.diffFilter "echo too-short" &&
+       test_config interactive.diffFilter "sed 1d" &&
        printf y >y &&
        test_must_fail force_color git add -p <y
 '
index 3cfdb669b7a5a1bc4b915abb0c880f067bfbd7b9..9e35c1fbca68b67336ce2725d91b57a981e8b5c7 100755 (executable)
@@ -97,7 +97,11 @@ test_expect_success 'CRLF delimiters' '
 test_expect_success 'quotes' '
        restore_checkpoint &&
 
-       printf "\"file\\101.t\"" | git add --pathspec-from-file=- &&
+       cat >list <<-\EOF &&
+       "file\101.t"
+       EOF
+
+       git add --pathspec-from-file=list &&
 
        cat >expect <<-\EOF &&
        A  fileA.t
@@ -108,7 +112,10 @@ test_expect_success 'quotes' '
 test_expect_success 'quotes not compatible with --pathspec-file-nul' '
        restore_checkpoint &&
 
-       printf "\"file\\101.t\"" >list &&
+       cat >list <<-\EOF &&
+       "file\101.t"
+       EOF
+
        test_must_fail git add --pathspec-from-file=list --pathspec-file-nul
 '
 
@@ -124,4 +131,29 @@ test_expect_success 'only touches what was listed' '
        verify_expect
 '
 
+test_expect_success 'error conditions' '
+       restore_checkpoint &&
+       echo fileA.t >list &&
+       >empty_list &&
+
+       test_must_fail git add --pathspec-from-file=list --interactive 2>err &&
+       test_i18ngrep -e "--pathspec-from-file is incompatible with --interactive/--patch" err &&
+
+       test_must_fail git add --pathspec-from-file=list --patch 2>err &&
+       test_i18ngrep -e "--pathspec-from-file is incompatible with --interactive/--patch" err &&
+
+       test_must_fail git add --pathspec-from-file=list --edit 2>err &&
+       test_i18ngrep -e "--pathspec-from-file is incompatible with --edit" err &&
+
+       test_must_fail git add --pathspec-from-file=list -- fileA.t 2>err &&
+       test_i18ngrep -e "--pathspec-from-file is incompatible with pathspec arguments" err &&
+
+       test_must_fail git add --pathspec-file-nul 2>err &&
+       test_i18ngrep -e "--pathspec-file-nul requires --pathspec-from-file" err &&
+
+       # This case succeeds, but still prints to stderr
+       git add --pathspec-from-file=empty_list 2>err &&
+       test_i18ngrep -e "Nothing specified, nothing added." err
+'
+
 test_done
index c0f48395432539f807dd9474d71529378f71306b..02255a08bf1718659f9815e37644c07a196ab850 100755 (executable)
@@ -106,7 +106,6 @@ do
                result=success
        fi
        test_expect_$result "hunk header: $i" "
-               test_when_finished 'cat actual' &&      # for debugging only
                git diff -U1 $i >actual &&
                grep '@@ .* @@.*RIGHT' actual
        "
index fcae82fffa7f74a65fc6b475cc5c3eeefda3029a..8c95f152b23b242df5f5aaa0b5b25e6e1826f753 100755 (executable)
@@ -4,8 +4,9 @@ test_description='test diff with a bogus tree containing the null sha1'
 . ./test-lib.sh
 
 test_expect_success 'create bogus tree' '
+       name=$(echo $ZERO_OID | sed -e "s/00/Q/g") &&
        bogus_tree=$(
-               printf "100644 fooQQQQQQQQQQQQQQQQQQQQQ" |
+               printf "100644 fooQ$name" |
                q_to_nul |
                git hash-object -w --stdin -t tree
        )
index 5df6b5e64e0e76e776c337c985dcc1846dc40408..6331f63b127cc7a7ecf63000c99e2876b1ee4dfb 100755 (executable)
@@ -18,7 +18,7 @@ test_expect_success 'set up history with a merge' '
 '
 
 test_expect_success 'log --cc -p --stat --color-moved' '
-       cat >expect <<-\EOF &&
+       cat >expect <<-EOF &&
        commit D
        ---
         D.t | 1 +
@@ -26,7 +26,7 @@ test_expect_success 'log --cc -p --stat --color-moved' '
 
        diff --git a/D.t b/D.t
        new file mode 100644
-       index 0000000..1784810
+       index 0000000..$(git rev-parse --short D:D.t)
        --- /dev/null
        +++ b/D.t
        @@ -0,0 +1 @@
@@ -42,7 +42,7 @@ test_expect_success 'log --cc -p --stat --color-moved' '
 
        diff --git a/C.t b/C.t
        new file mode 100644
-       index 0000000..3cc58df
+       index 0000000..$(git rev-parse --short C:C.t)
        --- /dev/null
        +++ b/C.t
        @@ -0,0 +1 @@
@@ -54,7 +54,7 @@ test_expect_success 'log --cc -p --stat --color-moved' '
 
        diff --git a/B.t b/B.t
        new file mode 100644
-       index 0000000..223b783
+       index 0000000..$(git rev-parse --short B:B.t)
        --- /dev/null
        +++ b/B.t
        @@ -0,0 +1 @@
@@ -66,7 +66,7 @@ test_expect_success 'log --cc -p --stat --color-moved' '
 
        diff --git a/A.t b/A.t
        new file mode 100644
-       index 0000000..f70f10e
+       index 0000000..$(git rev-parse --short A:A.t)
        --- /dev/null
        +++ b/A.t
        @@ -0,0 +1 @@
index ff51e9e78914e4b9c7a801b4f88f3691b5f2844d..971a5a7512ac772f8b7e25a6a75bb4ac2ca43fd3 100755 (executable)
@@ -35,9 +35,15 @@ prepare_test_file () {
 }
 
 apply_patch () {
+       cmd_prefix= &&
+       if test "x$1" = 'x!'
+       then
+               cmd_prefix=test_must_fail &&
+               shift
+       fi &&
        >target &&
        sed -e "s|\([ab]\)/file|\1/target|" <patch |
-       git apply "$@"
+       $cmd_prefix git apply "$@"
 }
 
 test_fix () {
@@ -99,7 +105,7 @@ test_expect_success 'whitespace=warn, default rule' '
 
 test_expect_success 'whitespace=error-all, default rule' '
 
-       test_must_fail apply_patch --whitespace=error-all &&
+       apply_patch ! --whitespace=error-all &&
        test_must_be_empty target
 
 '
index 0043930ca6ab31f6cd6a0bf6464ceb281663f9e8..99ed4cc54676de6d56a2caa6a8f38d2c834700f9 100755 (executable)
@@ -8,6 +8,7 @@ test_description='git apply submodule tests'
 . ./test-lib.sh
 
 test_expect_success setup '
+       test_oid_init &&
        cat > create-sm.patch <<EOF &&
 diff --git a/dir/sm b/dir/sm
 new file mode 160000
@@ -15,7 +16,7 @@ index 0000000..0123456
 --- /dev/null
 +++ b/dir/sm
 @@ -0,0 +1 @@
-+Subproject commit 0123456789abcdef0123456789abcdef01234567
++Subproject commit $(test_oid numeric)
 EOF
        cat > remove-sm.patch <<EOF
 diff --git a/dir/sm b/dir/sm
@@ -24,7 +25,7 @@ index 0123456..0000000
 --- a/dir/sm
 +++ /dev/null
 @@ -1 +0,0 @@
--Subproject commit 0123456789abcdef0123456789abcdef01234567
+-Subproject commit $(test_oid numeric)
 EOF
 '
 
index 55b7750ade1c5fd225f830bbbfcb42938abcbe37..831d424c4720924275b9071d4ad5edf8fc73734a 100755 (executable)
@@ -25,6 +25,7 @@ test_description='git rerere
 . ./test-lib.sh
 
 test_expect_success 'setup' '
+       test_oid_init &&
        cat >a1 <<-\EOF &&
        Some title
        ==========
@@ -210,7 +211,7 @@ test_expect_success 'set up for garbage collection tests' '
        echo Hello >$rr/preimage &&
        echo World >$rr/postimage &&
 
-       sha2=4000000000000000000000000000000000000000 &&
+       sha2=$(test_oid deadbeef) &&
        rr2=.git/rr-cache/$sha2 &&
        mkdir $rr2 &&
        echo Hello >$rr2/preimage &&
index 2c9489484a922b03afb8249631e8d30ad53dcbd5..192347a3e1fa50af7e33ef1172c2fbf61120cfa0 100755 (executable)
@@ -87,12 +87,12 @@ test_expect_success 'format %w(,1,2)' '
 '
 
 cat > expect << EOF
-804a787 sixth
-394ef78 fifth
-5d31159 fourth
-2fbe8c0 third
-f7dab8e second
-3a2fdcb initial
+$(git rev-parse --short :/sixth  ) sixth
+$(git rev-parse --short :/fifth  ) fifth
+$(git rev-parse --short :/fourth ) fourth
+$(git rev-parse --short :/third  ) third
+$(git rev-parse --short :/second ) second
+$(git rev-parse --short :/initial) initial
 EOF
 test_expect_success 'oneline' '
 
@@ -173,43 +173,45 @@ test_expect_success 'git config log.follow is overridden by --no-follow' '
        verbose test "$actual" = "$expect"
 '
 
+# Note that these commits are intentionally listed out of order.
+last_three="$(git rev-parse :/fourth :/sixth :/fifth)"
 cat > expect << EOF
-804a787 sixth
-394ef78 fifth
-5d31159 fourth
+$(git rev-parse --short :/sixth ) sixth
+$(git rev-parse --short :/fifth ) fifth
+$(git rev-parse --short :/fourth) fourth
 EOF
 test_expect_success 'git log --no-walk <commits> sorts by commit time' '
-       git log --no-walk --oneline 5d31159 804a787 394ef78 > actual &&
+       git log --no-walk --oneline $last_three > actual &&
        test_cmp expect actual
 '
 
 test_expect_success 'git log --no-walk=sorted <commits> sorts by commit time' '
-       git log --no-walk=sorted --oneline 5d31159 804a787 394ef78 > actual &&
+       git log --no-walk=sorted --oneline $last_three > actual &&
        test_cmp expect actual
 '
 
 cat > expect << EOF
-=== 804a787 sixth
-=== 394ef78 fifth
-=== 5d31159 fourth
+=== $(git rev-parse --short :/sixth ) sixth
+=== $(git rev-parse --short :/fifth ) fifth
+=== $(git rev-parse --short :/fourth) fourth
 EOF
 test_expect_success 'git log --line-prefix="=== " --no-walk <commits> sorts by commit time' '
-       git log --line-prefix="=== " --no-walk --oneline 5d31159 804a787 394ef78 > actual &&
+       git log --line-prefix="=== " --no-walk --oneline $last_three > actual &&
        test_cmp expect actual
 '
 
 cat > expect << EOF
-5d31159 fourth
-804a787 sixth
-394ef78 fifth
+$(git rev-parse --short :/fourth) fourth
+$(git rev-parse --short :/sixth ) sixth
+$(git rev-parse --short :/fifth ) fifth
 EOF
 test_expect_success 'git log --no-walk=unsorted <commits> leaves list of commits as given' '
-       git log --no-walk=unsorted --oneline 5d31159 804a787 394ef78 > actual &&
+       git log --no-walk=unsorted --oneline $last_three > actual &&
        test_cmp expect actual
 '
 
 test_expect_success 'git show <commits> leaves list of commits as given' '
-       git show --oneline -s 5d31159 804a787 394ef78 > actual &&
+       git show --oneline -s $last_three > actual &&
        test_cmp expect actual
 '
 
@@ -957,7 +959,7 @@ cat >expect <<\EOF
 | |
 | | diff --git a/reach.t b/reach.t
 | | new file mode 100644
-| | index 0000000..10c9591
+| | index BEFORE..AFTER
 | | --- /dev/null
 | | +++ b/reach.t
 | | @@ -0,0 +1 @@
@@ -980,7 +982,7 @@ cat >expect <<\EOF
 | | |
 | | |   diff --git a/octopus-b.t b/octopus-b.t
 | | |   new file mode 100644
-| | |   index 0000000..d5fcad0
+| | |   index BEFORE..AFTER
 | | |   --- /dev/null
 | | |   +++ b/octopus-b.t
 | | |   @@ -0,0 +1 @@
@@ -996,7 +998,7 @@ cat >expect <<\EOF
 | |
 | |   diff --git a/octopus-a.t b/octopus-a.t
 | |   new file mode 100644
-| |   index 0000000..11ee015
+| |   index BEFORE..AFTER
 | |   --- /dev/null
 | |   +++ b/octopus-a.t
 | |   @@ -0,0 +1 @@
@@ -1012,7 +1014,7 @@ cat >expect <<\EOF
 |
 |   diff --git a/seventh.t b/seventh.t
 |   new file mode 100644
-|   index 0000000..9744ffc
+|   index BEFORE..AFTER
 |   --- /dev/null
 |   +++ b/seventh.t
 |   @@ -0,0 +1 @@
@@ -1046,7 +1048,7 @@ cat >expect <<\EOF
 | | | |
 | | | | diff --git a/tangle-a b/tangle-a
 | | | | new file mode 100644
-| | | | index 0000000..7898192
+| | | | index BEFORE..AFTER
 | | | | --- /dev/null
 | | | | +++ b/tangle-a
 | | | | @@ -0,0 +1 @@
@@ -1068,7 +1070,7 @@ cat >expect <<\EOF
 | | | |
 | | | |   diff --git a/2 b/2
 | | | |   new file mode 100644
-| | | |   index 0000000..0cfbf08
+| | | |   index BEFORE..AFTER
 | | | |   --- /dev/null
 | | | |   +++ b/2
 | | | |   @@ -0,0 +1 @@
@@ -1084,7 +1086,7 @@ cat >expect <<\EOF
 | | | |
 | | | | diff --git a/1 b/1
 | | | | new file mode 100644
-| | | | index 0000000..d00491f
+| | | | index BEFORE..AFTER
 | | | | --- /dev/null
 | | | | +++ b/1
 | | | | @@ -0,0 +1 @@
@@ -1100,7 +1102,7 @@ cat >expect <<\EOF
 | | | |
 | | | | diff --git a/one b/one
 | | | | new file mode 100644
-| | | | index 0000000..9a33383
+| | | | index BEFORE..AFTER
 | | | | --- /dev/null
 | | | | +++ b/one
 | | | | @@ -0,0 +1 @@
@@ -1116,7 +1118,7 @@ cat >expect <<\EOF
 | | |
 | | |   diff --git a/a/two b/a/two
 | | |   deleted file mode 100644
-| | |   index 9245af5..0000000
+| | |   index BEFORE..AFTER
 | | |   --- a/a/two
 | | |   +++ /dev/null
 | | |   @@ -1 +0,0 @@
@@ -1132,7 +1134,7 @@ cat >expect <<\EOF
 | | |
 | | | diff --git a/a/two b/a/two
 | | | new file mode 100644
-| | | index 0000000..9245af5
+| | | index BEFORE..AFTER
 | | | --- /dev/null
 | | | +++ b/a/two
 | | | @@ -0,0 +1 @@
@@ -1148,7 +1150,7 @@ cat >expect <<\EOF
 | |
 | |   diff --git a/ein b/ein
 | |   new file mode 100644
-| |   index 0000000..9d7e69f
+| |   index BEFORE..AFTER
 | |   --- /dev/null
 | |   +++ b/ein
 | |   @@ -0,0 +1 @@
@@ -1165,14 +1167,14 @@ cat >expect <<\EOF
 |
 |   diff --git a/ichi b/ichi
 |   new file mode 100644
-|   index 0000000..9d7e69f
+|   index BEFORE..AFTER
 |   --- /dev/null
 |   +++ b/ichi
 |   @@ -0,0 +1 @@
 |   +ichi
 |   diff --git a/one b/one
 |   deleted file mode 100644
-|   index 9d7e69f..0000000
+|   index BEFORE..AFTER
 |   --- a/one
 |   +++ /dev/null
 |   @@ -1 +0,0 @@
@@ -1187,7 +1189,7 @@ cat >expect <<\EOF
 |  1 file changed, 1 insertion(+), 1 deletion(-)
 |
 | diff --git a/one b/one
-| index 5626abf..9d7e69f 100644
+| index BEFORE..AFTER 100644
 | --- a/one
 | +++ b/one
 | @@ -1 +1 @@
@@ -1204,7 +1206,7 @@ cat >expect <<\EOF
 
   diff --git a/one b/one
   new file mode 100644
-  index 0000000..5626abf
+  index BEFORE..AFTER
   --- /dev/null
   +++ b/one
   @@ -0,0 +1 @@
@@ -1221,7 +1223,8 @@ sanitize_output () {
            -e 's/, 0 insertions(+)//' \
            -e 's/ 1 files changed, / 1 file changed, /' \
            -e 's/, 1 deletions(-)/, 1 deletion(-)/' \
-           -e 's/, 1 insertions(+)/, 1 insertion(+)/'
+           -e 's/, 1 insertions(+)/, 1 insertion(+)/' \
+           -e 's/index [0-9a-f]*\.\.[0-9a-f]*/index BEFORE..AFTER/'
 }
 
 test_expect_success 'log --graph with diff and stats' '
@@ -1247,7 +1250,7 @@ cat >expect <<\EOF
 *** | |
 *** | | diff --git a/reach.t b/reach.t
 *** | | new file mode 100644
-*** | | index 0000000..10c9591
+*** | | index BEFORE..AFTER
 *** | | --- /dev/null
 *** | | +++ b/reach.t
 *** | | @@ -0,0 +1 @@
@@ -1270,7 +1273,7 @@ cat >expect <<\EOF
 *** | | |
 *** | | |   diff --git a/octopus-b.t b/octopus-b.t
 *** | | |   new file mode 100644
-*** | | |   index 0000000..d5fcad0
+*** | | |   index BEFORE..AFTER
 *** | | |   --- /dev/null
 *** | | |   +++ b/octopus-b.t
 *** | | |   @@ -0,0 +1 @@
@@ -1286,7 +1289,7 @@ cat >expect <<\EOF
 *** | |
 *** | |   diff --git a/octopus-a.t b/octopus-a.t
 *** | |   new file mode 100644
-*** | |   index 0000000..11ee015
+*** | |   index BEFORE..AFTER
 *** | |   --- /dev/null
 *** | |   +++ b/octopus-a.t
 *** | |   @@ -0,0 +1 @@
@@ -1302,7 +1305,7 @@ cat >expect <<\EOF
 *** |
 *** |   diff --git a/seventh.t b/seventh.t
 *** |   new file mode 100644
-*** |   index 0000000..9744ffc
+*** |   index BEFORE..AFTER
 *** |   --- /dev/null
 *** |   +++ b/seventh.t
 *** |   @@ -0,0 +1 @@
@@ -1336,7 +1339,7 @@ cat >expect <<\EOF
 *** | | | |
 *** | | | | diff --git a/tangle-a b/tangle-a
 *** | | | | new file mode 100644
-*** | | | | index 0000000..7898192
+*** | | | | index BEFORE..AFTER
 *** | | | | --- /dev/null
 *** | | | | +++ b/tangle-a
 *** | | | | @@ -0,0 +1 @@
@@ -1358,7 +1361,7 @@ cat >expect <<\EOF
 *** | | | |
 *** | | | |   diff --git a/2 b/2
 *** | | | |   new file mode 100644
-*** | | | |   index 0000000..0cfbf08
+*** | | | |   index BEFORE..AFTER
 *** | | | |   --- /dev/null
 *** | | | |   +++ b/2
 *** | | | |   @@ -0,0 +1 @@
@@ -1374,7 +1377,7 @@ cat >expect <<\EOF
 *** | | | |
 *** | | | | diff --git a/1 b/1
 *** | | | | new file mode 100644
-*** | | | | index 0000000..d00491f
+*** | | | | index BEFORE..AFTER
 *** | | | | --- /dev/null
 *** | | | | +++ b/1
 *** | | | | @@ -0,0 +1 @@
@@ -1390,7 +1393,7 @@ cat >expect <<\EOF
 *** | | | |
 *** | | | | diff --git a/one b/one
 *** | | | | new file mode 100644
-*** | | | | index 0000000..9a33383
+*** | | | | index BEFORE..AFTER
 *** | | | | --- /dev/null
 *** | | | | +++ b/one
 *** | | | | @@ -0,0 +1 @@
@@ -1406,7 +1409,7 @@ cat >expect <<\EOF
 *** | | |
 *** | | |   diff --git a/a/two b/a/two
 *** | | |   deleted file mode 100644
-*** | | |   index 9245af5..0000000
+*** | | |   index BEFORE..AFTER
 *** | | |   --- a/a/two
 *** | | |   +++ /dev/null
 *** | | |   @@ -1 +0,0 @@
@@ -1422,7 +1425,7 @@ cat >expect <<\EOF
 *** | | |
 *** | | | diff --git a/a/two b/a/two
 *** | | | new file mode 100644
-*** | | | index 0000000..9245af5
+*** | | | index BEFORE..AFTER
 *** | | | --- /dev/null
 *** | | | +++ b/a/two
 *** | | | @@ -0,0 +1 @@
@@ -1438,7 +1441,7 @@ cat >expect <<\EOF
 *** | |
 *** | |   diff --git a/ein b/ein
 *** | |   new file mode 100644
-*** | |   index 0000000..9d7e69f
+*** | |   index BEFORE..AFTER
 *** | |   --- /dev/null
 *** | |   +++ b/ein
 *** | |   @@ -0,0 +1 @@
@@ -1455,14 +1458,14 @@ cat >expect <<\EOF
 *** |
 *** |   diff --git a/ichi b/ichi
 *** |   new file mode 100644
-*** |   index 0000000..9d7e69f
+*** |   index BEFORE..AFTER
 *** |   --- /dev/null
 *** |   +++ b/ichi
 *** |   @@ -0,0 +1 @@
 *** |   +ichi
 *** |   diff --git a/one b/one
 *** |   deleted file mode 100644
-*** |   index 9d7e69f..0000000
+*** |   index BEFORE..AFTER
 *** |   --- a/one
 *** |   +++ /dev/null
 *** |   @@ -1 +0,0 @@
@@ -1477,7 +1480,7 @@ cat >expect <<\EOF
 *** |  1 file changed, 1 insertion(+), 1 deletion(-)
 *** |
 *** | diff --git a/one b/one
-*** | index 5626abf..9d7e69f 100644
+*** | index BEFORE..AFTER 100644
 *** | --- a/one
 *** | +++ b/one
 *** | @@ -1 +1 @@
@@ -1494,7 +1497,7 @@ cat >expect <<\EOF
 ***
 ***   diff --git a/one b/one
 ***   new file mode 100644
-***   index 0000000..5626abf
+***   index BEFORE..AFTER
 ***   --- /dev/null
 ***   +++ b/one
 ***   @@ -0,0 +1 @@
@@ -1709,10 +1712,10 @@ test_expect_success 'set up --source tests' '
 '
 
 test_expect_success 'log --source paints branch names' '
-       cat >expect <<-\EOF &&
-       09e12a9 source-b three
-       8e393e1 source-a two
-       1ac6c77 source-b one
+       cat >expect <<-EOF &&
+       $(git rev-parse --short :/three)        source-b three
+       $(git rev-parse --short :/two  )        source-a two
+       $(git rev-parse --short :/one  )        source-b one
        EOF
        git log --oneline --source source-a source-b >actual &&
        test_cmp expect actual
@@ -1720,19 +1723,19 @@ test_expect_success 'log --source paints branch names' '
 
 test_expect_success 'log --source paints tag names' '
        git tag -m tagged source-tag &&
-       cat >expect <<-\EOF &&
-       09e12a9 source-tag three
-       8e393e1 source-a two
-       1ac6c77 source-tag one
+       cat >expect <<-EOF &&
+       $(git rev-parse --short :/three)        source-tag three
+       $(git rev-parse --short :/two  )        source-a two
+       $(git rev-parse --short :/one  )        source-tag one
        EOF
        git log --oneline --source source-tag source-a >actual &&
        test_cmp expect actual
 '
 
 test_expect_success 'log --source paints symmetric ranges' '
-       cat >expect <<-\EOF &&
-       09e12a9 source-b three
-       8e393e1 source-a two
+       cat >expect <<-EOF &&
+       $(git rev-parse --short :/three)        source-b three
+       $(git rev-parse --short :/two  )        source-a two
        EOF
        git log --oneline --source source-a...source-b >actual &&
        test_cmp expect actual
index 0288c17ec60b803d2815fb1b704c35f74e4a7753..8ff8bd84c75e0fd4fd4c75e911b6abfca18a70fc 100755 (executable)
@@ -25,7 +25,7 @@ test_expect_success 'setup' '
 
 test_expect_success 'patch-id output is well-formed' '
        git log -p -1 | git patch-id >output &&
-       grep "^[a-f0-9]\{40\} $(git rev-parse HEAD)$" output
+       grep "^$OID_REGEX $(git rev-parse HEAD)$" output
 '
 
 #calculate patch id. Make sure output is not empty.
index 5661ed5881f4831a36637cfd2dd96f1a9c19e504..1d0d3240ff69c9e8b5261939a8fa903de65cabd5 100755 (executable)
@@ -311,4 +311,66 @@ test_expect_success 'log --graph with multiple tips and colors' '
        test_cmp expect.colors actual.colors
 '
 
+test_expect_success 'log --graph with multiple tips' '
+       git checkout --orphan 7_1 &&
+       test_commit 7_A &&
+       test_commit 7_B &&
+       test_commit 7_C &&
+       git checkout -b 7_2 7_1~2 &&
+       test_commit 7_D &&
+       test_commit 7_E &&
+       git checkout -b 7_3 7_1~1 &&
+       test_commit 7_F &&
+       test_commit 7_G &&
+       git checkout -b 7_4 7_2~1 &&
+       test_commit 7_H &&
+       git checkout -b 7_5 7_1~2 &&
+       test_commit 7_I &&
+       git checkout -b 7_6 7_3~1 &&
+       test_commit 7_J &&
+       git checkout -b M_1 7_1 &&
+       git merge --no-ff 7_2 -m 7_M1 &&
+       git checkout -b M_3 7_3 &&
+       git merge --no-ff 7_4 -m 7_M2 &&
+       git checkout -b M_5 7_5 &&
+       git merge --no-ff 7_6 -m 7_M3 &&
+       git checkout -b M_7 7_1 &&
+       git merge --no-ff 7_2 7_3 -m 7_M4 &&
+
+       check_graph M_1 M_3 M_5 M_7 <<-\EOF
+       *   7_M1
+       |\
+       | | *   7_M2
+       | | |\
+       | | | * 7_H
+       | | | | *   7_M3
+       | | | | |\
+       | | | | | * 7_J
+       | | | | * | 7_I
+       | | | | | | *   7_M4
+       | |_|_|_|_|/|\
+       |/| | | | |/ /
+       | | |_|_|/| /
+       | |/| | | |/
+       | | | |_|/|
+       | | |/| | |
+       | | * | | | 7_G
+       | | | |_|/
+       | | |/| |
+       | | * | | 7_F
+       | * | | | 7_E
+       | | |/ /
+       | |/| |
+       | * | | 7_D
+       | | |/
+       | |/|
+       * | | 7_C
+       | |/
+       |/|
+       * | 7_B
+       |/
+       * 7_A
+       EOF
+'
+
 test_done
index d87cc7d9efde9d728825ae84b4a329b720b6d821..e59601e5fe9b75eda9f4daaa801c1ed6c40d6fc6 100755 (executable)
@@ -11,16 +11,16 @@ test_expect_success setup '
 '
 
 test_expect_success 'file add A, !B' '
-       cat >expected <<\EXPECTED &&
+       git reset --hard initial &&
+       test_commit "add-a-not-b" "ONE" "AAA" &&
+       git merge-tree initial initial add-a-not-b >actual &&
+       cat >expected <<EXPECTED &&
 added in remote
-  their  100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+  their  100644 $(git rev-parse HEAD:ONE) ONE
 @@ -0,0 +1 @@
 +AAA
 EXPECTED
 
-       git reset --hard initial &&
-       test_commit "add-a-not-b" "ONE" "AAA" &&
-       git merge-tree initial initial add-a-not-b >actual &&
        test_cmp expected actual
 '
 
@@ -41,10 +41,15 @@ test_expect_success 'file add A, B (same)' '
 '
 
 test_expect_success 'file add A, B (different)' '
-       cat >expected <<\EXPECTED &&
+       git reset --hard initial &&
+       test_commit "add-a-b-diff-A" "ONE" "AAA" &&
+       git reset --hard initial &&
+       test_commit "add-a-b-diff-B" "ONE" "BBB" &&
+       git merge-tree initial add-a-b-diff-A add-a-b-diff-B >actual &&
+       cat >expected <<EXPECTED &&
 added in both
-  our    100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
-  their  100644 ba629238ca89489f2b350e196ca445e09d8bb834 ONE
+  our    100644 $(git rev-parse add-a-b-diff-A:ONE) ONE
+  their  100644 $(git rev-parse add-a-b-diff-B:ONE) ONE
 @@ -1 +1,5 @@
 +<<<<<<< .our
  AAA
@@ -53,11 +58,6 @@ added in both
 +>>>>>>> .their
 EXPECTED
 
-       git reset --hard initial &&
-       test_commit "add-a-b-diff-A" "ONE" "AAA" &&
-       git reset --hard initial &&
-       test_commit "add-a-b-diff-B" "ONE" "BBB" &&
-       git merge-tree initial add-a-b-diff-A add-a-b-diff-B >actual &&
        test_cmp expected actual
 '
 
@@ -69,18 +69,18 @@ test_expect_success 'file change A, !B' '
 '
 
 test_expect_success 'file change !A, B' '
-       cat >expected <<\EXPECTED &&
+       git reset --hard initial &&
+       test_commit "change-not-a-b" "initial-file" "BBB" &&
+       git merge-tree initial initial change-not-a-b >actual &&
+       cat >expected <<EXPECTED &&
 merged
-  result 100644 ba629238ca89489f2b350e196ca445e09d8bb834 initial-file
-  our    100644 e79c5e8f964493290a409888d5413a737e8e5dd5 initial-file
+  result 100644 $(git rev-parse change-a-not-b:initial-file) initial-file
+  our    100644 $(git rev-parse initial:initial-file       ) initial-file
 @@ -1 +1 @@
 -initial
 +BBB
 EXPECTED
 
-       git reset --hard initial &&
-       test_commit "change-not-a-b" "initial-file" "BBB" &&
-       git merge-tree initial initial change-not-a-b >actual &&
        test_cmp expected actual
 '
 
@@ -94,11 +94,16 @@ test_expect_success 'file change A, B (same)' '
 '
 
 test_expect_success 'file change A, B (different)' '
-       cat >expected <<\EXPECTED &&
+       git reset --hard initial &&
+       test_commit "change-a-b-diff-A" "initial-file" "AAA" &&
+       git reset --hard initial &&
+       test_commit "change-a-b-diff-B" "initial-file" "BBB" &&
+       git merge-tree initial change-a-b-diff-A change-a-b-diff-B >actual &&
+       cat >expected <<EXPECTED &&
 changed in both
-  base   100644 e79c5e8f964493290a409888d5413a737e8e5dd5 initial-file
-  our    100644 43d5a8ed6ef6c00ff775008633f95787d088285d initial-file
-  their  100644 ba629238ca89489f2b350e196ca445e09d8bb834 initial-file
+  base   100644 $(git rev-parse initial:initial-file          ) initial-file
+  our    100644 $(git rev-parse change-a-b-diff-A:initial-file) initial-file
+  their  100644 $(git rev-parse change-a-b-diff-B:initial-file) initial-file
 @@ -1 +1,5 @@
 +<<<<<<< .our
  AAA
@@ -107,34 +112,10 @@ changed in both
 +>>>>>>> .their
 EXPECTED
 
-       git reset --hard initial &&
-       test_commit "change-a-b-diff-A" "initial-file" "AAA" &&
-       git reset --hard initial &&
-       test_commit "change-a-b-diff-B" "initial-file" "BBB" &&
-       git merge-tree initial change-a-b-diff-A change-a-b-diff-B >actual &&
        test_cmp expected actual
 '
 
 test_expect_success 'file change A, B (mixed)' '
-       cat >expected <<\EXPECTED &&
-changed in both
-  base   100644 f4f1f998c7776568c4ff38f516d77fef9399b5a7 ONE
-  our    100644 af14c2c3475337c73759d561ef70b59e5c731176 ONE
-  their  100644 372d761493f524d44d59bd24700c3bdf914c973c ONE
-@@ -7,7 +7,11 @@
- AAA
- AAA
- AAA
-+<<<<<<< .our
- BBB
-+=======
-+CCC
-+>>>>>>> .their
- AAA
- AAA
- AAA
-EXPECTED
-
        git reset --hard initial &&
        test_commit "change-a-b-mix-base" "ONE" "
 AAA
@@ -159,6 +140,26 @@ AAA" &&
                "$(sed -e "1{s/AAA/BBB/;}" -e "10{s/AAA/CCC/;}" <ONE)" &&
        git merge-tree change-a-b-mix-base change-a-b-mix-A change-a-b-mix-B \
                >actual &&
+
+       cat >expected <<EXPECTED &&
+changed in both
+  base   100644 $(git rev-parse change-a-b-mix-base:ONE) ONE
+  our    100644 $(git rev-parse change-a-b-mix-A:ONE   ) ONE
+  their  100644 $(git rev-parse change-a-b-mix-B:ONE   ) ONE
+@@ -7,7 +7,11 @@
+ AAA
+ AAA
+ AAA
++<<<<<<< .our
+ BBB
++=======
++CCC
++>>>>>>> .their
+ AAA
+ AAA
+ AAA
+EXPECTED
+
        test_cmp expected actual
 '
 
@@ -173,20 +174,20 @@ test_expect_success 'file remove A, !B' '
 '
 
 test_expect_success 'file remove !A, B' '
-       cat >expected <<\EXPECTED &&
-removed in remote
-  base   100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
-  our    100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
-@@ -1 +0,0 @@
--AAA
-EXPECTED
-
        git reset --hard initial &&
        test_commit "rm-not-a-b-base" "ONE" "AAA" &&
        git rm ONE &&
        git commit -m "rm-not-a-b" &&
        git tag "rm-not-a-b" &&
        git merge-tree rm-a-not-b-base rm-a-not-b-base rm-a-not-b >actual &&
+       cat >expected <<EXPECTED &&
+removed in remote
+  base   100644 $(git rev-parse rm-a-not-b-base:ONE) ONE
+  our    100644 $(git rev-parse rm-a-not-b-base:ONE) ONE
+@@ -1 +0,0 @@
+-AAA
+EXPECTED
+
        test_cmp expected actual
 '
 
@@ -201,14 +202,6 @@ test_expect_success 'file remove A, B (same)' '
 '
 
 test_expect_success 'file change A, remove B' '
-       cat >expected <<\EXPECTED &&
-removed in remote
-  base   100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
-  our    100644 ba629238ca89489f2b350e196ca445e09d8bb834 ONE
-@@ -1 +0,0 @@
--BBB
-EXPECTED
-
        git reset --hard initial &&
        test_commit "change-a-rm-b-base" "ONE" "AAA" &&
        test_commit "change-a-rm-b-A" "ONE" "BBB" &&
@@ -218,16 +211,18 @@ EXPECTED
        git tag "change-a-rm-b-B" &&
        git merge-tree change-a-rm-b-base change-a-rm-b-A change-a-rm-b-B \
                >actual &&
+       cat >expected <<EXPECTED &&
+removed in remote
+  base   100644 $(git rev-parse change-a-rm-b-base:ONE) ONE
+  our    100644 $(git rev-parse change-a-rm-b-A:ONE   ) ONE
+@@ -1 +0,0 @@
+-BBB
+EXPECTED
+
        test_cmp expected actual
 '
 
 test_expect_success 'file remove A, change B' '
-       cat >expected <<\EXPECTED &&
-removed in local
-  base   100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
-  their  100644 ba629238ca89489f2b350e196ca445e09d8bb834 ONE
-EXPECTED
-
        git reset --hard initial &&
        test_commit "rm-a-change-b-base" "ONE" "AAA" &&
 
@@ -238,6 +233,11 @@ EXPECTED
        test_commit "rm-a-change-b-B" "ONE" "BBB" &&
        git merge-tree rm-a-change-b-base rm-a-change-b-A rm-a-change-b-B \
                >actual &&
+       cat >expected <<EXPECTED &&
+removed in local
+  base   100644 $(git rev-parse rm-a-change-b-base:ONE) ONE
+  their  100644 $(git rev-parse rm-a-change-b-B:ONE   ) ONE
+EXPECTED
        test_cmp expected actual
 '
 
@@ -250,10 +250,17 @@ test_expect_success 'tree add A, B (same)' '
 '
 
 test_expect_success 'tree add A, B (different)' '
-       cat >expect <<-\EOF &&
+       git reset --hard initial &&
+       mkdir sub &&
+       test_commit "add sub/file" "sub/file" "AAA" add-tree-a-b-A &&
+       git reset --hard initial &&
+       mkdir sub &&
+       test_commit "add sub/file" "sub/file" "BBB" add-tree-a-b-B &&
+       git merge-tree initial add-tree-a-b-A add-tree-a-b-B >actual &&
+       cat >expect <<-EOF &&
        added in both
-         our    100644 43d5a8ed6ef6c00ff775008633f95787d088285d sub/file
-         their  100644 ba629238ca89489f2b350e196ca445e09d8bb834 sub/file
+         our    100644 $(git rev-parse add-tree-a-b-A:sub/file) sub/file
+         their  100644 $(git rev-parse add-tree-a-b-B:sub/file) sub/file
        @@ -1 +1,5 @@
        +<<<<<<< .our
         AAA
@@ -261,24 +268,10 @@ test_expect_success 'tree add A, B (different)' '
        +BBB
        +>>>>>>> .their
        EOF
-       git reset --hard initial &&
-       mkdir sub &&
-       test_commit "add sub/file" "sub/file" "AAA" add-tree-a-b-A &&
-       git reset --hard initial &&
-       mkdir sub &&
-       test_commit "add sub/file" "sub/file" "BBB" add-tree-a-b-B &&
-       git merge-tree initial add-tree-a-b-A add-tree-a-b-B >actual &&
        test_cmp expect actual
 '
 
 test_expect_success 'tree unchanged A, removed B' '
-       cat >expect <<-\EOF &&
-       removed in remote
-         base   100644 43d5a8ed6ef6c00ff775008633f95787d088285d sub/file
-         our    100644 43d5a8ed6ef6c00ff775008633f95787d088285d sub/file
-       @@ -1 +0,0 @@
-       -AAA
-       EOF
        git reset --hard initial &&
        mkdir sub &&
        test_commit "add sub/file" "sub/file" "AAA" tree-remove-b-initial &&
@@ -287,6 +280,13 @@ test_expect_success 'tree unchanged A, removed B' '
        git commit -m "remove sub/file" &&
        git tag tree-remove-b-B &&
        git merge-tree tree-remove-b-initial tree-remove-b-initial tree-remove-b-B >actual &&
+       cat >expect <<-EOF &&
+       removed in remote
+         base   100644 $(git rev-parse tree-remove-b-initial:sub/file) sub/file
+         our    100644 $(git rev-parse tree-remove-b-initial:sub/file) sub/file
+       @@ -1 +0,0 @@
+       -AAA
+       EOF
        test_cmp expect actual
 '
 
@@ -296,14 +296,14 @@ test_expect_success 'turn file to tree' '
        mkdir initial-file &&
        test_commit "turn-file-to-tree" "initial-file/ONE" "CCC" &&
        git merge-tree initial initial turn-file-to-tree >actual &&
-       cat >expect <<-\EOF &&
+       cat >expect <<-EOF &&
        added in remote
-         their  100644 43aa4fdec31eb92e1fdc2f0ce6ea9ddb7c32bcf7 initial-file/ONE
+         their  100644 $(git rev-parse turn-file-to-tree:initial-file/ONE) initial-file/ONE
        @@ -0,0 +1 @@
        +CCC
        removed in remote
-         base   100644 e79c5e8f964493290a409888d5413a737e8e5dd5 initial-file
-         our    100644 e79c5e8f964493290a409888d5413a737e8e5dd5 initial-file
+         base   100644 $(git rev-parse initial:initial-file) initial-file
+         our    100644 $(git rev-parse initial:initial-file) initial-file
        @@ -1 +0,0 @@
        -initial
        EOF
@@ -318,14 +318,14 @@ test_expect_success 'turn tree to file' '
        rm -fr dir &&
        test_commit "make-file" "dir" "CCC" &&
        git merge-tree add-tree add-another-tree make-file >actual &&
-       cat >expect <<-\EOF &&
+       cat >expect <<-EOF &&
        removed in remote
-         base   100644 43d5a8ed6ef6c00ff775008633f95787d088285d dir/path
-         our    100644 43d5a8ed6ef6c00ff775008633f95787d088285d dir/path
+         base   100644 $(git rev-parse add-tree:dir/path) dir/path
+         our    100644 $(git rev-parse add-tree:dir/path) dir/path
        @@ -1 +0,0 @@
        -AAA
        added in remote
-         their  100644 43aa4fdec31eb92e1fdc2f0ce6ea9ddb7c32bcf7 dir
+         their  100644 $(git rev-parse make-file:dir) dir
        @@ -0,0 +1 @@
        +CCC
        EOF
index 3f03de60185738a07f42e291eae649598930d81c..81cf118cb6622b1984811b62df9dc7b9c957e1be 100755 (executable)
@@ -481,7 +481,7 @@ test_expect_success 'detect bad version' '
 '
 
 test_expect_success 'detect bad hash version' '
-       corrupt_graph_and_verify $GRAPH_BYTE_HASH "\02" \
+       corrupt_graph_and_verify $GRAPH_BYTE_HASH "\03" \
                "hash version"
 '
 
index cd2f87be6afe241a35e7c3b20fac09d10908f633..43a7a66c9d1b50640775d6b38df7a31b32e8e548 100755 (executable)
@@ -28,6 +28,20 @@ midx_read_expect () {
        test_cmp expect actual
 }
 
+test_expect_success 'setup' '
+       test_oid_init &&
+       test_oid_cache <<-EOF
+       idxoff sha1:2999
+       idxoff sha256:3739
+
+       packnameoff sha1:652
+       packnameoff sha256:940
+
+       fanoutoff sha1:1
+       fanoutoff sha256:3
+       EOF
+'
+
 test_expect_success 'write midx with no packs' '
        test_when_finished rm -f pack/multi-pack-index &&
        git multi-pack-index --object-dir=. write &&
@@ -225,7 +239,7 @@ test_expect_success 'verify bad signature' '
                "multi-pack-index signature"
 '
 
-HASH_LEN=20
+HASH_LEN=$(test_oid rawsz)
 NUM_OBJECTS=74
 MIDX_BYTE_VERSION=4
 MIDX_BYTE_OID_VERSION=5
@@ -238,9 +252,9 @@ MIDX_CHUNK_LOOKUP_WIDTH=12
 MIDX_OFFSET_PACKNAMES=$(($MIDX_HEADER_SIZE + \
                         $MIDX_NUM_CHUNKS * $MIDX_CHUNK_LOOKUP_WIDTH))
 MIDX_BYTE_PACKNAME_ORDER=$(($MIDX_OFFSET_PACKNAMES + 2))
-MIDX_OFFSET_OID_FANOUT=$(($MIDX_OFFSET_PACKNAMES + 652))
+MIDX_OFFSET_OID_FANOUT=$(($MIDX_OFFSET_PACKNAMES + $(test_oid packnameoff)))
 MIDX_OID_FANOUT_WIDTH=4
-MIDX_BYTE_OID_FANOUT_ORDER=$((MIDX_OFFSET_OID_FANOUT + 250 * $MIDX_OID_FANOUT_WIDTH + 1))
+MIDX_BYTE_OID_FANOUT_ORDER=$((MIDX_OFFSET_OID_FANOUT + 250 * $MIDX_OID_FANOUT_WIDTH + $(test_oid fanoutoff)))
 MIDX_OFFSET_OID_LOOKUP=$(($MIDX_OFFSET_OID_FANOUT + 256 * $MIDX_OID_FANOUT_WIDTH))
 MIDX_BYTE_OID_LOOKUP=$(($MIDX_OFFSET_OID_LOOKUP + 16 * $HASH_LEN))
 MIDX_OFFSET_OBJECT_OFFSETS=$(($MIDX_OFFSET_OID_LOOKUP + $NUM_OBJECTS * $HASH_LEN))
@@ -304,12 +318,12 @@ test_expect_success 'verify incorrect pack-int-id' '
 '
 
 test_expect_success 'verify incorrect offset' '
-       corrupt_midx_and_verify $MIDX_BYTE_OFFSET "\07" $objdir \
+       corrupt_midx_and_verify $MIDX_BYTE_OFFSET "\377" $objdir \
                "incorrect object offset"
 '
 
 test_expect_success 'git-fsck incorrect offset' '
-       corrupt_midx_and_verify $MIDX_BYTE_OFFSET "\07" $objdir \
+       corrupt_midx_and_verify $MIDX_BYTE_OFFSET "\377" $objdir \
                "incorrect object offset" \
                "git -c core.multipackindex=true fsck"
 '
@@ -387,7 +401,7 @@ test_expect_success 'force some 64-bit offsets with pack-objects' '
        pack64=$(git pack-objects --index-version=2,0x40 objects64/pack/test-64 <obj-list) &&
        idx64=objects64/pack/test-64-$pack64.idx &&
        chmod u+w $idx64 &&
-       corrupt_data $idx64 2999 "\02" &&
+       corrupt_data $idx64 $(test_oid idxoff) "\02" &&
        midx64=$(git multi-pack-index --object-dir=objects64 write) &&
        midx_read_expect 1 63 5 objects64 " large-offsets"
 '
index c24823431f2314169874adc705188f40da4c1618..53b2e6b4555de7d7cc27c727e2e7a3d7a4ff3f5b 100755 (executable)
@@ -11,7 +11,14 @@ test_expect_success 'setup repo' '
        git config gc.writeCommitGraph false &&
        infodir=".git/objects/info" &&
        graphdir="$infodir/commit-graphs" &&
-       test_oid_init
+       test_oid_init &&
+       test_oid_cache <<-EOM
+       shallow sha1:1760
+       shallow sha256:2064
+
+       base sha1:1376
+       base sha256:1496
+       EOM
 '
 
 graph_read_expect() {
@@ -248,7 +255,7 @@ test_expect_success 'verify hashes along chain, even in shallow' '
                cd verify &&
                git commit-graph verify &&
                base_file=$graphdir/graph-$(head -n 1 $graphdir/commit-graph-chain).graph &&
-               corrupt_file "$base_file" 1760 "\01" &&
+               corrupt_file "$base_file" $(test_oid shallow) "\01" &&
                test_must_fail git commit-graph verify --shallow 2>test_err &&
                grep -v "^+" test_err >err &&
                test_i18ngrep "incorrect checksum" err
@@ -275,7 +282,7 @@ test_expect_success 'warn on base graph chunk incorrect' '
                cd base-chunk &&
                git commit-graph verify &&
                base_file=$graphdir/graph-$(tail -n 1 $graphdir/commit-graph-chain).graph &&
-               corrupt_file "$base_file" 1376 "\01" &&
+               corrupt_file "$base_file" $(test_oid base) "\01" &&
                git commit-graph verify --shallow 2>test_err &&
                grep -v "^+" test_err >err &&
                test_i18ngrep "commit-graph chain does not match" err
index 571d620aedbac122960b319130ebad64a49d4f5b..b84618c925fe014c1cf277da8825ec0a104c18ef 100755 (executable)
@@ -288,7 +288,7 @@ test_expect_success 'receive-pack de-dupes .have lines' '
        $shared .have
        EOF
 
-       GIT_TRACE_PACKET=$(pwd)/trace GIT_TEST_PROTOCOL_VERSION= \
+       GIT_TRACE_PACKET=$(pwd)/trace GIT_TEST_PROTOCOL_VERSION=0 \
            git push \
                --receive-pack="unset GIT_TRACE_PACKET; git-receive-pack" \
                fork HEAD:foo &&
index 6b97923964ef990a8112e753d138af386275eccb..baa1a99f45f8540b1c5c91841e9797e85e902b63 100755 (executable)
@@ -440,11 +440,12 @@ test_expect_success 'setup tests for the --stdin parameter' '
 '
 
 test_expect_success 'setup fetch refs from cmdline v[12]' '
+       cp -r client client0 &&
        cp -r client client1 &&
        cp -r client client2
 '
 
-for version in '' 1 2
+for version in '' 1 2
 do
        test_expect_success "protocol.version=$version fetch refs from cmdline" "
                (
@@ -638,7 +639,7 @@ test_expect_success 'fetch-pack cannot fetch a raw sha1 that is not advertised a
        git init client &&
        # Some protocol versions (e.g. 2) support fetching
        # unadvertised objects, so restrict this test to v0.
-       test_must_fail env GIT_TEST_PROTOCOL_VERSION= git -C client fetch-pack ../server \
+       test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 git -C client fetch-pack ../server \
                $(git -C server rev-parse refs/heads/master^) 2>err &&
        test_i18ngrep "Server does not allow request for unadvertised object" err
 '
@@ -917,7 +918,10 @@ test_expect_success 'filtering by size' '
        git -C client fetch-pack --filter=blob:limit=0 ../server HEAD &&
 
        # Ensure that object is not inadvertently fetched
-       test_must_fail git -C client cat-file -e $(git hash-object server/one.t)
+       commit=$(git -C server rev-parse HEAD) &&
+       blob=$(git hash-object server/one.t) &&
+       git -C client rev-list --objects --missing=allow-any "$commit" >oids &&
+       ! grep "$blob" oids
 '
 
 test_expect_success 'filtering by size has no effect if support for it is not advertised' '
@@ -929,7 +933,10 @@ test_expect_success 'filtering by size has no effect if support for it is not ad
        git -C client fetch-pack --filter=blob:limit=0 ../server HEAD 2> err &&
 
        # Ensure that object is fetched
-       git -C client cat-file -e $(git hash-object server/one.t) &&
+       commit=$(git -C server rev-parse HEAD) &&
+       blob=$(git hash-object server/one.t) &&
+       git -C client rev-list --objects --missing=allow-any "$commit" >oids &&
+       grep "$blob" oids &&
 
        test_i18ngrep "filtering not recognized by server" err
 '
@@ -951,9 +958,11 @@ fetch_filter_blob_limit_zero () {
        git -C client fetch --filter=blob:limit=0 origin HEAD:somewhere &&
 
        # Ensure that commit is fetched, but blob is not
-       test_config -C client extensions.partialclone "arbitrary string" &&
-       git -C client cat-file -e $(git -C "$SERVER" rev-parse two) &&
-       test_must_fail git -C client cat-file -e $(git hash-object "$SERVER/two.t")
+       commit=$(git -C "$SERVER" rev-parse two) &&
+       blob=$(git hash-object server/two.t) &&
+       git -C client rev-list --objects --missing=allow-any "$commit" >oids &&
+       grep "$commit" oids &&
+       ! grep "$blob" oids
 }
 
 test_expect_success 'fetch with --filter=blob:limit=0' '
index fdfe179b11885be7fdc49ed3732d0dfe5d3537bc..645b4c78d356971cdf096456d7e90463f58cd0e3 100755 (executable)
@@ -4,6 +4,7 @@ test_description='fetch/receive strict mode'
 . ./test-lib.sh
 
 test_expect_success 'setup and inject "corrupt or missing" object' '
+       test_oid_init &&
        echo hello >greetings &&
        git add greetings &&
        git commit -m greetings &&
@@ -144,11 +145,11 @@ test_expect_success 'fsck with no skipList input' '
 
 test_expect_success 'setup sorted and unsorted skipLists' '
        cat >SKIP.unsorted <<-EOF &&
-       0000000000000000000000000000000000000004
-       0000000000000000000000000000000000000002
+       $(test_oid 004)
+       $(test_oid 002)
        $commit
-       0000000000000000000000000000000000000001
-       0000000000000000000000000000000000000003
+       $(test_oid 001)
+       $(test_oid 003)
        EOF
        sort SKIP.unsorted >SKIP.sorted
 '
@@ -172,14 +173,14 @@ test_expect_success 'fsck with invalid or bogus skipList input' '
 test_expect_success 'fsck with other accepted skipList input (comments & empty lines)' '
        cat >SKIP.with-comment <<-EOF &&
        # Some bad commit
-       0000000000000000000000000000000000000001
+       $(test_oid 001)
        EOF
        test_must_fail git -c fsck.skipList=SKIP.with-comment fsck 2>err-with-comment &&
        test_i18ngrep "missingEmail" err-with-comment &&
        cat >SKIP.with-empty-line <<-EOF &&
-       0000000000000000000000000000000000000001
+       $(test_oid 001)
 
-       0000000000000000000000000000000000000002
+       $(test_oid 002)
        EOF
        test_must_fail git -c fsck.skipList=SKIP.with-empty-line fsck 2>err-with-empty-line &&
        test_i18ngrep "missingEmail" err-with-empty-line
@@ -204,7 +205,7 @@ test_expect_success 'fsck with exhaustive accepted skipList input (various types
        echo " # Comment after whitespace" >>SKIP.exhaustive &&
        echo "$commit # Our bad commit (with leading whitespace and trailing comment)" >>SKIP.exhaustive &&
        echo "# Some bad commit (leading whitespace)" >>SKIP.exhaustive &&
-       echo "  0000000000000000000000000000000000000001" >>SKIP.exhaustive &&
+       echo "  $(test_oid 001)" >>SKIP.exhaustive &&
        git -c fsck.skipList=SKIP.exhaustive fsck 2>err &&
        test_must_be_empty err
 '
index 4b602826895e21154376391333c6517b4ec1ec54..566994cede3ef22c0c3927779a53d3923e3d6694 100755 (executable)
@@ -11,7 +11,7 @@ D=$(pwd)
 
 test_bundle_object_count () {
        git verify-pack -v "$1" >verify.out &&
-       test "$2" = $(grep '^[0-9a-f]\{40\} ' verify.out | wc -l)
+       test "$2" = $(grep "^$OID_REGEX " verify.out | wc -l)
 }
 
 convert_bundle_to_pack () {
@@ -174,6 +174,30 @@ test_expect_success 'fetch --prune --tags with refspec prunes based on refspec'
        git rev-parse sometag
 '
 
+test_expect_success '--refmap="" ignores configured refspec' '
+       cd "$TRASH_DIRECTORY" &&
+       git clone "$D" remote-refs &&
+       git -C remote-refs rev-parse remotes/origin/master >old &&
+       git -C remote-refs update-ref refs/remotes/origin/master master~1 &&
+       git -C remote-refs rev-parse remotes/origin/master >new &&
+       git -C remote-refs fetch --refmap= origin "+refs/heads/*:refs/hidden/origin/*" &&
+       git -C remote-refs rev-parse remotes/origin/master >actual &&
+       test_cmp new actual &&
+       git -C remote-refs fetch origin &&
+       git -C remote-refs rev-parse remotes/origin/master >actual &&
+       test_cmp old actual
+'
+
+test_expect_success '--refmap="" and --prune' '
+       git -C remote-refs update-ref refs/remotes/origin/foo/otherbranch master &&
+       git -C remote-refs update-ref refs/hidden/foo/otherbranch master &&
+       git -C remote-refs fetch --prune --refmap="" origin +refs/heads/*:refs/hidden/* &&
+       git -C remote-refs rev-parse remotes/origin/foo/otherbranch &&
+       test_must_fail git -C remote-refs rev-parse refs/hidden/foo/otherbranch &&
+       git -C remote-refs fetch --prune origin &&
+       test_must_fail git -C remote-refs rev-parse remotes/origin/foo/otherbranch
+'
+
 test_expect_success 'fetch tags when there is no tags' '
 
     cd "$D" &&
@@ -261,9 +285,10 @@ test_expect_success 'create bundle 1' '
 '
 
 test_expect_success 'header of bundle looks right' '
+       head -n 4 "$D"/bundle1 &&
        head -n 1 "$D"/bundle1 | grep "^#" &&
-       head -n 2 "$D"/bundle1 | grep "^-[0-9a-f]\{40\} " &&
-       head -n 3 "$D"/bundle1 | grep "^[0-9a-f]\{40\} " &&
+       head -n 2 "$D"/bundle1 | grep "^-$OID_REGEX " &&
+       head -n 3 "$D"/bundle1 | grep "^$OID_REGEX " &&
        head -n 4 "$D"/bundle1 | grep "^$"
 '
 
@@ -289,7 +314,7 @@ test_expect_success 'bundle 1 has only 3 files ' '
 test_expect_success 'unbundle 2' '
        cd "$D/bundle" &&
        git fetch ../bundle2 master:master &&
-       test "tip" = "$(git log -1 --pretty=oneline master | cut -b42-)"
+       test "tip" = "$(git log -1 --pretty=oneline master | cut -d" " -f2)"
 '
 
 test_expect_success 'bundle does not prerequisite objects' '
index d7b9f9078f6f95e866c6a0dec68511681f1957d6..04b35402c7aab16319713f8aed205e6c9d809893 100755 (executable)
@@ -225,44 +225,45 @@ test_expect_success 'ls-remote --symref' '
        EOF
        # Protocol v2 supports sending symrefs for refs other than HEAD, so use
        # protocol v0 here.
-       GIT_TEST_PROTOCOL_VERSION= git ls-remote --symref >actual &&
+       GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref >actual &&
        test_cmp expect actual
 '
 
 test_expect_success 'ls-remote with filtered symref (refname)' '
-       cat >expect <<-\EOF &&
+       rev=$(git rev-parse HEAD) &&
+       cat >expect <<-EOF &&
        ref: refs/heads/master  HEAD
-       1bd44cb9d13204b0fe1958db0082f5028a16eb3a        HEAD
+       $rev    HEAD
        EOF
        # Protocol v2 supports sending symrefs for refs other than HEAD, so use
        # protocol v0 here.
-       GIT_TEST_PROTOCOL_VERSION= git ls-remote --symref . HEAD >actual &&
+       GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref . HEAD >actual &&
        test_cmp expect actual
 '
 
 test_expect_failure 'ls-remote with filtered symref (--heads)' '
        git symbolic-ref refs/heads/foo refs/tags/mark &&
-       cat >expect <<-\EOF &&
+       cat >expect <<-EOF &&
        ref: refs/tags/mark     refs/heads/foo
-       1bd44cb9d13204b0fe1958db0082f5028a16eb3a        refs/heads/foo
-       1bd44cb9d13204b0fe1958db0082f5028a16eb3a        refs/heads/master
+       $rev    refs/heads/foo
+       $rev    refs/heads/master
        EOF
        # Protocol v2 supports sending symrefs for refs other than HEAD, so use
        # protocol v0 here.
-       GIT_TEST_PROTOCOL_VERSION= git ls-remote --symref --heads . >actual &&
+       GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref --heads . >actual &&
        test_cmp expect actual
 '
 
 test_expect_success 'ls-remote --symref omits filtered-out matches' '
-       cat >expect <<-\EOF &&
-       1bd44cb9d13204b0fe1958db0082f5028a16eb3a        refs/heads/foo
-       1bd44cb9d13204b0fe1958db0082f5028a16eb3a        refs/heads/master
+       cat >expect <<-EOF &&
+       $rev    refs/heads/foo
+       $rev    refs/heads/master
        EOF
        # Protocol v2 supports sending symrefs for refs other than HEAD, so use
        # protocol v0 here.
-       GIT_TEST_PROTOCOL_VERSION= git ls-remote --symref --heads . >actual &&
+       GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref --heads . >actual &&
        test_cmp expect actual &&
-       GIT_TEST_PROTOCOL_VERSION= git ls-remote --symref . "refs/heads/*" >actual &&
+       GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref . "refs/heads/*" >actual &&
        test_cmp expect actual
 '
 
index 961eb35c99db6ba4d740f707cd17c7357a3aad0a..a9a7d50d3eb83654262fcbc3b8f533d0ded4c687 100755 (executable)
@@ -8,7 +8,8 @@ test_description='Merge logic in fetch'
 
 # NEEDSWORK: If the overspecification of the expected result is reduced, we
 # might be able to run this test in all protocol versions.
-GIT_TEST_PROTOCOL_VERSION=
+GIT_TEST_PROTOCOL_VERSION=0
+export GIT_TEST_PROTOCOL_VERSION
 
 . ./test-lib.sh
 
index c81ca360ac4ac9edccf86132aa63e44812906980..f12cbef09728d9c49b0ad66eab77c57043df0596 100755 (executable)
@@ -1151,7 +1151,7 @@ test_expect_success 'fetch exact SHA1' '
                # unadvertised objects, so restrict this test to v0.
 
                # fetching the hidden object should fail by default
-               test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+               test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
                        git fetch -v ../testrepo $the_commit:refs/heads/copy 2>err &&
                test_i18ngrep "Server does not allow request for unadvertised object" err &&
                test_must_fail git rev-parse --verify refs/heads/copy &&
@@ -1210,7 +1210,7 @@ do
                        cd shallow &&
                        # Some protocol versions (e.g. 2) support fetching
                        # unadvertised objects, so restrict this test to v0.
-                       test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+                       test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
                                git fetch --depth=1 ../testrepo/.git $SHA1 &&
                        git --git-dir=../testrepo/.git config uploadpack.allowreachablesha1inwant true &&
                        git fetch --depth=1 ../testrepo/.git $SHA1 &&
@@ -1241,9 +1241,9 @@ do
                        cd shallow &&
                        # Some protocol versions (e.g. 2) support fetching
                        # unadvertised objects, so restrict this test to v0.
-                       test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+                       test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
                                git fetch ../testrepo/.git $SHA1_3 &&
-                       test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+                       test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
                                git fetch ../testrepo/.git $SHA1_1 &&
                        git --git-dir=../testrepo/.git config uploadpack.allowreachablesha1inwant true &&
                        git fetch ../testrepo/.git $SHA1_1 &&
@@ -1251,7 +1251,7 @@ do
                        test_must_fail git cat-file commit $SHA1_2 &&
                        git fetch ../testrepo/.git $SHA1_2 &&
                        git cat-file commit $SHA1_2 &&
-                       test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+                       test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
                                git fetch ../testrepo/.git $SHA1_3 2>err &&
                        test_i18ngrep "remote error:.*not our ref.*$SHA1_3\$" err
                )
@@ -1291,7 +1291,7 @@ test_expect_success 'peeled advertisements are not considered ref tips' '
        git -C testrepo commit --allow-empty -m two &&
        git -C testrepo tag -m foo mytag HEAD^ &&
        oid=$(git -C testrepo rev-parse mytag^{commit}) &&
-       test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+       test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
                git fetch testrepo $oid 2>err &&
        test_i18ngrep "Server does not allow request for unadvertised object" err
 '
index a1d3031d40dcc3d9234c3a5f37c4130c78f7f499..4ce9a9f7041112e49061acf14e0a51076ba770d2 100755 (executable)
@@ -14,7 +14,7 @@ corrupt_repo () {
 }
 
 test_expect_success 'setup and corrupt repository' '
-
+       test_oid_init &&
        echo file >file &&
        git add file &&
        git rev-parse :file &&
@@ -31,9 +31,10 @@ test_expect_success 'fsck fails' '
 '
 
 test_expect_success 'upload-pack fails due to error in pack-objects packing' '
-
-       printf "0032want %s\n00000009done\n0000" \
-               $(git rev-parse HEAD) >input &&
+       head=$(git rev-parse HEAD) &&
+       hexsz=$(test_oid hexsz) &&
+       printf "%04xwant %s\n00000009done\n0000" \
+               $(($hexsz + 10)) $head >input &&
        test_must_fail git upload-pack . <input >/dev/null 2>output.err &&
        test_i18ngrep "unable to read" output.err &&
        test_i18ngrep "pack-objects died" output.err
@@ -51,16 +52,17 @@ test_expect_success 'fsck fails' '
 '
 test_expect_success 'upload-pack fails due to error in rev-list' '
 
-       printf "0032want %s\n0034shallow %s00000009done\n0000" \
-               $(git rev-parse HEAD) $(git rev-parse HEAD^) >input &&
+       printf "%04xwant %s\n%04xshallow %s00000009done\n0000" \
+               $(($hexsz + 10)) $(git rev-parse HEAD) \
+               $(($hexsz + 12)) $(git rev-parse HEAD^) >input &&
        test_must_fail git upload-pack . <input >/dev/null 2>output.err &&
        grep "bad tree object" output.err
 '
 
 test_expect_success 'upload-pack fails due to bad want (no object)' '
 
-       printf "0045want %s multi_ack_detailed\n00000009done\n0000" \
-               "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" >input &&
+       printf "%04xwant %s multi_ack_detailed\n00000009done\n0000" \
+               $(($hexsz + 29)) $(test_oid deadbeef) >input &&
        test_must_fail git upload-pack . <input >output 2>output.err &&
        grep "not our ref" output.err &&
        grep "ERR" output &&
@@ -70,8 +72,8 @@ test_expect_success 'upload-pack fails due to bad want (no object)' '
 test_expect_success 'upload-pack fails due to bad want (not tip)' '
 
        oid=$(echo an object we have | git hash-object -w --stdin) &&
-       printf "0045want %s multi_ack_detailed\n00000009done\n0000" \
-               "$oid" >input &&
+       printf "%04xwant %s multi_ack_detailed\n00000009done\n0000" \
+               $(($hexsz + 29)) "$oid" >input &&
        test_must_fail git upload-pack . <input >output 2>output.err &&
        grep "not our ref" output.err &&
        grep "ERR" output &&
@@ -80,8 +82,8 @@ test_expect_success 'upload-pack fails due to bad want (not tip)' '
 
 test_expect_success 'upload-pack fails due to error in pack-objects enumeration' '
 
-       printf "0032want %s\n00000009done\n0000" \
-               $(git rev-parse HEAD) >input &&
+       printf "%04xwant %s\n00000009done\n0000" \
+               $((hexsz + 10)) $(git rev-parse HEAD) >input &&
        test_must_fail git upload-pack . <input >/dev/null 2>output.err &&
        grep "bad tree object" output.err &&
        grep "pack-objects died" output.err
index 97a67728ca9b9a16f81fb278f44fd3412383c154..9e16512fe31b9bbb64c88093c5c317299a0e5735 100755 (executable)
@@ -15,7 +15,11 @@ test_expect_success 'setup' '
        commit 2 &&
        commit 3 &&
        commit 4 &&
-       git config --global transfer.fsckObjects true
+       git config --global transfer.fsckObjects true &&
+       test_oid_cache <<-EOF
+       sed sha1:s/0034shallow %s/0036unshallow %s/
+       sed sha256:s/004cshallow %s/004eunshallow %s/
+       EOF
 '
 
 test_expect_success 'setup shallow clone' '
@@ -239,7 +243,7 @@ test_expect_success 'shallow fetches check connectivity before writing shallow f
        # with an empty packfile. This is done by refetching with a shorter
        # depth (to ensure that the packfile is empty), and overwriting the
        # shallow line in the response with the unshallow line we want.
-       printf "s/0034shallow %s/0036unshallow %s/" \
+       printf "$(test_oid sed)" \
               "$(git -C "$REPO" rev-parse HEAD)" \
               "$(git -C "$REPO" rev-parse HEAD^)" \
               >"$HTTPD_ROOT_PATH/one-time-sed" &&
index b4ad81f00635efc144a3b531a939e5fc34d5c14f..c0d02dee893fc5466c36ce105b5a029ae66c7845 100755 (executable)
@@ -69,7 +69,7 @@ test_expect_success 'no shallow lines after receiving ACK ready' '
                test_commit new-too &&
                # NEEDSWORK: If the overspecification of the expected result is reduced, we
                # might be able to run this test in all protocol versions.
-               GIT_TRACE_PACKET="$TRASH_DIRECTORY/trace" GIT_TEST_PROTOCOL_VERSION= \
+               GIT_TRACE_PACKET="$TRASH_DIRECTORY/trace" GIT_TEST_PROTOCOL_VERSION=0 \
                        git fetch --depth=2 &&
                grep "fetch-pack< ACK .* ready" ../trace &&
                ! grep "fetch-pack> done" ../trace
index a094fd5e71334bd16ac890234962485a7f9b7d24..d476c335098eef1e61aa8c7b8951fe760e8d13c1 100755 (executable)
@@ -134,15 +134,13 @@ test_expect_success 'MKCOL sends directory names with trailing slashes' '
 
 x1="[0-9a-f]"
 x2="$x1$x1"
-x5="$x1$x1$x1$x1$x1"
-x38="$x5$x5$x5$x5$x5$x5$x5$x1$x1$x1"
-x40="$x38$x2"
+xtrunc=$(echo $OID_REGEX | sed -e "s/\[0-9a-f\]\[0-9a-f\]//")
 
 test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' '
        sed \
                -e "s/PUT /OP /" \
                -e "s/MOVE /OP /" \
-           -e "s|/objects/$x2/${x38}_$x40|WANTED_PATH_REQUEST|" \
+           -e "s|/objects/$x2/${xtrunc}_$OID_REGEX|WANTED_PATH_REQUEST|" \
                "$HTTPD_ROOT_PATH"/access.log |
        grep -e "\"OP .*WANTED_PATH_REQUEST HTTP/[.0-9]*\" 20[0-9] "
 
index 4c970787b0ec1fcc3d18cdcab0f5c8fd15f16afd..23be8ce92d67824166156cf0c5ca380022b79efc 100755 (executable)
@@ -49,7 +49,7 @@ test_expect_success 'no empty path components' '
 
        # NEEDSWORK: If the overspecification of the expected result is reduced, we
        # might be able to run this test in all protocol versions.
-       if test -z "$GIT_TEST_PROTOCOL_VERSION"
+       if test "$GIT_TEST_PROTOCOL_VERSION" = 0
        then
                check_access_log exp
        fi
@@ -135,7 +135,7 @@ EOF
 test_expect_success 'used receive-pack service' '
        # NEEDSWORK: If the overspecification of the expected result is reduced, we
        # might be able to run this test in all protocol versions.
-       if test -z "$GIT_TEST_PROTOCOL_VERSION"
+       if test "$GIT_TEST_PROTOCOL_VERSION" = 0
        then
                check_access_log exp
        fi
index e38e54386795a0e05003e803dc79301ed1cd52d9..6788aefaceb8b01d9f9914a3a096b2a6bddec1df 100755 (executable)
@@ -43,7 +43,7 @@ test_expect_success 'clone http repository' '
        < Cache-Control: no-cache, max-age=0, must-revalidate
        < Content-Type: application/x-git-upload-pack-result
        EOF
-       GIT_TRACE_CURL=true GIT_TEST_PROTOCOL_VERSION= \
+       GIT_TRACE_CURL=true GIT_TEST_PROTOCOL_VERSION=0 \
                git clone --quiet $HTTPD_URL/smart/repo.git clone 2>err &&
        test_cmp file clone/file &&
        tr '\''\015'\'' Q <err |
@@ -84,7 +84,7 @@ test_expect_success 'clone http repository' '
 
        # NEEDSWORK: If the overspecification of the expected result is reduced, we
        # might be able to run this test in all protocol versions.
-       if test -z "$GIT_TEST_PROTOCOL_VERSION"
+       if test "$GIT_TEST_PROTOCOL_VERSION" = 0
        then
                sed -e "s/^> Accept-Encoding: .*/> Accept-Encoding: ENCODINGS/" \
                                actual >actual.smudged &&
@@ -113,7 +113,7 @@ test_expect_success 'used upload-pack service' '
 
        # NEEDSWORK: If the overspecification of the expected result is reduced, we
        # might be able to run this test in all protocol versions.
-       if test -z "$GIT_TEST_PROTOCOL_VERSION"
+       if test "$GIT_TEST_PROTOCOL_VERSION" = 0
        then
                check_access_log exp
        fi
@@ -241,7 +241,7 @@ test_expect_success 'cookies stored in http.cookiefile when http.savecookies set
 
        # NEEDSWORK: If the overspecification of the expected result is reduced, we
        # might be able to run this test in all protocol versions.
-       if test -z "$GIT_TEST_PROTOCOL_VERSION"
+       if test "$GIT_TEST_PROTOCOL_VERSION" = 0
        then
                tail -3 cookies.txt | sort >cookies_tail.txt &&
                test_cmp expect_cookies.txt cookies_tail.txt
@@ -336,7 +336,7 @@ test_expect_success 'test allowreachablesha1inwant with unreachable' '
        git -C test_reachable.git remote add origin "$HTTPD_URL/smart/repo.git" &&
        # Some protocol versions (e.g. 2) support fetching
        # unadvertised objects, so restrict this test to v0.
-       test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+       test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
                git -C test_reachable.git fetch origin "$(git rev-parse HEAD)"
 '
 
@@ -358,7 +358,7 @@ test_expect_success 'test allowanysha1inwant with unreachable' '
        git -C test_reachable.git remote add origin "$HTTPD_URL/smart/repo.git" &&
        # Some protocol versions (e.g. 2) support fetching
        # unadvertised objects, so restrict this test to v0.
-       test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+       test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
                git -C test_reachable.git fetch origin "$(git rev-parse HEAD)" &&
 
        git -C "$server" config uploadpack.allowanysha1inwant 1 &&
index f70cbcc9cabf6499de57529cbfa095ed9eff73e3..156c70404083fb789127a0a5540bb6ba5332a3fb 100755 (executable)
@@ -107,7 +107,11 @@ test_expect_success 'use ref advertisement to filter out commits' '
 
        # The ref advertisement itself is filtered when protocol v2 is used, so
        # use v0.
-       GIT_TEST_PROTOCOL_VERSION= trace_fetch client origin to_fetch &&
+       (
+               GIT_TEST_PROTOCOL_VERSION=0 &&
+               export GIT_TEST_PROTOCOL_VERSION &&
+               trace_fetch client origin to_fetch
+       ) &&
        have_sent c5 c4^ c2side &&
        have_not_sent c4 c4^^ c4^^^
 '
@@ -169,7 +173,17 @@ test_expect_success 'do not send "have" with ancestors of commits that server AC
        test_commit -C server commit-on-b1 &&
 
        test_config -C client fetch.negotiationalgorithm skipping &&
-       trace_fetch client "$(pwd)/server" to_fetch &&
+
+       # NEEDSWORK: The number of "have"s sent depends on whether the transport
+       # is stateful. If the overspecification of the result were reduced, this
+       # test could be used for both stateful and stateless transports.
+       (
+               # Force protocol v0, in which local transport is stateful (in
+               # protocol v2 it is stateless).
+               GIT_TEST_PROTOCOL_VERSION=0 &&
+               export GIT_TEST_PROTOCOL_VERSION &&
+               trace_fetch client "$(pwd)/server" to_fetch
+       ) &&
        grep "  fetch" trace &&
 
        # fetch-pack sends 2 requests each containing 16 "have" lines before
index f0f425b2cf5746fb3f7205909cbc93eb25a84874..4a110b307ee53e623bff98a68307c4947139c39f 100755 (executable)
@@ -59,7 +59,7 @@ test_expect_success 'setup' '
        printf done | packetize >>fetch_body &&
        test_copy_bytes 10 <fetch_body >fetch_body.trunc &&
        hash_next=$(git commit-tree -p HEAD -m next HEAD^{tree}) &&
-       printf "%s %s refs/heads/newbranch\\0report-status\\n" "$_z40" "$hash_next" | packetize >push_body &&
+       printf "%s %s refs/heads/newbranch\\0report-status\\n" "$ZERO_OID" "$hash_next" | packetize >push_body &&
        printf 0000 >>push_body &&
        echo "$hash_next" | git pack-objects --stdout >>push_body &&
        test_copy_bytes 10 <push_body >push_body.trunc &&
index 3e9876e1971348daad7fd87f8aa107ea5fd6aaff..a53dd8550d0b8cbe40f49ece0381fb554ba87062 100755 (executable)
@@ -60,6 +60,27 @@ test_expect_success GPG 'pull commit with untrusted signature with --verify-sign
        test_i18ngrep "has an untrusted GPG signature" pullerror
 '
 
+test_expect_success GPG 'pull commit with untrusted signature with --verify-signatures and minTrustLevel=ultimate' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       test_config gpg.minTrustLevel ultimate &&
+       test_must_fail git pull --ff-only --verify-signatures untrusted 2>pullerror &&
+       test_i18ngrep "has an untrusted GPG signature" pullerror
+'
+
+test_expect_success GPG 'pull commit with untrusted signature with --verify-signatures and minTrustLevel=marginal' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       test_config gpg.minTrustLevel marginal &&
+       test_must_fail git pull --ff-only --verify-signatures untrusted 2>pullerror &&
+       test_i18ngrep "has an untrusted GPG signature" pullerror
+'
+
+test_expect_success GPG 'pull commit with untrusted signature with --verify-signatures and minTrustLevel=undefined' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       test_config gpg.minTrustLevel undefined &&
+       git pull --ff-only --verify-signatures untrusted >pulloutput &&
+       test_i18ngrep "has a good GPG signature" pulloutput
+'
+
 test_expect_success GPG 'pull signed commit with --verify-signatures' '
        test_when_finished "git reset --hard && git checkout initial" &&
        git pull --verify-signatures signed >pulloutput &&
@@ -79,10 +100,53 @@ test_expect_success GPG 'pull commit with bad signature with --no-verify-signatu
 '
 
 test_expect_success GPG 'pull unsigned commit into unborn branch' '
+       test_when_finished "rm -rf empty-repo" &&
        git init empty-repo &&
        test_must_fail \
                git -C empty-repo pull --verify-signatures ..  2>pullerror &&
        test_i18ngrep "does not have a GPG signature" pullerror
 '
 
+test_expect_success GPG 'pull commit into unborn branch with bad signature and --verify-signatures' '
+       test_when_finished "rm -rf empty-repo" &&
+       git init empty-repo &&
+       test_must_fail \
+               git -C empty-repo pull --ff-only --verify-signatures ../bad 2>pullerror &&
+       test_i18ngrep "has a bad GPG signature" pullerror
+'
+
+test_expect_success GPG 'pull commit into unborn branch with untrusted signature and --verify-signatures' '
+       test_when_finished "rm -rf empty-repo" &&
+       git init empty-repo &&
+       test_must_fail \
+               git -C empty-repo pull --ff-only --verify-signatures ../untrusted 2>pullerror &&
+       test_i18ngrep "has an untrusted GPG signature" pullerror
+'
+
+test_expect_success GPG 'pull commit into unborn branch with untrusted signature and --verify-signatures and minTrustLevel=ultimate' '
+       test_when_finished "rm -rf empty-repo" &&
+       git init empty-repo &&
+       test_config_global gpg.minTrustLevel ultimate &&
+       test_must_fail \
+               git -C empty-repo pull --ff-only --verify-signatures ../untrusted 2>pullerror &&
+       test_i18ngrep "has an untrusted GPG signature" pullerror
+'
+
+test_expect_success GPG 'pull commit into unborn branch with untrusted signature and --verify-signatures and minTrustLevel=marginal' '
+       test_when_finished "rm -rf empty-repo" &&
+       git init empty-repo &&
+       test_config_global gpg.minTrustLevel marginal &&
+       test_must_fail \
+               git -C empty-repo pull --ff-only --verify-signatures ../untrusted 2>pullerror &&
+       test_i18ngrep "has an untrusted GPG signature" pullerror
+'
+
+test_expect_success GPG 'pull commit into unborn branch with untrusted signature and --verify-signatures and minTrustLevel=undefined' '
+       test_when_finished "rm -rf empty-repo" &&
+       git init empty-repo &&
+       test_config_global gpg.minTrustLevel undefined &&
+       git -C empty-repo pull --ff-only --verify-signatures ../untrusted >pulloutput &&
+       test_i18ngrep "has a good GPG signature" pulloutput
+'
+
 test_done
index ad8c41176ef76690004c8f73131a7ba464758143..84ea2a3eb707a7405d7fc3c5267f8bfff06abd18 100755 (executable)
@@ -635,10 +635,10 @@ partial_clone_server () {
        rm -rf "$SERVER" client &&
        test_create_repo "$SERVER" &&
        test_commit -C "$SERVER" one &&
-       HASH1=$(git hash-object "$SERVER/one.t") &&
+       HASH1=$(git -C "$SERVER" hash-object one.t) &&
        git -C "$SERVER" revert HEAD &&
        test_commit -C "$SERVER" two &&
-       HASH2=$(git hash-object "$SERVER/two.t") &&
+       HASH2=$(git -C "$SERVER" hash-object two.t) &&
        test_config -C "$SERVER" uploadpack.allowfilter 1 &&
        test_config -C "$SERVER" uploadpack.allowanysha1inwant 1
 }
index 4894237ab8059c83aea8aae50a0803ebf9df6831..0c74b4e21a3ef17499401cdd1f4eeaab5c392137 100755 (executable)
@@ -326,15 +326,16 @@ test_expect_success SYMLINKS 'clone repo with symlinked or unknown files at obje
        for raw in $(ls T*.raw)
        do
                sed -e "s!/../!/Y/!; s![0-9a-f]\{38,\}!Z!" -e "/commit-graph/d" \
-                   -e "/multi-pack-index/d" <$raw >$raw.de-sha || return 1
+                   -e "/multi-pack-index/d" <$raw >$raw.de-sha-1 &&
+               sort $raw.de-sha-1 >$raw.de-sha || return 1
        done &&
 
        cat >expected-files <<-EOF &&
        ./Y/Z
        ./Y/Z
+       ./Y/Z
        ./a-loose-dir/Z
        ./an-object
-       ./Y/Z
        ./info/packs
        ./pack/pack-Z.idx
        ./pack/pack-Z.pack
index fea56cda6d3a256161a21cb7e79e6e96f328bf17..9a9178fd2810025322d1ad3048a09a661eaac110 100755 (executable)
@@ -309,26 +309,36 @@ setup_triangle () {
 
        printf "line %d\n" $(test_seq 1 100) >big-blob.txt &&
 
-       # Create a server with 2 commits: a commit with a big blob and a child
+       # Create a server with 2 commits: a commit with a big tree and a child
        # commit with an incremental change. Also, create a partial clone
        # client that only contains the first commit.
        git init server &&
        git -C server config --local uploadpack.allowfilter 1 &&
-       cp big-blob.txt server &&
-       git -C server add big-blob.txt &&
+       for i in $(test_seq 1 100)
+       do
+               echo "make the tree big" >server/file$i &&
+               git -C server add file$i
+       done &&
        git -C server commit -m "initial" &&
        git clone --bare --filter=tree:0 "file://$(pwd)/server" client &&
-       echo another line >>server/big-blob.txt &&
-       git -C server commit -am "append line to big blob" &&
+       echo another line >>server/file1 &&
+       git -C server commit -am "incremental change" &&
 
-       # Create a promisor remote that only contains the blob from the first
-       # commit, and set it as the promisor remote of client. Thus, whenever
-       # the client lazy fetches, the lazy fetch will succeed only if it is
-       # for this blob.
+       # Create a promisor remote that only contains the tree and blob from
+       # the first commit.
        git init promisor-remote &&
+       git -C server config --local uploadpack.allowanysha1inwant 1 &&
+       TREE_HASH=$(git -C server rev-parse HEAD~1^{tree}) &&
+       git -C promisor-remote fetch --keep "file://$(pwd)/server" "$TREE_HASH" &&
+       git -C promisor-remote count-objects -v >object-count &&
+       test_i18ngrep "count: 0" object-count &&
+       test_i18ngrep "in-pack: 2" object-count &&
+
+       # Set it as the promisor remote of client. Thus, whenever
+       # the client lazy fetches, the lazy fetch will succeed only if it is
+       # for this tree or blob.
        test_commit -C promisor-remote one && # so that ref advertisement is not empty
        git -C promisor-remote config --local uploadpack.allowanysha1inwant 1 &&
-       git -C promisor-remote hash-object -w --stdin <big-blob.txt &&
        git -C client remote set-url origin "file://$(pwd)/promisor-remote"
 }
 
@@ -341,14 +351,14 @@ test_expect_success 'fetch lazy-fetches only to resolve deltas' '
        setup_triangle &&
 
        # Exercise to make sure it works. Git will not fetch anything from the
-       # promisor remote other than for the big blob (because it needs to
+       # promisor remote other than for the big tree (because it needs to
        # resolve the delta).
        GIT_TRACE_PACKET="$(pwd)/trace" git -C client \
                fetch "file://$(pwd)/server" master &&
 
        # Verify the assumption that the client needed to fetch the delta base
        # to resolve the delta.
-       git hash-object big-blob.txt >hash &&
+       git -C server rev-parse HEAD~1^{tree} >hash &&
        grep "want $(cat hash)" trace
 '
 
@@ -370,7 +380,7 @@ test_expect_success 'fetch lazy-fetches only to resolve deltas, protocol v2' '
 
        # Verify the assumption that the client needed to fetch the delta base
        # to resolve the delta.
-       git hash-object big-blob.txt >hash &&
+       git -C server rev-parse HEAD~1^{tree} >hash &&
        grep "want $(cat hash)" trace
 '
 
index 2571eb90b7656cf2237a90806b79f14ab204d90c..022901b9eb6fdcaf3ff625b0db8056047946aa1a 100755 (executable)
@@ -5,7 +5,8 @@ test_description='test git wire-protocol transition'
 TEST_NO_CREATE_REPO=1
 
 # This is a protocol-specific test.
-GIT_TEST_PROTOCOL_VERSION=
+GIT_TEST_PROTOCOL_VERSION=0
+export GIT_TEST_PROTOCOL_VERSION
 
 . ./test-lib.sh
 
index e73067d23fe747210f0ea767a7e8952b81ed3d30..7fd7102c8741f3a6c426d3f84b6556b9bbe9a861 100755 (executable)
@@ -665,6 +665,18 @@ test_expect_success 'fetch from namespaced repo respects namespaces' '
        test_cmp expect actual
 '
 
+test_expect_success 'ls-remote with v2 http sends only one POST' '
+       test_when_finished "rm -f log" &&
+
+       git ls-remote "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" >expect &&
+       GIT_TRACE_CURL="$(pwd)/log" git -c protocol.version=2 \
+               ls-remote "$HTTPD_URL/smart/http_parent" >actual &&
+       test_cmp expect actual &&
+
+       grep "Send header: POST" log >posts &&
+       test_line_count = 1 posts
+'
+
 test_expect_success 'push with http:// and a config of v2 does not request v2' '
        test_when_finished "rm -f log" &&
        # Till v2 for push is designed, make sure that if a client has
index 433c4de08f0cc8d220d5368ab2ab0dffde372482..6c0a90d044c52500fe1b1a31d2bf12eef9691de0 100755 (executable)
@@ -10,52 +10,53 @@ if core.symlinks is false.'
 
 . ./test-lib.sh
 
-test_expect_success \
-'setup' '
-git config core.symlinks false &&
-> file &&
-git add file &&
-git commit -m initial &&
-git branch b-symlink &&
-git branch b-file &&
-l=$(printf file | git hash-object -t blob -w --stdin) &&
-echo "120000 $l        symlink" | git update-index --index-info &&
-git commit -m master &&
-git checkout b-symlink &&
-l=$(printf file-different | git hash-object -t blob -w --stdin) &&
-echo "120000 $l        symlink" | git update-index --index-info &&
-git commit -m b-symlink &&
-git checkout b-file &&
-echo plain-file > symlink &&
-git add symlink &&
-git commit -m b-file'
-
-test_expect_success \
-'merge master into b-symlink, which has a different symbolic link' '
-git checkout b-symlink &&
-test_must_fail git merge master'
-
-test_expect_success \
-'the merge result must be a file' '
-test -f symlink'
-
-test_expect_success \
-'merge master into b-file, which has a file instead of a symbolic link' '
-git reset --hard && git checkout b-file &&
-test_must_fail git merge master'
-
-test_expect_success \
-'the merge result must be a file' '
-test -f symlink'
-
-test_expect_success \
-'merge b-file, which has a file instead of a symbolic link, into master' '
-git reset --hard &&
-git checkout master &&
-test_must_fail git merge b-file'
-
-test_expect_success \
-'the merge result must be a file' '
-test -f symlink'
+test_expect_success 'setup' '
+       git config core.symlinks false &&
+       >file &&
+       git add file &&
+       git commit -m initial &&
+       git branch b-symlink &&
+       git branch b-file &&
+       l=$(printf file | git hash-object -t blob -w --stdin) &&
+       echo "120000 $l symlink" | git update-index --index-info &&
+       git commit -m master &&
+       git checkout b-symlink &&
+       l=$(printf file-different | git hash-object -t blob -w --stdin) &&
+       echo "120000 $l symlink" | git update-index --index-info &&
+       git commit -m b-symlink &&
+       git checkout b-file &&
+       echo plain-file >symlink &&
+       git add symlink &&
+       git commit -m b-file
+'
+
+test_expect_success 'merge master into b-symlink, which has a different symbolic link' '
+       git checkout b-symlink &&
+       test_must_fail git merge master
+'
+
+test_expect_success 'the merge result must be a file' '
+       test_path_is_file symlink
+'
+
+test_expect_success 'merge master into b-file, which has a file instead of a symbolic link' '
+       git reset --hard &&
+       git checkout b-file &&
+       test_must_fail git merge master
+'
+
+test_expect_success 'the merge result must be a file' '
+       test_path_is_file symlink
+'
+
+test_expect_success 'merge b-file, which has a file instead of a symbolic link, into master' '
+       git reset --hard &&
+       git checkout master &&
+       test_must_fail git merge b-file
+'
+
+test_expect_success 'the merge result must be a file' '
+       test_path_is_file symlink
+'
 
 test_done
index 8f077bea6095a6fea8d92ac8a83b11f5d865cdf6..5c5bc32ccb13ac77b453a90e432c74f3ed331ac1 100755 (executable)
@@ -86,6 +86,30 @@ test_expect_success GPGSM 'verify and show signatures x509' '
        echo ninth-signed-x509 OK
 '
 
+test_expect_success GPGSM 'verify and show signatures x509 with low minTrustLevel' '
+       test_config gpg.minTrustLevel undefined &&
+       git verify-tag ninth-signed-x509 2>actual &&
+       grep "Good signature from" actual &&
+       ! grep "BAD signature from" actual &&
+       echo ninth-signed-x509 OK
+'
+
+test_expect_success GPGSM 'verify and show signatures x509 with matching minTrustLevel' '
+       test_config gpg.minTrustLevel fully &&
+       git verify-tag ninth-signed-x509 2>actual &&
+       grep "Good signature from" actual &&
+       ! grep "BAD signature from" actual &&
+       echo ninth-signed-x509 OK
+'
+
+test_expect_success GPGSM 'verify and show signatures x509 with high minTrustLevel' '
+       test_config gpg.minTrustLevel ultimate &&
+       test_must_fail git verify-tag ninth-signed-x509 2>actual &&
+       grep "Good signature from" actual &&
+       ! grep "BAD signature from" actual &&
+       echo ninth-signed-x509 OK
+'
+
 test_expect_success GPG 'detect fudged signature' '
        git cat-file tag seventh-signed >raw &&
        sed -e "/^tag / s/seventh/7th forged/" raw >forged1 &&
index 6b1a731fffe65f10259a5200e071c4767181d759..cad3a9de9efc21339d82b4532d5af482e3446bc9 100755 (executable)
@@ -105,8 +105,12 @@ test_expect_success 'CRLF delimiters' '
 test_expect_success 'quotes' '
        restore_checkpoint &&
 
+       cat >list <<-\EOF &&
+       "file\101.t"
+       EOF
+
        git rm fileA.t &&
-       printf "\"file\\101.t\"" | git reset --pathspec-from-file=- &&
+       git reset --pathspec-from-file=list &&
 
        cat >expect <<-\EOF &&
         D fileA.t
@@ -117,8 +121,10 @@ test_expect_success 'quotes' '
 test_expect_success 'quotes not compatible with --pathspec-file-nul' '
        restore_checkpoint &&
 
-       git rm fileA.t &&
-       printf "\"file\\101.t\"" >list &&
+       cat >list <<-\EOF &&
+       "file\101.t"
+       EOF
+
        # Note: "git reset" has not yet learned to fail on wrong pathspecs
        git reset --pathspec-from-file=list --pathspec-file-nul &&
 
@@ -128,15 +134,6 @@ test_expect_success 'quotes not compatible with --pathspec-file-nul' '
        test_must_fail verify_expect
 '
 
-test_expect_success '--pathspec-from-file is not compatible with --soft or --hard' '
-       restore_checkpoint &&
-
-       git rm fileA.t &&
-       echo fileA.t >list &&
-       test_must_fail git reset --soft --pathspec-from-file=list &&
-       test_must_fail git reset --hard --pathspec-from-file=list
-'
-
 test_expect_success 'only touches what was listed' '
        restore_checkpoint &&
 
@@ -152,4 +149,25 @@ test_expect_success 'only touches what was listed' '
        verify_expect
 '
 
+test_expect_success 'error conditions' '
+       restore_checkpoint &&
+       echo fileA.t >list &&
+       git rm fileA.t &&
+
+       test_must_fail git reset --pathspec-from-file=list --patch 2>err &&
+       test_i18ngrep -e "--pathspec-from-file is incompatible with --patch" err &&
+
+       test_must_fail git reset --pathspec-from-file=list -- fileA.t 2>err &&
+       test_i18ngrep -e "--pathspec-from-file is incompatible with pathspec arguments" err &&
+
+       test_must_fail git reset --pathspec-file-nul 2>err &&
+       test_i18ngrep -e "--pathspec-file-nul requires --pathspec-from-file" err &&
+
+       test_must_fail git reset --soft --pathspec-from-file=list 2>err &&
+       test_i18ngrep -e "fatal: Cannot do soft reset with paths" err &&
+
+       test_must_fail git reset --hard --pathspec-from-file=list 2>err &&
+       test_i18ngrep -e "fatal: Cannot do hard reset with paths" err
+'
+
 test_done
index 6e6d24c1c3a5c5397241e266c09fe214ec14d53f..cb5e34d94c3a163ecc3785d4b3afdeeb98c60ec6 100755 (executable)
@@ -737,4 +737,13 @@ test_expect_success MINGW 'handle clean & core.longpaths = false nicely' '
        test_i18ngrep "too long" .git/err
 '
 
+test_expect_success 'clean untracked paths by pathspec' '
+       git init untracked &&
+       mkdir untracked/dir &&
+       echo >untracked/dir/file.txt &&
+       git -C untracked clean -f dir/file.txt &&
+       ls untracked/dir >actual &&
+       test_must_be_empty actual
+'
+
 test_done
index 7f75bb1be62dc8dfb223e4e89ad3a2faf07bb50a..7e8925a8eed7f00b710f7efe36ccc535c92f290d 100755 (executable)
@@ -156,9 +156,11 @@ test_expect_success 'submodule add to .gitignored path fails' '
        (
                cd addtest-ignore &&
                cat <<-\EOF >expect &&
-               The following path is ignored by one of your .gitignore files:
+               The following paths are ignored by one of your .gitignore files:
                submod
-               Use -f if you really want to add it.
+               hint: Use -f if you really want to add them.
+               hint: Turn this message off by running
+               hint: "git config advice.addIgnoredFile false"
                EOF
                # Does not use test_commit due to the ignore
                echo "*" > .gitignore &&
@@ -191,6 +193,17 @@ test_expect_success 'submodule add to reconfigure existing submodule with --forc
        )
 '
 
+test_expect_success 'submodule add relays add --dry-run stderr' '
+       test_when_finished "rm -rf addtest/.git/index.lock" &&
+       (
+               cd addtest &&
+               : >.git/index.lock &&
+               ! git submodule add "$submodurl" sub-while-locked 2>output.err &&
+               test_i18ngrep "^fatal: .*index\.lock" output.err &&
+               test_path_is_missing sub-while-locked
+       )
+'
+
 test_expect_success 'submodule add --branch' '
        echo "refs/heads/initial" >expect-head &&
        cat <<-\EOF >expect-heads &&
index 7478f7ab7eb8648a761111a9990d256fea15df58..4fb447a143e653b780d2e7eef94a7de30d75f7e8 100755 (executable)
@@ -960,7 +960,7 @@ test_expect_success 'submodule update clone shallow submodule outside of depth'
                mv -f .gitmodules.tmp .gitmodules &&
                # Some protocol versions (e.g. 2) support fetching
                # unadvertised objects, so restrict this test to v0.
-               test_must_fail env GIT_TEST_PROTOCOL_VERSION= \
+               test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 \
                        git submodule update --init --depth=1 2>actual &&
                test_i18ngrep "Direct fetching of that commit failed." actual &&
                git -C ../submodule config uploadpack.allowReachableSHA1InWant true &&
diff --git a/t/t7410-submodule-checkout-to.sh b/t/t7410-submodule-checkout-to.sh
deleted file mode 100755 (executable)
index f1b492e..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-#!/bin/sh
-
-test_description='Combination of submodules and multiple workdirs'
-
-. ./test-lib.sh
-
-base_path=$(pwd -P)
-
-test_expect_success 'setup: make origin'  '
-       mkdir -p origin/sub &&
-       (
-               cd origin/sub && git init &&
-               echo file1 >file1 &&
-               git add file1 &&
-               git commit -m file1
-       ) &&
-       mkdir -p origin/main &&
-       (
-               cd origin/main && git init &&
-               git submodule add ../sub &&
-               git commit -m "add sub"
-       ) &&
-       (
-               cd origin/sub &&
-               echo file1updated >file1 &&
-               git add file1 &&
-               git commit -m "file1 updated"
-       ) &&
-       git -C origin/main/sub pull &&
-       (
-               cd origin/main &&
-               git add sub &&
-               git commit -m "sub updated"
-       )
-'
-
-test_expect_success 'setup: clone' '
-       mkdir clone &&
-       git -C clone clone --recursive "$base_path/origin/main"
-'
-
-rev1_hash_main=$(git --git-dir=origin/main/.git show --pretty=format:%h -q "HEAD~1")
-rev1_hash_sub=$(git --git-dir=origin/sub/.git show --pretty=format:%h -q "HEAD~1")
-
-test_expect_success 'checkout main' '
-       mkdir default_checkout &&
-       git -C clone/main worktree add "$base_path/default_checkout/main" "$rev1_hash_main"
-'
-
-test_expect_failure 'can see submodule diffs just after checkout' '
-       git -C default_checkout/main diff --submodule master"^!" >out &&
-       grep "file1 updated" out
-'
-
-test_expect_success 'checkout main and initialize independent clones' '
-       mkdir fully_cloned_submodule &&
-       git -C clone/main worktree add "$base_path/fully_cloned_submodule/main" "$rev1_hash_main" &&
-       git -C fully_cloned_submodule/main submodule update
-'
-
-test_expect_success 'can see submodule diffs after independent cloning' '
-       git -C fully_cloned_submodule/main diff --submodule master"^!" >out &&
-       grep "file1 updated" out
-'
-
-test_expect_success 'checkout sub manually' '
-       mkdir linked_submodule &&
-       git -C clone/main worktree add "$base_path/linked_submodule/main" "$rev1_hash_main" &&
-       git -C clone/main/sub worktree add "$base_path/linked_submodule/main/sub" "$rev1_hash_sub"
-'
-
-test_expect_success 'can see submodule diffs after manual checkout of linked submodule' '
-       git -C linked_submodule/main diff --submodule master"^!" >out &&
-       grep "file1 updated" out
-'
-
-test_done
index 46a5cd4b7395ce8bba29763644a6fbd6739c929e..6d19ece05dd320b970492daf015b874eee0d391f 100755 (executable)
@@ -382,4 +382,13 @@ test_expect_success 'check commit with unstaged rename and copy' '
        )
 '
 
+test_expect_success 'commit without staging files fails and displays hints' '
+       echo "initial" >file &&
+       git add file &&
+       git commit -m initial &&
+       echo "changes" >>file &&
+       test_must_fail git commit -m update >actual &&
+       test_i18ngrep "no changes added to commit (use \"git add\" and/or \"git commit -a\")" actual
+'
+
 test_done
index 682b23a06818cddf6ff89e11b4395d32cebe9758..0c06d22a0079a61f04cd83dad29562dc4d017218 100755 (executable)
@@ -109,6 +109,21 @@ test_expect_success GPG 'verify-commit exits success on untrusted signature' '
        grep "not certified" actual
 '
 
+test_expect_success GPG 'verify-commit exits success with matching minTrustLevel' '
+       test_config gpg.minTrustLevel ultimate &&
+       git verify-commit sixth-signed
+'
+
+test_expect_success GPG 'verify-commit exits success with low minTrustLevel' '
+       test_config gpg.minTrustLevel fully &&
+       git verify-commit sixth-signed
+'
+
+test_expect_success GPG 'verify-commit exits failure with high minTrustLevel' '
+       test_config gpg.minTrustLevel ultimate &&
+       test_must_fail git verify-commit eighth-signed-alt
+'
+
 test_expect_success GPG 'verify signatures with --raw' '
        (
                for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed
@@ -219,6 +234,30 @@ test_expect_success GPG 'show untrusted signature with custom format' '
        test_cmp expect actual
 '
 
+test_expect_success GPG 'show untrusted signature with undefined trust level' '
+       cat >expect <<-\EOF &&
+       undefined
+       65A0EEA02E30CAD7
+       Eris Discordia <discord@example.net>
+       F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
+       D4BE22311AD3131E5EDA29A461092E85B7227189
+       EOF
+       git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with ultimate trust level' '
+       cat >expect <<-\EOF &&
+       ultimate
+       13B6F51ECDDE430D
+       C O Mitter <committer@example.com>
+       73D758744BE721698EC54E8713B6F51ECDDE430D
+       73D758744BE721698EC54E8713B6F51ECDDE430D
+       EOF
+       git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" sixth-signed >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success GPG 'show unknown signature with custom format' '
        cat >expect <<-\EOF &&
        E
index cf0fda2d5aa534d3ed4f64be48a52201535d854f..fbfdcca000777461aed96c19eb7c2cc4b298c86c 100755 (executable)
@@ -32,11 +32,12 @@ write_integration_script () {
                echo "$0: exactly 2 arguments expected"
                exit 2
        fi
-       if test "$1" != 1
+       if test "$1" != 2
        then
                echo "Unsupported core.fsmonitor hook version." >&2
                exit 1
        fi
+       printf "last_update_token\0"
        printf "untracked\0"
        printf "dir1/untracked\0"
        printf "dir2/untracked\0"
@@ -107,6 +108,7 @@ EOF
 # test that "update-index --fsmonitor-valid" sets the fsmonitor valid bit
 test_expect_success 'update-index --fsmonitor-valid" sets the fsmonitor valid bit' '
        write_script .git/hooks/fsmonitor-test<<-\EOF &&
+               printf "last_update_token\0"
        EOF
        git update-index --fsmonitor &&
        git update-index --fsmonitor-valid dir1/modified &&
@@ -167,6 +169,7 @@ EOF
 # test that newly added files are marked valid
 test_expect_success 'newly added files are marked valid' '
        write_script .git/hooks/fsmonitor-test<<-\EOF &&
+               printf "last_update_token\0"
        EOF
        git add new &&
        git add dir1/new &&
@@ -207,6 +210,7 @@ EOF
 # test that *only* files returned by the integration script get flagged as invalid
 test_expect_success '*only* files returned by the integration script get flagged as invalid' '
        write_script .git/hooks/fsmonitor-test<<-\EOF &&
+       printf "last_update_token\0"
        printf "dir1/modified\0"
        EOF
        clean_repo &&
@@ -276,6 +280,7 @@ do
                # (if enabled) files unless it is told about them.
                test_expect_success "status doesn't detect unreported modifications" '
                        write_script .git/hooks/fsmonitor-test<<-\EOF &&
+                       printf "last_update_token\0"
                        :>marker
                        EOF
                        clean_repo &&
index 691bc94dc2c8e8e362599d43de3b39a17b959f17..94ab66bd3d8686f92d66547b79bb42adbe5e3f4a 100755 (executable)
@@ -17,7 +17,6 @@ fi
 
 if test "$1" != 1
 then
-       echo "Unsupported core.fsmonitor hook version." >&2
        exit 1
 fi
 
diff --git a/t/t7519/fsmonitor-all-v2 b/t/t7519/fsmonitor-all-v2
new file mode 100755 (executable)
index 0000000..061907e
--- /dev/null
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+#
+# An test hook script to integrate with git to test fsmonitor.
+#
+# The hook is passed a version (currently 2) and since token
+# formatted as a string and outputs to stdout all files that have been
+# modified since the given time. Paths must be relative to the root of
+# the working tree and separated by a single NUL.
+#
+#echo "$0 $*" >&2
+my ($version, $last_update_token) = @ARGV;
+
+if ($version ne 2) {
+       print "Unsupported query-fsmonitor hook version '$version'.\n";
+       exit 1;
+}
+
+print "last_update_token\0/\0"
index d8e7a1e5ba85c0a9e7736435c923adb18bb4549c..264b9daf834ec83685c9388f03b78288d605e548 100755 (executable)
@@ -26,8 +26,7 @@ if ($version == 1) {
        # subtract one second to make sure watchman will return all changes
        $time = int ($time / 1000000000) - 1;
 } else {
-       die "Unsupported query-fsmonitor hook version '$version'.\n" .
-           "Falling back to scanning...\n";
+       exit 1;
 }
 
 my $git_work_tree;
diff --git a/t/t7519/fsmonitor-watchman-v2 b/t/t7519/fsmonitor-watchman-v2
new file mode 100755 (executable)
index 0000000..14ed0aa
--- /dev/null
@@ -0,0 +1,173 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use IPC::Open2;
+
+# An example hook script to integrate Watchman
+# (https://facebook.github.io/watchman/) with git to speed up detecting
+# new and modified files.
+#
+# The hook is passed a version (currently 2) and last update token
+# formatted as a string and outputs to stdout a new update token and
+# all files that have been modified since the update token. Paths must
+# be relative to the root of the working tree and separated by a single NUL.
+#
+# To enable this hook, rename this file to "query-watchman" and set
+# 'git config core.fsmonitor .git/hooks/query-watchman'
+#
+my ($version, $last_update_token) = @ARGV;
+
+# Uncomment for debugging
+# print STDERR "$0 $version $last_update_token\n";
+
+# Check the hook interface version
+if ($version ne 2) {
+       die "Unsupported query-fsmonitor hook version '$version'.\n" .
+           "Falling back to scanning...\n";
+}
+
+my $git_work_tree = get_working_dir();
+
+my $retry = 1;
+
+my $json_pkg;
+eval {
+       require JSON::XS;
+       $json_pkg = "JSON::XS";
+       1;
+} or do {
+       require JSON::PP;
+       $json_pkg = "JSON::PP";
+};
+
+launch_watchman();
+
+sub launch_watchman {
+       my $o = watchman_query();
+       if (is_work_tree_watched($o)) {
+               output_result($o->{clock}, @{$o->{files}});
+       }
+}
+
+sub output_result {
+       my ($clockid, @files) = @_;
+
+       # Uncomment for debugging watchman output
+       # open (my $fh, ">", ".git/watchman-output.out");
+       # binmode $fh, ":utf8";
+       # print $fh "$clockid\n@files\n";
+       # close $fh;
+
+       binmode STDOUT, ":utf8";
+       print $clockid;
+       print "\0";
+       local $, = "\0";
+       print @files;
+}
+
+sub watchman_clock {
+       my $response = qx/watchman clock "$git_work_tree"/;
+       die "Failed to get clock id on '$git_work_tree'.\n" .
+               "Falling back to scanning...\n" if $? != 0;
+
+       return $json_pkg->new->utf8->decode($response);
+}
+
+sub watchman_query {
+       my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
+       or die "open2() failed: $!\n" .
+       "Falling back to scanning...\n";
+
+       # In the query expression below we're asking for names of files that
+       # changed since $last_update_token but not from the .git folder.
+       #
+       # To accomplish this, we're using the "since" generator to use the
+       # recency index to select candidate nodes and "fields" to limit the
+       # output to file names only. Then we're using the "expression" term to
+       # further constrain the results.
+       if (substr($last_update_token, 0, 1) eq "c") {
+               $last_update_token = "\"$last_update_token\"";
+       }
+       my $query = <<" END";
+               ["query", "$git_work_tree", {
+                       "since": $last_update_token,
+                       "fields": ["name"],
+                       "expression": ["not", ["dirname", ".git"]]
+               }]
+       END
+
+       # Uncomment for debugging the watchman query
+       # open (my $fh, ">", ".git/watchman-query.json");
+       # print $fh $query;
+       # close $fh;
+
+       print CHLD_IN $query;
+       close CHLD_IN;
+       my $response = do {local $/; <CHLD_OUT>};
+
+       # Uncomment for debugging the watch response
+       # open ($fh, ">", ".git/watchman-response.json");
+       # print $fh $response;
+       # close $fh;
+
+       die "Watchman: command returned no output.\n" .
+       "Falling back to scanning...\n" if $response eq "";
+       die "Watchman: command returned invalid output: $response\n" .
+       "Falling back to scanning...\n" unless $response =~ /^\{/;
+
+       return $json_pkg->new->utf8->decode($response);
+}
+
+sub is_work_tree_watched {
+       my ($output) = @_;
+       my $error = $output->{error};
+       if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
+               $retry--;
+               my $response = qx/watchman watch "$git_work_tree"/;
+               die "Failed to make watchman watch '$git_work_tree'.\n" .
+                   "Falling back to scanning...\n" if $? != 0;
+               $output = $json_pkg->new->utf8->decode($response);
+               $error = $output->{error};
+               die "Watchman: $error.\n" .
+               "Falling back to scanning...\n" if $error;
+
+               # Uncomment for debugging watchman output
+               # open (my $fh, ">", ".git/watchman-output.out");
+               # close $fh;
+
+               # Watchman will always return all files on the first query so
+               # return the fast "everything is dirty" flag to git and do the
+               # Watchman query just to get it over with now so we won't pay
+               # the cost in git to look up each individual file.
+               my $o = watchman_clock();
+               $error = $output->{error};
+
+               die "Watchman: $error.\n" .
+               "Falling back to scanning...\n" if $error;
+
+               output_result($o->{clock}, ("/"));
+               $last_update_token = $o->{clock};
+
+               eval { launch_watchman() };
+               return 0;
+       }
+
+       die "Watchman: $error.\n" .
+       "Falling back to scanning...\n" if $error;
+
+       return 1;
+}
+
+sub get_working_dir {
+       my $working_dir;
+       if ($^O =~ 'msys' || $^O =~ 'cygwin') {
+               $working_dir = Win32::GetCwd();
+               $working_dir =~ tr/\\/\//;
+       } else {
+               require Cwd;
+               $working_dir = Cwd::cwd();
+       }
+
+       return $working_dir;
+}
index 4b58901ed67c1349b255dd54977692f36cac988c..5fbe47ebcd02714b8bf2e0cdf5f0d32064b55fd8 100755 (executable)
@@ -100,7 +100,11 @@ test_expect_success 'CRLF delimiters' '
 test_expect_success 'quotes' '
        restore_checkpoint &&
 
-       printf "\"file\\101.t\"" | git commit --pathspec-from-file=- -m "Commit" &&
+       cat >list <<-\EOF &&
+       "file\101.t"
+       EOF
+
+       git commit --pathspec-from-file=list -m "Commit" &&
 
        cat >expect <<-\EOF &&
        A       fileA.t
@@ -111,7 +115,10 @@ test_expect_success 'quotes' '
 test_expect_success 'quotes not compatible with --pathspec-file-nul' '
        restore_checkpoint &&
 
-       printf "\"file\\101.t\"" >list &&
+       cat >list <<-\EOF &&
+       "file\101.t"
+       EOF
+
        test_must_fail git commit --pathspec-from-file=list --pathspec-file-nul -m "Commit"
 '
 
@@ -127,10 +134,31 @@ test_expect_success 'only touches what was listed' '
        verify_expect
 '
 
-test_expect_success '--pathspec-from-file and --all cannot be used together' '
+test_expect_success 'error conditions' '
        restore_checkpoint &&
-       test_must_fail git commit --pathspec-from-file=- --all -m "Commit" 2>err &&
-       test_i18ngrep "[-]-pathspec-from-file with -a does not make sense" err
+       echo fileA.t >list &&
+       >empty_list &&
+
+       test_must_fail git commit --pathspec-from-file=list --interactive -m "Commit" 2>err &&
+       test_i18ngrep -e "--pathspec-from-file is incompatible with --interactive/--patch" err &&
+
+       test_must_fail git commit --pathspec-from-file=list --patch -m "Commit" 2>err &&
+       test_i18ngrep -e "--pathspec-from-file is incompatible with --interactive/--patch" err &&
+
+       test_must_fail git commit --pathspec-from-file=list --all -m "Commit" 2>err &&
+       test_i18ngrep -e "--pathspec-from-file with -a does not make sense" err &&
+
+       test_must_fail git commit --pathspec-from-file=list -m "Commit" -- fileA.t 2>err &&
+       test_i18ngrep -e "--pathspec-from-file is incompatible with pathspec arguments" err &&
+
+       test_must_fail git commit --pathspec-file-nul -m "Commit" 2>err &&
+       test_i18ngrep -e "--pathspec-file-nul requires --pathspec-from-file" err &&
+
+       test_must_fail git commit --pathspec-from-file=empty_list --include -m "Commit" 2>err &&
+       test_i18ngrep -e "No paths with --include/--only does not make sense." err &&
+
+       test_must_fail git commit --pathspec-from-file=empty_list --only -m "Commit" 2>err &&
+       test_i18ngrep -e "No paths with --include/--only does not make sense." err
 '
 
 test_done
index d99218a725c5956d75e32684b196bdc050a0e8b1..a426f3a89aa76fb9b89464988211264e56314a60 100755 (executable)
@@ -66,6 +66,20 @@ test_expect_success GPG 'merge commit with untrusted signature with verification
        test_i18ngrep "has an untrusted GPG signature" mergeerror
 '
 
+test_expect_success GPG 'merge commit with untrusted signature with verification and high minTrustLevel' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       test_config gpg.minTrustLevel marginal &&
+       test_must_fail git merge --ff-only --verify-signatures side-untrusted 2>mergeerror &&
+       test_i18ngrep "has an untrusted GPG signature" mergeerror
+'
+
+test_expect_success GPG 'merge commit with untrusted signature with verification and low minTrustLevel' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       test_config gpg.minTrustLevel undefined &&
+       git merge --ff-only --verify-signatures side-untrusted >mergeoutput &&
+       test_i18ngrep "has a good GPG signature" mergeoutput
+'
+
 test_expect_success GPG 'merge commit with untrusted signature with merge.verifySignatures=true' '
        test_when_finished "git reset --hard && git checkout initial" &&
        test_config merge.verifySignatures true &&
@@ -73,6 +87,14 @@ test_expect_success GPG 'merge commit with untrusted signature with merge.verify
        test_i18ngrep "has an untrusted GPG signature" mergeerror
 '
 
+test_expect_success GPG 'merge commit with untrusted signature with merge.verifySignatures=true and minTrustLevel' '
+       test_when_finished "git reset --hard && git checkout initial" &&
+       test_config merge.verifySignatures true &&
+       test_config gpg.minTrustLevel marginal &&
+       test_must_fail git merge --ff-only side-untrusted 2>mergeerror &&
+       test_i18ngrep "has an untrusted GPG signature" mergeerror
+'
+
 test_expect_success GPG 'merge signed commit with verification' '
        test_when_finished "git reset --hard && git checkout initial" &&
        git merge --verbose --ff-only --verify-signatures side-signed >mergeoutput &&
index 6bac9ed180e7342b34401b2d6af9e83b4472f08d..29b92907e2ad850514fe970ca1adeeda8d0697c8 100755 (executable)
@@ -125,15 +125,14 @@ test_expect_success 'difftool stops on error with --trust-exit-code' '
        test_when_finished "rm -f for-diff .git/fail-right-file" &&
        test_when_finished "git reset -- for-diff" &&
        write_script .git/fail-right-file <<-\EOF &&
-       echo "$2"
+       echo failed
        exit 1
        EOF
        >for-diff &&
        git add for-diff &&
-       echo file >expect &&
        test_must_fail git difftool -y --trust-exit-code \
                --extcmd .git/fail-right-file branch >actual &&
-       test_cmp expect actual
+       test_line_count = 1 actual
 '
 
 test_expect_success 'difftool honors exit status if command not found' '
index 946f91fa5782f2810b1286427359c06b64273b3a..828cb3ba5818fd47b6466fd52235e1d5a54cb333 100755 (executable)
@@ -345,7 +345,16 @@ test_incompatible_with_recurse_submodules ()
 }
 
 test_incompatible_with_recurse_submodules --untracked
-test_incompatible_with_recurse_submodules --no-index
+
+test_expect_success 'grep --recurse-submodules --no-index ignores --recurse-submodules' '
+       git grep --recurse-submodules --no-index -e "^(.|.)[\d]" >actual &&
+       cat >expect <<-\EOF &&
+       a:(1|2)d(3|4)
+       submodule/a:(1|2)d(3|4)
+       submodule/sub/a:(1|2)d(3|4)
+       EOF
+       test_cmp expect actual
+'
 
 test_expect_success 'grep --recurse-submodules should pass the pattern type along' '
        # Fixed
index 93877ba9cd625de373cff3a704b17c9bdf7ed379..5505e5aa249e43b88455b0565f0f9348546d5e4c 100755 (executable)
@@ -1363,6 +1363,63 @@ test_expect_success 'teardown after path completion tests' '
               BS\\dir '$'separators\034in\035dir''
 '
 
+test_expect_success '__git_find_on_cmdline - single match' '
+       echo list >expect &&
+       (
+               words=(git command --opt list) &&
+               cword=${#words[@]} &&
+               __git_find_on_cmdline "add list remove" >actual
+       ) &&
+       test_cmp expect actual
+'
+
+test_expect_success '__git_find_on_cmdline - multiple matches' '
+       echo remove >expect &&
+       (
+               words=(git command -o --opt remove list add) &&
+               cword=${#words[@]} &&
+               __git_find_on_cmdline "add list remove" >actual
+       ) &&
+       test_cmp expect actual
+'
+
+test_expect_success '__git_find_on_cmdline - no match' '
+       (
+               words=(git command --opt branch) &&
+               cword=${#words[@]} &&
+               __git_find_on_cmdline "add list remove" >actual
+       ) &&
+       test_must_be_empty actual
+'
+
+test_expect_success '__git_find_on_cmdline - single match with index' '
+       echo "3 list" >expect &&
+       (
+               words=(git command --opt list) &&
+               cword=${#words[@]} &&
+               __git_find_on_cmdline --show-idx "add list remove" >actual
+       ) &&
+       test_cmp expect actual
+'
+
+test_expect_success '__git_find_on_cmdline - multiple matches with index' '
+       echo "4 remove" >expect &&
+       (
+               words=(git command -o --opt remove list add) &&
+               cword=${#words[@]} &&
+               __git_find_on_cmdline --show-idx "add list remove" >actual
+       ) &&
+       test_cmp expect actual
+'
+
+test_expect_success '__git_find_on_cmdline - no match with index' '
+       (
+               words=(git command --opt branch) &&
+               cword=${#words[@]} &&
+               __git_find_on_cmdline --show-idx "add list remove" >actual
+       ) &&
+       test_must_be_empty actual
+'
 
 test_expect_success '__git_get_config_variables' '
        cat >expect <<-EOF &&
index ef94fa293800b31e3982204f43b13785afebd9bd..14ed0aa42de0f291c0f696922110e70544c3dae2 100755 (executable)
@@ -8,102 +8,166 @@ use IPC::Open2;
 # (https://facebook.github.io/watchman/) with git to speed up detecting
 # new and modified files.
 #
-# The hook is passed a version (currently 1) and a time in nanoseconds
-# formatted as a string and outputs to stdout all files that have been
-# modified since the given time. Paths must be relative to the root of
-# the working tree and separated by a single NUL.
+# The hook is passed a version (currently 2) and last update token
+# formatted as a string and outputs to stdout a new update token and
+# all files that have been modified since the update token. Paths must
+# be relative to the root of the working tree and separated by a single NUL.
 #
 # To enable this hook, rename this file to "query-watchman" and set
 # 'git config core.fsmonitor .git/hooks/query-watchman'
 #
-my ($version, $time) = @ARGV;
+my ($version, $last_update_token) = @ARGV;
 
-# Check the hook interface version
+# Uncomment for debugging
+# print STDERR "$0 $version $last_update_token\n";
 
-if ($version == 1) {
-       # convert nanoseconds to seconds
-       # subtract one second to make sure watchman will return all changes
-       $time = int ($time / 1000000000) - 1;
-} else {
+# Check the hook interface version
+if ($version ne 2) {
        die "Unsupported query-fsmonitor hook version '$version'.\n" .
            "Falling back to scanning...\n";
 }
 
-my $git_work_tree;
-if ($^O =~ 'msys' || $^O =~ 'cygwin') {
-       $git_work_tree = Win32::GetCwd();
-       $git_work_tree =~ tr/\\/\//;
-} else {
-       require Cwd;
-       $git_work_tree = Cwd::cwd();
-}
+my $git_work_tree = get_working_dir();
 
 my $retry = 1;
 
+my $json_pkg;
+eval {
+       require JSON::XS;
+       $json_pkg = "JSON::XS";
+       1;
+} or do {
+       require JSON::PP;
+       $json_pkg = "JSON::PP";
+};
+
 launch_watchman();
 
 sub launch_watchman {
+       my $o = watchman_query();
+       if (is_work_tree_watched($o)) {
+               output_result($o->{clock}, @{$o->{files}});
+       }
+}
+
+sub output_result {
+       my ($clockid, @files) = @_;
 
+       # Uncomment for debugging watchman output
+       # open (my $fh, ">", ".git/watchman-output.out");
+       # binmode $fh, ":utf8";
+       # print $fh "$clockid\n@files\n";
+       # close $fh;
+
+       binmode STDOUT, ":utf8";
+       print $clockid;
+       print "\0";
+       local $, = "\0";
+       print @files;
+}
+
+sub watchman_clock {
+       my $response = qx/watchman clock "$git_work_tree"/;
+       die "Failed to get clock id on '$git_work_tree'.\n" .
+               "Falling back to scanning...\n" if $? != 0;
+
+       return $json_pkg->new->utf8->decode($response);
+}
+
+sub watchman_query {
        my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
-           or die "open2() failed: $!\n" .
-           "Falling back to scanning...\n";
+       or die "open2() failed: $!\n" .
+       "Falling back to scanning...\n";
 
        # In the query expression below we're asking for names of files that
-       # changed since $time but were not transient (ie created after
-       # $time but no longer exist).
+       # changed since $last_update_token but not from the .git folder.
        #
        # To accomplish this, we're using the "since" generator to use the
        # recency index to select candidate nodes and "fields" to limit the
-       # output to file names only.
-
+       # output to file names only. Then we're using the "expression" term to
+       # further constrain the results.
+       if (substr($last_update_token, 0, 1) eq "c") {
+               $last_update_token = "\"$last_update_token\"";
+       }
        my $query = <<" END";
                ["query", "$git_work_tree", {
-                       "since": $time,
-                       "fields": ["name"]
+                       "since": $last_update_token,
+                       "fields": ["name"],
+                       "expression": ["not", ["dirname", ".git"]]
                }]
        END
 
+       # Uncomment for debugging the watchman query
+       # open (my $fh, ">", ".git/watchman-query.json");
+       # print $fh $query;
+       # close $fh;
+
        print CHLD_IN $query;
        close CHLD_IN;
        my $response = do {local $/; <CHLD_OUT>};
 
+       # Uncomment for debugging the watch response
+       # open ($fh, ">", ".git/watchman-response.json");
+       # print $fh $response;
+       # close $fh;
+
        die "Watchman: command returned no output.\n" .
-           "Falling back to scanning...\n" if $response eq "";
+       "Falling back to scanning...\n" if $response eq "";
        die "Watchman: command returned invalid output: $response\n" .
-           "Falling back to scanning...\n" unless $response =~ /^\{/;
-
-       my $json_pkg;
-       eval {
-               require JSON::XS;
-               $json_pkg = "JSON::XS";
-               1;
-       } or do {
-               require JSON::PP;
-               $json_pkg = "JSON::PP";
-       };
-
-       my $o = $json_pkg->new->utf8->decode($response);
-
-       if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) {
-               print STDERR "Adding '$git_work_tree' to watchman's watch list.\n";
+       "Falling back to scanning...\n" unless $response =~ /^\{/;
+
+       return $json_pkg->new->utf8->decode($response);
+}
+
+sub is_work_tree_watched {
+       my ($output) = @_;
+       my $error = $output->{error};
+       if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
                $retry--;
-               qx/watchman watch "$git_work_tree"/;
+               my $response = qx/watchman watch "$git_work_tree"/;
                die "Failed to make watchman watch '$git_work_tree'.\n" .
                    "Falling back to scanning...\n" if $? != 0;
+               $output = $json_pkg->new->utf8->decode($response);
+               $error = $output->{error};
+               die "Watchman: $error.\n" .
+               "Falling back to scanning...\n" if $error;
+
+               # Uncomment for debugging watchman output
+               # open (my $fh, ">", ".git/watchman-output.out");
+               # close $fh;
 
                # Watchman will always return all files on the first query so
                # return the fast "everything is dirty" flag to git and do the
                # Watchman query just to get it over with now so we won't pay
                # the cost in git to look up each individual file.
-               print "/\0";
+               my $o = watchman_clock();
+               $error = $output->{error};
+
+               die "Watchman: $error.\n" .
+               "Falling back to scanning...\n" if $error;
+
+               output_result($o->{clock}, ("/"));
+               $last_update_token = $o->{clock};
+
                eval { launch_watchman() };
-               exit 0;
+               return 0;
        }
 
-       die "Watchman: $o->{error}.\n" .
-           "Falling back to scanning...\n" if $o->{error};
+       die "Watchman: $error.\n" .
+       "Falling back to scanning...\n" if $error;
 
-       binmode STDOUT, ":utf8";
-       local $, = "\0";
-       print @{$o->{files}};
+       return 1;
+}
+
+sub get_working_dir {
+       my $working_dir;
+       if ($^O =~ 'msys' || $^O =~ 'cygwin') {
+               $working_dir = Win32::GetCwd();
+               $working_dir =~ tr/\\/\//;
+       } else {
+               require Cwd;
+               $working_dir = Cwd::cwd();
+       }
+
+       return $working_dir;
 }
index 6a756416384c210ada2631f17862f5c01fffa478..e144712c85c055bcf3248ab342592b440a477062 100755 (executable)
@@ -16,7 +16,7 @@ else
 fi
 
 # If you want to allow non-ASCII filenames set this variable to true.
-allownonascii=$(git config --bool hooks.allownonascii)
+allownonascii=$(git config --type=bool hooks.allownonascii)
 
 # Redirect output to stderr.
 exec 1>&2
index 80ba94135cc378364af9d3cb2450df48e51faf2c..5014c4b31cb9700dbd458c8841d8cd91d3b8773c 100755 (executable)
@@ -43,11 +43,11 @@ if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
 fi
 
 # --- Config
-allowunannotated=$(git config --bool hooks.allowunannotated)
-allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
-denycreatebranch=$(git config --bool hooks.denycreatebranch)
-allowdeletetag=$(git config --bool hooks.allowdeletetag)
-allowmodifytag=$(git config --bool hooks.allowmodifytag)
+allowunannotated=$(git config --type=bool hooks.allowunannotated)
+allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch)
+denycreatebranch=$(git config --type=bool hooks.denycreatebranch)
+allowdeletetag=$(git config --type=bool hooks.allowdeletetag)
+allowmodifytag=$(git config --type=bool hooks.allowmodifytag)
 
 # check for no description
 projectdesc=$(sed -e '1q' "$GIT_DIR/description")
index 413d9d873e8623ac40ee3c5912d80d5fb2534722..20a7185ec40e1cf4612c259019f325208632373b 100644 (file)
@@ -404,11 +404,12 @@ static int fetch_with_fetch(struct transport *transport,
        sendline(data, &buf);
 
        while (1) {
+               const char *name;
+
                if (recvline(data, &buf))
                        exit(128);
 
-               if (starts_with(buf.buf, "lock ")) {
-                       const char *name = buf.buf + 5;
+               if (skip_prefix(buf.buf, "lock ", &name)) {
                        if (transport->pack_lockfile)
                                warning(_("%s also locked %s"), data->name, name);
                        else
index 83379a037d69c2f6d97004d41b96f82f10d209dc..1fdc7dac1a6230bdf3492cbebd6642fc825dac5f 100644 (file)
@@ -737,7 +737,7 @@ static int disconnect_git(struct transport *transport)
 {
        struct git_transport_data *data = transport->data;
        if (data->conn) {
-               if (data->got_remote_heads)
+               if (data->got_remote_heads && !transport->stateless_rpc)
                        packet_flush(data->fd[1]);
                close(data->fd[0]);
                close(data->fd[1]);
index 2399b6818be6ddb9a6f4103cec667b2e2a9c24d0..1ecdab330408a1cca7f703b6f2dc8d9b11ae0261 100644 (file)
@@ -291,11 +291,11 @@ static void load_gitmodules_file(struct index_state *index,
        if (pos >= 0) {
                struct cache_entry *ce = index->cache[pos];
                if (!state && ce->ce_flags & CE_WT_REMOVE) {
-                       repo_read_gitmodules(the_repository);
+                       repo_read_gitmodules(the_repository, 0);
                } else if (state && (ce->ce_flags & CE_UPDATE)) {
                        submodule_free(the_repository);
                        checkout_entry(ce, state, NULL, NULL);
-                       repo_read_gitmodules(the_repository);
+                       repo_read_gitmodules(the_repository, 0);
                }
        }
 }
@@ -372,15 +372,20 @@ static int check_updates(struct unpack_trees_options *o)
        state.refresh_cache = 1;
        state.istate = index;
 
+       if (!o->update || o->dry_run) {
+               remove_marked_cache_entries(index, 0);
+               trace_performance_leave("check_updates");
+               return 0;
+       }
+
        if (o->clone)
                setup_collided_checkout_detection(&state, index);
 
        progress = get_progress(o);
 
-       if (o->update)
-               git_attr_set_direction(GIT_ATTR_CHECKOUT);
+       git_attr_set_direction(GIT_ATTR_CHECKOUT);
 
-       if (should_update_submodules() && o->update && !o->dry_run)
+       if (should_update_submodules())
                load_gitmodules_file(index, NULL);
 
        for (i = 0; i < index->cache_nr; i++) {
@@ -388,18 +393,18 @@ static int check_updates(struct unpack_trees_options *o)
 
                if (ce->ce_flags & CE_WT_REMOVE) {
                        display_progress(progress, ++cnt);
-                       if (o->update && !o->dry_run)
-                               unlink_entry(ce);
+                       unlink_entry(ce);
                }
        }
+
        remove_marked_cache_entries(index, 0);
        remove_scheduled_dirs();
 
-       if (should_update_submodules() && o->update && !o->dry_run)
+       if (should_update_submodules())
                load_gitmodules_file(index, &state);
 
        enable_delayed_checkout(&state);
-       if (has_promisor_remote() && o->update && !o->dry_run) {
+       if (has_promisor_remote()) {
                /*
                 * Prefetch the objects that are to be checked out in the loop
                 * below.
@@ -431,15 +436,12 @@ static int check_updates(struct unpack_trees_options *o)
                                    ce->name);
                        display_progress(progress, ++cnt);
                        ce->ce_flags &= ~CE_UPDATE;
-                       if (o->update && !o->dry_run) {
-                               errs |= checkout_entry(ce, &state, NULL, NULL);
-                       }
+                       errs |= checkout_entry(ce, &state, NULL, NULL);
                }
        }
        stop_progress(&progress);
        errs |= finish_delayed_checkout(&state, NULL);
-       if (o->update)
-               git_attr_set_direction(GIT_ATTR_CHECKIN);
+       git_attr_set_direction(GIT_ATTR_CHECKIN);
 
        if (o->clone)
                report_collided_checkout(index);
@@ -694,9 +696,11 @@ static int index_pos_by_traverse_info(struct name_entry *names,
        if (pos >= 0)
                BUG("This is a directory and should not exist in index");
        pos = -pos - 1;
-       if (!starts_with(o->src_index->cache[pos]->name, name.buf) ||
+       if (pos >= o->src_index->cache_nr ||
+           !starts_with(o->src_index->cache[pos]->name, name.buf) ||
            (pos > 0 && starts_with(o->src_index->cache[pos-1]->name, name.buf)))
-               BUG("pos must point at the first entry in this directory");
+               BUG("pos %d doesn't point to the first entry of %s in index",
+                   pos, name.buf);
        strbuf_release(&name);
        return pos;
 }
@@ -1305,14 +1309,14 @@ static int clear_ce_flags_dir(struct index_state *istate,
 
        if (pl->use_cone_patterns && orig_ret == MATCHED_RECURSIVE) {
                struct cache_entry **ce = cache;
-               rc = (cache_end - cache) / sizeof(struct cache_entry *);
+               rc = cache_end - cache;
 
                while (ce < cache_end) {
                        (*ce)->ce_flags &= ~clear_mask;
                        ce++;
                }
        } else if (pl->use_cone_patterns && orig_ret == NOT_MATCHED) {
-               rc = (cache_end - cache) / sizeof(struct cache_entry *);
+               rc = cache_end - cache;
        } else {
                rc = clear_ce_flags_1(istate, cache, cache_end - cache,
                                      prefix,
@@ -1348,7 +1352,7 @@ static int clear_ce_flags_1(struct index_state *istate,
                            enum pattern_match_result default_match,
                            int progress_nr)
 {
-       struct cache_entry **cache_end = cache + nr;
+       struct cache_entry **cache_end = nr ? cache + nr : cache;
 
        /*
         * Process all entries that have the given prefix and meet
@@ -1416,7 +1420,7 @@ static int clear_ce_flags_1(struct index_state *istate,
                                                name, &dtype, pl, istate);
                if (ret == UNDECIDED)
                        ret = default_match;
-               if (ret == MATCHED)
+               if (ret == MATCHED || ret == MATCHED_RECURSIVE)
                        ce->ce_flags &= ~clear_mask;
                cache++;
                progress_nr++;
index 8509f9ea223a1282a367874c3e3a3ef0c351a30f..4d20069302b25a133869380bd685e921ddc0bacc 100644 (file)
@@ -84,8 +84,8 @@ static void trim_common_tail(mmfile_t *a, mmfile_t *b)
 {
        const int blk = 1024;
        long trimmed = 0, recovered = 0;
-       char *ap = a->ptr + a->size;
-       char *bp = b->ptr + b->size;
+       char *ap = a->size ? a->ptr + a->size : a->ptr;
+       char *bp = b->size ? b->ptr + b->size : b->ptr;
        long smaller = (a->size < b->size) ? a->size : b->size;
 
        while (blk + trimmed <= smaller && !memcmp(ap - blk, bp - blk, blk)) {
@@ -250,9 +250,13 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *value, int cflags)
        ALLOC_ARRAY(regs->array, regs->nr);
        for (i = 0; i < regs->nr; i++) {
                struct ff_reg *reg = regs->array + i;
-               const char *ep = strchr(value, '\n'), *expression;
+               const char *ep, *expression;
                char *buffer = NULL;
 
+               if (!value)
+                       BUG("mismatch between line count and parsing");
+               ep = strchr(value, '\n');
+
                reg->negate = (*value == '!');
                if (reg->negate && i == regs->nr - 1)
                        die("Last expression must not be negated: %s", value);
@@ -265,7 +269,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *value, int cflags)
                if (regcomp(&reg->re, expression, cflags))
                        die("Invalid regexp to look for hunk header: %s", expression);
                free(buffer);
-               value = ep + 1;
+               value = ep ? ep + 1 : NULL;
        }
 }