]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'ab/complete-show-all-commands'
authorJunio C Hamano <gitster@pobox.com>
Fri, 18 Feb 2022 00:25:05 +0000 (16:25 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 18 Feb 2022 00:25:06 +0000 (16:25 -0800)
The command line completion script (in contrib/) learned to
complete all Git subcommands, including the ones that are normally
hidden, when GIT_COMPLETION_SHOW_ALL_COMMANDS is used.

* ab/complete-show-all-commands:
  completion: add a GIT_COMPLETION_SHOW_ALL_COMMANDS
  completion tests: re-source git-completion.bash in a subshell

193 files changed:
.gitignore
Documentation/CodingGuidelines
Documentation/RelNotes/2.36.0.txt
Documentation/SubmittingPatches
Documentation/config.txt
Documentation/config/fetch.txt
Documentation/config/gpg.txt
Documentation/config/stash.txt
Documentation/diff-options.txt
Documentation/fetch-options.txt
Documentation/git-cat-file.txt
Documentation/git-check-ignore.txt
Documentation/git-checkout-index.txt
Documentation/git-hook.txt [new file with mode: 0644]
Documentation/git-mktree.txt
Documentation/git-name-rev.txt
Documentation/gitattributes.txt
Documentation/gitcli.txt
Documentation/githooks.txt
Documentation/rev-list-options.txt
Documentation/technical/multi-pack-index.txt
Documentation/technical/pack-format.txt
Makefile
README.md
add-patch.c
apply.c
apply.h
blame.c
builtin.h
builtin/add.c
builtin/am.c
builtin/cat-file.c
builtin/checkout-index.c
builtin/checkout.c
builtin/clean.c
builtin/clone.c
builtin/diff.c
builtin/fetch.c
builtin/gc.c
builtin/hook.c [new file with mode: 0644]
builtin/log.c
builtin/merge.c
builtin/name-rev.c
builtin/patch-id.c
builtin/pull.c
builtin/rebase.c
builtin/receive-pack.c
builtin/reflog.c
builtin/reset.c
builtin/sparse-checkout.c
builtin/stash.c
builtin/update-index.c
builtin/worktree.c
cache.h
ci/lib.sh
command-list.txt
commit.c
commit.h
compat/qsort_s.c
compat/winansi.c
compat/zlib-uncompress2.c
config.c
config.h
config.mak.uname
configure.ac
contrib/buildsystems/CMakeLists.txt
contrib/completion/git-completion.bash
contrib/scalar/scalar.c
contrib/scalar/scalar.txt
contrib/scalar/t/t9099-scalar.sh
contrib/subtree/git-subtree.sh
diff-merges.c
diff.c
diff.h
fetch-negotiator.c
git-compat-util.h
git-p4.py
git-send-email.perl
git-sh-setup.sh
git.c
gpg-interface.c
grep.c
hook.c
hook.h
ll-merge.c
ll-merge.h
log-tree.c
mem-pool.c
merge-blobs.c
merge-ort.c
merge-ort.h
merge-recursive.c
merge-recursive.h
midx.c
midx.h
notes-merge.c
object-name.c
pack-bitmap.c
pack-revindex.c
parse-options.c
parse-options.h
perl/Git.pm
read-cache.c
refs.c
refs.h
refs/files-backend.c
reftable/block.c
reftable/block_test.c
reftable/blocksource.c
reftable/generic.c
reftable/iter.c
reftable/merged.c
reftable/pq.c
reftable/pq_test.c
reftable/reader.c
reftable/readwrite_test.c
reftable/record.c
reftable/record.h
reftable/record_test.c
reftable/reftable-record.h
reftable/reftable.c [deleted file]
reftable/stack.c
reftable/stack_test.c
reftable/system.h
reftable/writer.c
remote.c
repo-settings.c
repository.h
rerere.c
reset.c
revision.c
revision.h
run-command.c
run-command.h
sequencer.c
shallow.c
sparse-index.c
split-index.c
stable-qsort.c
t/helper/test-csprng.c [new file with mode: 0644]
t/helper/test-ref-store.c
t/helper/test-reftable.c
t/helper/test-tool.c
t/helper/test-tool.h
t/lib-bitmap.sh
t/lib-read-tree-m-3way.sh
t/perf/p2000-sparse-operations.sh
t/t0027-auto-crlf.sh
t/t1006-cat-file.sh
t/t1091-sparse-checkout-builtin.sh
t/t1092-sparse-checkout-compatibility.sh
t/t1300-config.sh
t/t1405-main-ref-store.sh
t/t1800-hook.sh [new file with mode: 0755]
t/t2108-update-index-refresh-racy.sh [new file with mode: 0755]
t/t3412-rebase-root.sh
t/t3701-add-interactive.sh
t/t3903-stash.sh
t/t4069-remerge-diff.sh [new file with mode: 0755]
t/t4202-log.sh
t/t4204-patch-id.sh
t/t5310-pack-bitmaps.sh
t/t5312-prune-corruption.sh
t/t5326-multi-pack-bitmaps.sh
t/t5327-multi-pack-bitmaps-rev.sh [new file with mode: 0755]
t/t5500-fetch-pack.sh
t/t5510-fetch.sh
t/t5516-fetch-push.sh
t/t5520-pull.sh
t/t5700-protocol-v1.sh
t/t5702-protocol-v2.sh
t/t6007-rev-list-cherry-pick-file.sh
t/t6012-rev-list-simplify.sh
t/t6111-rev-list-treesame.sh
t/t6120-describe.sh
t/t6404-recursive-merge.sh
t/t6406-merge-attr.sh
t/t6429-merge-sequence-rename-caching.sh
t/t7508-status.sh
t/t7700-repack.sh
t/t8007-cat-file-textconv.sh
t/t9001-send-email.sh
t/t9102-git-svn-deep-rmdir.sh
t/t9123-git-svn-rebuild-with-rewriteroot.sh
t/t9128-git-svn-cmd-branch.sh
t/t9167-git-svn-cmd-branch-subproject.sh
t/t9902-completion.sh
t/test-lib-functions.sh
t/test-lib.sh
tmp-objdir.c
tmp-objdir.h
worktree.c
wrapper.c

index 054249b20a8d91d77a569294f8104e53bdd12f51..f817c509ec052beb22c73814145876ffa235d59e 100644 (file)
@@ -77,6 +77,7 @@
 /git-grep
 /git-hash-object
 /git-help
+/git-hook
 /git-http-backend
 /git-http-fetch
 /git-http-push
index 0e27b5395d8b665fa4c474dc931640a02d4a5f6f..c37c43186ea80e2f11cd1b66c8ed5eaea1c904e3 100644 (file)
@@ -26,6 +26,13 @@ code.  For Git in general, a few rough rules are:
    go and fix it up."
    Cf. http://lkml.iu.edu/hypermail/linux/kernel/1001.3/01069.html
 
+ - Log messages to explain your changes are as important as the
+   changes themselves.  Clearly written code and in-code comments
+   explain how the code works and what is assumed from the surrounding
+   context.  The log messages explain what the changes wanted to
+   achieve and why the changes were necessary (more on this in the
+   accompanying SubmittingPatches document).
+
 Make your code readable and sensible, and don't try to be clever.
 
 As for more concrete guidelines, just imitate the existing code
index c59e0c80e94ec96060b632af53494ff2f761eaf2..3dfa5e409f5d9b0770cf192ce1d5d5e9808b5a21 100644 (file)
@@ -6,7 +6,8 @@ Updates since Git 2.35
 
 Backward compatibility warts
 
- *
+ * "git name-rev --stdin" has been deprecated and issues a warning
+   when used; use "git name-rev --annotate-stdin" instead.
 
 
 Note to those who build from the source
@@ -16,12 +17,35 @@ Note to those who build from the source
 
 UI, Workflows & Features
 
- *
+ * Assorted updates to "git cat-file", especially "-h".
+
+ * The command line completion (in contrib/) learns to complete
+   arguments to give to "git sparse-checkout" command.
+
+ * "git log --remerge-diff" shows the difference from mechanical merge
+   result and the result that is actually recorded in a merge commit.
 
 
 Performance, Internal Implementation, Development Support etc.
 
- *
+ * "git apply" (ab)used the util pointer of the string-list to keep
+   track of how each symbolic link needs to be handled, which has been
+   simplified by using strset.
+
+ * Fix a hand-rolled alloca() imitation that may have violated
+   alignment requirement of data being sorted in compatibility
+   implementation of qsort_s() and stable qsort().
+
+ * Use the parse-options API in "git reflog" command.
+
+ * The conditional inclusion mechanism of configuration files using
+   "[includeIf <condition>]" learns to base its decision on the
+   URL of the remote repository the repository interacts with.
+   (merge 399b198489 jt/conditional-config-on-remote-url later to maint).
+
+ * "git name-rev --stdin" does not behave like usual "--stdin" at
+   all.  Start the process of renaming it to "--annotate-stdin".
+   (merge a2585719b3 jc/name-rev-stdin later to maint).
 
 
 Fixes since v2.35
@@ -29,6 +53,107 @@ Fixes since v2.35
 
  * "rebase" and "stash" in secondary worktrees are broken in
    Git 2.35.0, which has been corrected.
-   (merge ff5b7913f0 en/keep-cwd later to maint).
+
+ * "git pull --rebase" ignored the rebase.autostash configuration
+   variable when the remote history is a descendant of our history,
+   which has been corrected.
+   (merge 3013d98d7a pb/pull-rebase-autostash-fix later to maint).
+
+ * "git update-index --refresh" has been taught to deal better with
+   racy timestamps (just like "git status" already does).
+   (merge 2ede073fd2 ms/update-index-racy later to maint).
+
+ * Avoid tests that are run under GIT_TRACE2 set from failing
+   unnecessarily.
+   (merge 944d808e42 js/test-unset-trace2-parents later to maint).
+
+ * The merge-ort misbehaved when merge.renameLimit configuration is
+   set too low and failed to find all renames.
+   (merge 9ae39fef7f en/merge-ort-restart-optim-fix later to maint).
+
+ * We explain that revs come first before the pathspec among command
+   line arguments, but did not spell out that dashed options come
+   before other args, which has been corrected.
+   (merge c11f95010c tl/doc-cli-options-first later to maint).
+
+ * "git add -p" rewritten in C regressed hunk splitting in some cases,
+   which has been corrected.
+   (merge 7008ddc645 pw/add-p-hunk-split-fix later to maint).
+
+ * "git fetch --negotiate-only" is an internal command used by "git
+   push" to figure out which part of our history is missing from the
+   other side.  It should never recurse into submodules even when
+   fetch.recursesubmodules configuration variable is set, nor it
+   should trigger "gc".  The code has been tightened up to ensure it
+   only does common ancestry discovery and nothing else.
+   (merge de4eaae63a gc/fetch-negotiate-only-early-return later to maint).
+
+ * The code path that verifies signatures made with ssh were made to
+   work better on a system with CRLF line endings.
+   (merge caeef01ea7 fs/ssh-signing-crlf later to maint).
+
+ * "git sparse-checkout init" failed to write into $GIT_DIR/info
+   directory when the repository was created without one, which has
+   been corrected to auto-create it.
+   (merge 7f44842ac1 jt/sparse-checkout-leading-dir-fix later to maint).
+
+ * Cloning from a repository that does not yet have any branches or
+   tags but has other refs resulted in a "remote transport reported
+   error", which has been corrected.
+   (merge dccea605b6 jt/clone-not-quite-empty later to maint).
+
+ * Mark in various places in the code that the sparse index and the
+   split index features are mutually incompatible.
+   (merge 451b66c533 js/sparse-vs-split-index later to maint).
+
+ * Update the logic to compute alignment requirement for our mem-pool.
+   (merge e38bcc66d8 jc/mem-pool-alignment later to maint).
+
+ * Pick a better random number generator and use it when we prepare
+   temporary filenames.
+   (merge 47efda967c bc/csprng-mktemps later to maint).
+
+ * Update the contributor-facing documents on proposed log messages.
+   (merge cdba0295b0 jc/doc-log-messages later to maint).
+
+ * When "git fetch --prune" failed to prune the refs it wanted to
+   prune, the command issued error messages but exited with exit
+   status 0, which has been corrected.
+   (merge c9e04d905e tg/fetch-prune-exit-code-fix later to maint).
+
+ * Problems identified by Coverity in the reftable code have been
+   corrected.
+   (merge 01033de49f hn/reftable-coverity-fixes later to maint).
+
+ * A bug that made multi-pack bitmap and the object order out-of-sync,
+   making the .midx data corrupt, has been fixed.
+   (merge f8b60cf99b tb/midx-bitmap-corruption-fix later to maint).
+
+ * The build procedure has been taught to notice older version of zlib
+   and enable our replacement uncompress2() automatically.
+   (merge 07564773c2 ab/auto-detect-zlib-compress2 later to maint).
+
+ * Interaction between fetch.negotiationAlgorithm and
+   feature.experimental configuration variables has been corrected.
+   (merge 714edc620c en/fetch-negotiation-default-fix later to maint).
+
+ * "git diff --diff-filter=aR" is now parsed correctly.
+   (merge 75408ca949 js/diff-filter-negation-fix later to maint).
 
  * Other code cleanup, docfix, build fix, etc.
+   (merge cfc5cf428b jc/find-header later to maint).
+   (merge 40e7cfdd46 jh/p4-fix-use-of-process-error-exception later to maint).
+   (merge 727e6ea350 jh/p4-spawning-external-commands-cleanup later to maint).
+   (merge 0a6adc26e2 rs/grep-expr-cleanup later to maint).
+   (merge 4ed7dfa713 po/readme-mention-contributor-hints later to maint).
+   (merge 6046f7a91c en/plug-leaks-in-merge later to maint).
+   (merge 8c591dbfce bc/clarify-eol-attr later to maint).
+   (merge 518e15db74 rs/parse-options-lithelp-help later to maint).
+   (merge cbac0076ef gh/doc-typos later to maint).
+   (merge ce14de03db ab/no-errno-from-resolve-ref-unsafe later to maint).
+   (merge 2826ffad8c rc/negotiate-only-typofix later to maint).
+   (merge 0f03f04c5c en/sparse-checkout-leakfix later to maint).
+   (merge 74f3390dde sy/diff-usage-typofix later to maint).
+   (merge 45d0212a71 ll/doc-mktree-typofix later to maint).
+   (merge e9b272e4c1 js/no-more-legacy-stash later to maint).
+   (merge 6798b08e84 ab/do-not-hide-failures-in-git-dot-pm later to maint).
index 92b80d94d488c2f7af6f5be2b5fbded582b65850..a6121d1d4280248ae8d0dd44f8ad5952c6e0ced9 100644 (file)
@@ -110,6 +110,35 @@ run `git diff --check` on your changes before you commit.
 [[describe-changes]]
 === Describe your changes well.
 
+The log message that explains your changes is just as important as the
+changes themselves.  Your code may be clearly written with in-code
+comment to sufficiently explain how it works with the surrounding
+code, but those who need to fix or enhance your code in the future
+will need to know _why_ your code does what it does, for a few
+reasons:
+
+. Your code may be doing something differently from what you wanted it
+  to do.  Writing down what you actually wanted to achieve will help
+  them fix your code and make it do what it should have been doing
+  (also, you often discover your own bugs yourself, while writing the
+  log message to summarize the thought behind it).
+
+. Your code may be doing things that were only necessary for your
+  immediate needs (e.g. "do X to directories" without implementing or
+  even designing what is to be done on files).  Writing down why you
+  excluded what the code does not do will help guide future developers.
+  Writing down "we do X to directories, because directories have
+  characteristic Y" would help them infer "oh, files also have the same
+  characteristic Y, so perhaps doing X to them would also make sense?".
+  Saying "we don't do the same X to files, because ..." will help them
+  decide if the reasoning is sound (in which case they do not waste
+  time extending your code to cover files), or reason differently (in
+  which case, they can explain why they extend your code to cover
+  files, too).
+
+The goal of your log message is to convey the _why_ behind your
+change to help future developers.
+
 The first line of the commit message should be a short description (50
 characters is the soft limit, see DISCUSSION in linkgit:git-commit[1]),
 and should skip the full stop.  It is also conventional in most cases to
@@ -142,6 +171,13 @@ The body should provide a meaningful commit message, which:
 
 . alternate solutions considered but discarded, if any.
 
+[[present-tense]]
+The problem statement that describes the status quo is written in the
+present tense.  Write "The code does X when it is given input Y",
+instead of "The code used to do Y when given input X".  You do not
+have to say "Currently"---the status quo in the problem statement is
+about the code _without_ your change, by project convention.
+
 [[imperative-mood]]
 Describe your changes in imperative mood, e.g. "make xyzzy do frotz"
 instead of "[This patch] makes xyzzy do frotz" or "[I] changed xyzzy
index b168f02dc3d92c77be4d37061403a2909e6235c8..bf3e512921fe75744ead531d3fa8252aa7039885 100644 (file)
@@ -159,6 +159,33 @@ all branches that begin with `foo/`. This is useful if your branches are
 organized hierarchically and you would like to apply a configuration to
 all the branches in that hierarchy.
 
+`hasconfig:remote.*.url:`::
+       The data that follows this keyword is taken to
+       be a pattern with standard globbing wildcards and two
+       additional ones, `**/` and `/**`, that can match multiple
+       components. The first time this keyword is seen, the rest of
+       the config files will be scanned for remote URLs (without
+       applying any values). If there exists at least one remote URL
+       that matches this pattern, the include condition is met.
++
+Files included by this option (directly or indirectly) are not allowed
+to contain remote URLs.
++
+Note that unlike other includeIf conditions, resolving this condition
+relies on information that is not yet known at the point of reading the
+condition. A typical use case is this option being present as a
+system-level or global-level config, and the remote URL being in a
+local-level config; hence the need to scan ahead when resolving this
+condition. In order to avoid the chicken-and-egg problem in which
+potentially-included files can affect whether such files are potentially
+included, Git breaks the cycle by prohibiting these files from affecting
+the resolution of these conditions (thus, prohibiting them from
+declaring remote URLs).
++
+As for the naming of this keyword, it is for forwards compatibiliy with
+a naming scheme that supports more variable-based include conditions,
+but currently Git only supports the exact keyword described above.
+
 A few more notes on matching via `gitdir` and `gitdir/i`:
 
  * Symlinks in `$GIT_DIR` are not resolved before matching.
@@ -226,6 +253,14 @@ Example
 ; currently checked out
 [includeIf "onbranch:foo-branch"]
        path = foo.inc
+
+; include only if a remote with the given URL exists (note
+; that such a URL may be provided later in a file or in a
+; file read after this file is read, as seen in this example)
+[includeIf "hasconfig:remote.*.url:https://example.com/**"]
+       path = foo.inc
+[remote "origin"]
+       url = https://example.com/git
 ----
 
 Values
index 63748c02b72afe5211a710e963c2be502888545e..cd65d236b43ffc3a7c08fa9df480fcbe6c82e68c 100644 (file)
@@ -56,18 +56,19 @@ fetch.output::
        OUTPUT in linkgit:git-fetch[1] for detail.
 
 fetch.negotiationAlgorithm::
-       Control how information about the commits in the local repository is
-       sent when negotiating the contents of the packfile to be sent by the
-       server. Set to "skipping" to use an algorithm that skips commits in an
-       effort to converge faster, but may result in a larger-than-necessary
-       packfile; or set to "noop" to not send any information at all, which
-       will almost certainly result in a larger-than-necessary packfile, but
-       will skip the negotiation step.
-       The default is "default" which instructs Git to use the default algorithm
-       that never skips commits (unless the server has acknowledged it or one
-       of its descendants). If `feature.experimental` is enabled, then this
-       setting defaults to "skipping".
-       Unknown values will cause 'git fetch' to error out.
+       Control how information about the commits in the local repository
+       is sent when negotiating the contents of the packfile to be sent by
+       the server.  Set to "consecutive" to use an algorithm that walks
+       over consecutive commits checking each one.  Set to "skipping" to
+       use an algorithm that skips commits in an effort to converge
+       faster, but may result in a larger-than-necessary packfile; or set
+       to "noop" to not send any information at all, which will almost
+       certainly result in a larger-than-necessary packfile, but will skip
+       the negotiation step.  Set to "default" to override settings made
+       previously and use the default behaviour.  The default is normally
+       "consecutive", but if `feature.experimental` is true, then the
+       default is "skipping".  Unknown values will cause 'git fetch' to
+       error out.
 +
 See also the `--negotiate-only` and `--negotiation-tip` options to
 linkgit:git-fetch[1].
index 0cb189a077ca54ed83a6dfefad9be327f7176d40..86892ada771e802830cd10d3e5ff95bc74355e9e 100644 (file)
@@ -37,7 +37,7 @@ gpg.minTrustLevel::
 gpg.ssh.defaultKeyCommand::
        This command that will be run when user.signingkey is not set and a ssh
        signature is requested. On successful exit a valid ssh public key is
-       expected in the first line of its output. To automatically use the first
+       expected in the first line of its output. To automatically use the first
        available key from your ssh-agent set this to "ssh-add -L".
 
 gpg.ssh.allowedSignersFile::
@@ -66,7 +66,7 @@ This way only committers with an already valid key can add or change keys in the
 +
 Since OpensSSH 8.8 this file allows specifying a key lifetime using valid-after &
 valid-before options. Git will mark signatures as valid if the signing key was
-valid at the time of the signatures creation. This allows users to change a
+valid at the time of the signature's creation. This allows users to change a
 signing key without invalidating all previously made signatures.
 +
 Using a SSH CA key with the cert-authority option
index 9ed775281fb34f4e7ce0f7277784b7d74164daa1..b9f609ed76b7f3ff41aef03ecf0ea22fd1d355ea 100644 (file)
@@ -1,10 +1,3 @@
-stash.useBuiltin::
-       Unused configuration variable.  Used in Git versions 2.22 to
-       2.26 as an escape hatch to enable the legacy shellscript
-       implementation of stash.  Now the built-in rewrite of it in C
-       is always used. Setting this will emit a warning, to alert any
-       remaining users that setting this now does nothing.
-
 stash.showIncludeUntracked::
        If this is set to true, the `git stash show` command will show
        the untracked files of a stash entry.  Defaults to false. See
index c89d530d3d1d39dbed0a46b3fa3f6def92ed7def..3674ac48e92c29e2a9fe5db0ccf8ce72e7727290 100644 (file)
@@ -34,7 +34,7 @@ endif::git-diff[]
 endif::git-format-patch[]
 
 ifdef::git-log[]
---diff-merges=(off|none|on|first-parent|1|separate|m|combined|c|dense-combined|cc)::
+--diff-merges=(off|none|on|first-parent|1|separate|m|combined|c|dense-combined|cc|remerge|r)::
 --no-diff-merges::
        Specify diff format to be used for merge commits. Default is
        {diff-merges-default} unless `--first-parent` is in use, in which case
@@ -64,6 +64,18 @@ ifdef::git-log[]
        each of the parents. Separate log entry and diff is generated
        for each parent.
 +
+--diff-merges=remerge:::
+--diff-merges=r:::
+--remerge-diff:::
+       With this option, two-parent merge commits are remerged to
+       create a temporary tree object -- potentially containing files
+       with conflict markers and such.  A diff is then shown between
+       that temporary tree and the actual merge commit.
++
+The output emitted when this option is used is subject to change, and
+so is its interaction with other options (unless explicitly
+documented).
++
 --diff-merges=combined:::
 --diff-merges=c:::
 -c:::
@@ -616,11 +628,8 @@ ifndef::git-format-patch[]
 Also, these upper-case letters can be downcased to exclude.  E.g.
 `--diff-filter=ad` excludes added and deleted paths.
 +
-Note that not all diffs can feature all types. For instance, diffs
-from the index to the working tree can never have Added entries
-(because the set of paths included in the diff is limited by what is in
-the index).  Similarly, copied and renamed entries cannot appear if
-detection for those types is disabled.
+Note that not all diffs can feature all types. For instance, copied and
+renamed entries cannot appear if detection for those types is disabled.
 
 -S<string>::
        Look for differences that change the number of occurrences of
index e967ff1874c252cdd73455f73146b4419b58f9c9..f9036831898f4ea275d6682f17c17479d4fb0953 100644 (file)
@@ -71,6 +71,7 @@ configuration variables documented in linkgit:git-config[1], and the
        ancestors of the provided `--negotiation-tip=*` arguments,
        which we have in common with the server.
 +
+This is incompatible with `--recurse-submodules=[yes|on-demand]`.
 Internally this is used to implement the `push.negotiate` option, see
 linkgit:git-config[1].
 
index 27b27e2b300c49bb07f348291a8b04af3d128f30..bef76f4dd060dd7fdd4efee44f53b34a3c546c13 100644 (file)
@@ -9,8 +9,14 @@ git-cat-file - Provide content or type and size information for repository objec
 SYNOPSIS
 --------
 [verse]
-'git cat-file' (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -e | -p | <type> | --textconv | --filters ) [--path=<path>] <object>
-'git cat-file' (--batch[=<format>] | --batch-check[=<format>]) [ --textconv | --filters ] [--follow-symlinks]
+'git cat-file' <type> <object>
+'git cat-file' (-e | -p) <object>
+'git cat-file' (-t | -s) [--allow-unknown-type] <object>
+'git cat-file' (--batch | --batch-check) [--batch-all-objects]
+            [--buffer] [--follow-symlinks] [--unordered]
+            [--textconv | --filters]
+'git cat-file' (--textconv | --filters)
+            [<rev>:<path|tree-ish> | --path=<path|tree-ish> <rev>]
 
 DESCRIPTION
 -----------
index 0c3924a63d2f8625c91cca9ceb391214c3fc3d41..2892799e32f9855bba3b5244e7051d11618fa846 100644 (file)
@@ -33,7 +33,7 @@ OPTIONS
        Instead of printing the paths that are excluded, for each path
        that matches an exclude pattern, print the exclude pattern
        together with the path.  (Matching an exclude pattern usually
-       means the path is excluded, but if the pattern begins with '!'
+       means the path is excluded, but if the pattern begins with "`!`"
        then it is a negated pattern and matching it means the path is
        NOT excluded.)
 +
@@ -77,7 +77,7 @@ If `--verbose` is specified, the output is a series of lines of the form:
 <pathname> is the path of a file being queried, <pattern> is the
 matching pattern, <source> is the pattern's source file, and <linenum>
 is the line number of the pattern within that source.  If the pattern
-contained a `!` prefix or `/` suffix, it will be preserved in the
+contained a "`!`" prefix or "`/`" suffix, it will be preserved in the
 output.  <source> will be an absolute path when referring to the file
 configured by `core.excludesFile`, or relative to the repository root
 when referring to `.git/info/exclude` or a per-directory exclude file.
index 4d33e7be0f5599cc3bb74a45cb0d20fcde1631e8..01dbd5cbf540ea96de2a1fd9b9ce0580077c29fa 100644 (file)
@@ -12,6 +12,7 @@ SYNOPSIS
 'git checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>]
                   [--stage=<number>|all]
                   [--temp]
+                  [--ignore-skip-worktree-bits]
                   [-z] [--stdin]
                   [--] [<file>...]
 
@@ -37,8 +38,9 @@ OPTIONS
 
 -a::
 --all::
-       checks out all files in the index.  Cannot be used
-       together with explicit filenames.
+       checks out all files in the index except for those with the
+       skip-worktree bit set (see `--ignore-skip-worktree-bits`).
+       Cannot be used together with explicit filenames.
 
 -n::
 --no-create::
@@ -59,6 +61,10 @@ OPTIONS
        write the content to temporary files.  The temporary name
        associations will be written to stdout.
 
+--ignore-skip-worktree-bits::
+       Check out all files, including those with the skip-worktree bit
+       set.
+
 --stdin::
        Instead of taking list of paths from the command line,
        read list of paths from the standard input.  Paths are
diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt
new file mode 100644 (file)
index 0000000..77c3a8a
--- /dev/null
@@ -0,0 +1,45 @@
+git-hook(1)
+===========
+
+NAME
+----
+git-hook - Run git hooks
+
+SYNOPSIS
+--------
+[verse]
+'git hook' run [--ignore-missing] <hook-name> [-- <hook-args>]
+
+DESCRIPTION
+-----------
+
+A command interface to running git hooks (see linkgit:githooks[5]),
+for use by other scripted git commands.
+
+SUBCOMMANDS
+-----------
+
+run::
+       Run the `<hook-name>` hook. See linkgit:githooks[5] for
+       supported hook names.
++
+
+Any positional arguments to the hook should be passed after a
+mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See
+linkgit:githooks[5] for arguments hooks might expect (if any).
+
+OPTIONS
+-------
+
+--ignore-missing::
+       Ignore any missing hook by quietly returning zero. Used for
+       tools that want to do a blind one-shot run of a hook that may
+       or may not be present.
+
+SEE ALSO
+--------
+linkgit:githooks[5]
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 27fe2b32e10b2f0c92315483ac4a5e8a9722d3db..76b44f4da103872d9b39c893b62286d37b159693 100644 (file)
@@ -31,7 +31,7 @@ OPTIONS
 
 --batch::
        Allow building of more than one tree object before exiting.  Each
-       tree is separated by as single blank line. The final new-line is
+       tree is separated by a single blank line. The final new-line is
        optional.  Note - if the `-z` option is used, lines are terminated
        with NUL.
 
index 5cb0eb0855fefe582721baeb3295beb610a74891..ec8a27ce8bf8d689bb5f64554dd5426ce38262c0 100644 (file)
@@ -42,11 +42,37 @@ OPTIONS
 --all::
        List all commits reachable from all refs
 
---stdin::
+--annotate-stdin::
        Transform stdin by substituting all the 40-character SHA-1
        hexes (say $hex) with "$hex ($rev_name)".  When used with
        --name-only, substitute with "$rev_name", omitting $hex
-       altogether.  Intended for the scripter's use.
+       altogether.
++
+For example:
++
+-----------
+$ cat sample.txt
+
+An abbreviated revision 2ae0a9cb82 will not be substituted.
+The full name after substitution is 2ae0a9cb8298185a94e5998086f380a355dd8907,
+while its tree object is 70d105cc79e63b81cfdcb08a15297c23e60b07ad
+
+$ git name-rev --annotate-stdin <sample.txt
+
+An abbreviated revision 2ae0a9cb82 will not be substituted.
+The full name after substitution is 2ae0a9cb8298185a94e5998086f380a355dd8907 (master),
+while its tree object is 70d105cc79e63b81cfdcb08a15297c23e60b07ad
+
+$ git name-rev --name-only --annotate-stdin <sample.txt
+
+An abbreviated revision 2ae0a9cb82 will not be substituted.
+The full name after substitution is master,
+while its tree object is 70d105cc79e63b81cfdcb08a15297c23e60b07ad
+-----------
+
+--stdin::
+       This option is deprecated in favor of 'git name-rev --annotate-stdin'.
+       They are functionally equivalent.
 
 --name-only::
        Instead of printing both the SHA-1 and the name, print only
index 83fd4e19a410ae7e4028e77384653d1c637aaa08..60984a468245258337ed8da33f5d559ad780615b 100644 (file)
@@ -160,11 +160,12 @@ unspecified.
 ^^^^^
 
 This attribute sets a specific line-ending style to be used in the
-working directory.  It enables end-of-line conversion without any
-content checks, effectively setting the `text` attribute.  Note that
-setting this attribute on paths which are in the index with CRLF line
-endings may make the paths to be considered dirty.  Adding the path to
-the index again will normalize the line endings in the index.
+working directory.  This attribute has effect only if the `text`
+attribute is set or unspecified, or if it is set to `auto` and the file
+is detected as text.  Note that setting this attribute on paths which
+are in the index with CRLF line endings may make the paths to be
+considered dirty. Adding the path to the index again will normalize the
+line endings in the index.
 
 Set to string value "crlf"::
 
index 92e4ba6a2fa9c4fd491d8c458dd4e807e08ac07a..1819a5a1859c5479064823bd786e98267f7037b5 100644 (file)
@@ -19,6 +19,15 @@ Many commands take revisions (most often "commits", but sometimes
 "tree-ish", depending on the context and command) and paths as their
 arguments.  Here are the rules:
 
+ * Options come first and then args.
+    A subcommand may take dashed options (which may take their own
+    arguments, e.g. "--max-parents 2") and arguments.  You SHOULD
+    give dashed options first and then arguments.  Some commands may
+    accept dashed options after you have already gave non-option
+    arguments (which may make the command ambiguous), but you should
+    not rely on it (because eventually we may find a way to fix
+    these ambiguity by enforcing the "options then args" rule).
+
  * Revisions come first and then paths.
    E.g. in `git diff v1.0 v2.0 arch/x86 include/asm-x86`,
    `v1.0` and `v2.0` are revisions and `arch/x86` and `include/asm-x86`
@@ -72,24 +81,24 @@ you will.
 Here are the rules regarding the "flags" that you should follow when you are
 scripting Git:
 
- * it's preferred to use the non-dashed form of Git commands, which means that
+ * It's preferred to use the non-dashed form of Git commands, which means that
    you should prefer `git foo` to `git-foo`.
 
- * splitting short options to separate words (prefer `git foo -a -b`
+ * Splitting short options to separate words (prefer `git foo -a -b`
    to `git foo -ab`, the latter may not even work).
 
- * when a command-line option takes an argument, use the 'stuck' form.  In
+ * When a command-line option takes an argument, use the 'stuck' form.  In
    other words, write `git foo -oArg` instead of `git foo -o Arg` for short
    options, and `git foo --long-opt=Arg` instead of `git foo --long-opt Arg`
    for long options.  An option that takes optional option-argument must be
    written in the 'stuck' form.
 
- * when you give a revision parameter to a command, make sure the parameter is
+ * When you give a revision parameter to a command, make sure the parameter is
    not ambiguous with a name of a file in the work tree.  E.g. do not write
    `git log -1 HEAD` but write `git log -1 HEAD --`; the former will not work
    if you happen to have a file called `HEAD` in the work tree.
 
- * many commands allow a long option `--option` to be abbreviated
+ * Many commands allow a long option `--option` to be abbreviated
    only to their unique prefix (e.g. if there is no other option
    whose name begins with `opt`, you may be able to spell `--opt` to
    invoke the `--option` flag), but you should fully spell them out
index b51959ff9418fd75583f41d77fe2603617ed563c..a16e62bc8c8ea7fd8bcb419515258b76ef682f61 100644 (file)
@@ -698,6 +698,10 @@ and "0" meaning they were not.
 Only one parameter should be set to "1" when the hook runs.  The hook
 running passing "1", "1" should not be possible.
 
+SEE ALSO
+--------
+linkgit:git-hook[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index 43a86fa5627ed07e40504948f34de6b04fb5c115..fd4f4e26c90face314632ac6c2b81bed648abd17 100644 (file)
@@ -122,19 +122,27 @@ again.  Equivalent forms are `--min-parents=0` (any commit has 0 or more
 parents) and `--max-parents=-1` (negative numbers denote no upper limit).
 
 --first-parent::
-       Follow only the first parent commit upon seeing a merge
-       commit.  This option can give a better overview when
-       viewing the evolution of a particular topic branch,
-       because merges into a topic branch tend to be only about
-       adjusting to updated upstream from time to time, and
-       this option allows you to ignore the individual commits
-       brought in to your history by such a merge.
+       When finding commits to include, follow only the first
+       parent commit upon seeing a merge commit.  This option
+       can give a better overview when viewing the evolution of
+       a particular topic branch, because merges into a topic
+       branch tend to be only about adjusting to updated upstream
+       from time to time, and this option allows you to ignore
+       the individual commits brought in to your history by such
+       a merge.
 ifdef::git-log[]
 +
 This option also changes default diff format for merge commits
 to `first-parent`, see `--diff-merges=first-parent` for details.
 endif::git-log[]
 
+--exclude-first-parent-only::
+       When finding commits to exclude (with a '{caret}'), follow only
+       the first parent commit upon seeing a merge commit.
+       This can be used to find the set of changes in a topic branch
+       from the point where it diverged from the remote branch, given
+       that arbitrary merges can be valid topic branch changes.
+
 --not::
        Reverses the meaning of the '{caret}' prefix (or lack thereof)
        for all following revision specifiers, up to the next `--not`.
index b39c69da8cd4166cd5ded92b0896fe2e1fdea005..f2221d2b441ef82c4b3639fb9cd9010734d0a2b6 100644 (file)
@@ -24,6 +24,7 @@ and their offsets into multiple packfiles. It contains:
 ** An offset within the jth packfile for the object.
 * If large offsets are required, we use another list of large
   offsets similar to version 2 pack-indexes.
+- An optional list of objects in pseudo-pack order (used with MIDX bitmaps).
 
 Thus, we can provide O(log N) lookup time for any number
 of packfiles.
index 8d2f42f29e58cd32930e2953d2b1599eff022c92..6d3efb7d16e10683ab3ad8e6bf87217e443f350e 100644 (file)
@@ -376,6 +376,11 @@ CHUNK DATA:
        [Optional] Object Large Offsets (ID: {'L', 'O', 'F', 'F'})
            8-byte offsets into large packfiles.
 
+       [Optional] Bitmap pack order (ID: {'R', 'I', 'D', 'X'})
+           A list of MIDX positions (one per object in the MIDX, num_objects in
+           total, each a 4-byte unsigned integer in network byte order), sorted
+           according to their relative bitmap/pseudo-pack positions.
+
 TRAILER:
 
        Index checksum of the above contents.
@@ -456,9 +461,5 @@ In short, a MIDX's pseudo-pack is the de-duplicated concatenation of
 objects in packs stored by the MIDX, laid out in pack order, and the
 packs arranged in MIDX order (with the preferred pack coming first).
 
-Finally, note that the MIDX's reverse index is not stored as a chunk in
-the multi-pack-index itself. This is done because the reverse index
-includes the checksum of the pack or MIDX to which it belongs, which
-makes it impossible to write in the MIDX. To avoid races when rewriting
-the MIDX, a MIDX reverse index includes the MIDX's checksum in its
-filename (e.g., `multi-pack-index-xyz.rev`).
+The MIDX's reverse index is stored in the optional 'RIDX' chunk within
+the MIDX itself.
index 5580859afdb45b44459798fa490f9b53e426079b..6f0b4b775fece6acd20348e318a8cff3e254bcb2 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -234,6 +234,14 @@ all::
 # Define NO_TRUSTABLE_FILEMODE if your filesystem may claim to support
 # the executable mode bit, but doesn't really do so.
 #
+# Define CSPRNG_METHOD to "arc4random" if your system has arc4random and
+# arc4random_buf, "libbsd" if your system has those functions from libbsd,
+# "getrandom" if your system has getrandom, "getentropy" if your system has
+# getentropy, "rtlgenrandom" for RtlGenRandom (Windows only), or "openssl" if
+# you'd want to use the OpenSSL CSPRNG.  You may set multiple options with
+# spaces, in which case a suitable option will be chosen.  If unset or set to
+# anything else, defaults to using "/dev/urandom".
+#
 # Define NEEDS_MODE_TRANSLATION if your OS strays from the typical file type
 # bits in mode values (e.g. z/OS defines I_SFMT to 0xFF000000 as opposed to the
 # usual 0xF000).
@@ -256,8 +264,6 @@ all::
 #
 # Define NO_DEFLATE_BOUND if your zlib does not have deflateBound.
 #
-# Define NO_UNCOMPRESS2 if your zlib does not have uncompress2.
-#
 # Define NO_NORETURN if using buggy versions of gcc 4.6+ and profile feedback,
 # as the compiler can crash (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49299)
 #
@@ -693,6 +699,7 @@ TEST_BUILTINS_OBJS += test-bloom.o
 TEST_BUILTINS_OBJS += test-chmtime.o
 TEST_BUILTINS_OBJS += test-config.o
 TEST_BUILTINS_OBJS += test-crontab.o
+TEST_BUILTINS_OBJS += test-csprng.o
 TEST_BUILTINS_OBJS += test-ctype.o
 TEST_BUILTINS_OBJS += test-date.o
 TEST_BUILTINS_OBJS += test-delta.o
@@ -862,6 +869,7 @@ LIB_OBJS += commit-reach.o
 LIB_OBJS += commit.o
 LIB_OBJS += compat/obstack.o
 LIB_OBJS += compat/terminal.o
+LIB_OBJS += compat/zlib-uncompress2.o
 LIB_OBJS += config.o
 LIB_OBJS += connect.o
 LIB_OBJS += connected.o
@@ -1109,6 +1117,7 @@ BUILTIN_OBJS += builtin/get-tar-commit-id.o
 BUILTIN_OBJS += builtin/grep.o
 BUILTIN_OBJS += builtin/hash-object.o
 BUILTIN_OBJS += builtin/help.o
+BUILTIN_OBJS += builtin/hook.o
 BUILTIN_OBJS += builtin/index-pack.o
 BUILTIN_OBJS += builtin/init-db.o
 BUILTIN_OBJS += builtin/interpret-trailers.o
@@ -1194,7 +1203,8 @@ THIRD_PARTY_SOURCES += compat/regex/%
 THIRD_PARTY_SOURCES += sha1collisiondetection/%
 THIRD_PARTY_SOURCES += sha1dc/%
 
-GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB)
+# xdiff and reftable libs may in turn depend on what is in libgit.a
+GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)
 EXTLIBS =
 
 GIT_USER_AGENT = git/$(GIT_VERSION)
@@ -1726,11 +1736,6 @@ ifdef NO_DEFLATE_BOUND
        BASIC_CFLAGS += -DNO_DEFLATE_BOUND
 endif
 
-ifdef NO_UNCOMPRESS2
-       BASIC_CFLAGS += -DNO_UNCOMPRESS2
-       REFTABLE_OBJS += compat/zlib-uncompress2.o
-endif
-
 ifdef NO_POSIX_GOODIES
        BASIC_CFLAGS += -DNO_POSIX_GOODIES
 endif
@@ -1908,6 +1913,31 @@ ifdef HAVE_GETDELIM
        BASIC_CFLAGS += -DHAVE_GETDELIM
 endif
 
+ifneq ($(findstring arc4random,$(CSPRNG_METHOD)),)
+       BASIC_CFLAGS += -DHAVE_ARC4RANDOM
+endif
+
+ifneq ($(findstring libbsd,$(CSPRNG_METHOD)),)
+       BASIC_CFLAGS += -DHAVE_ARC4RANDOM_LIBBSD
+       EXTLIBS += -lbsd
+endif
+
+ifneq ($(findstring getrandom,$(CSPRNG_METHOD)),)
+       BASIC_CFLAGS += -DHAVE_GETRANDOM
+endif
+
+ifneq ($(findstring getentropy,$(CSPRNG_METHOD)),)
+       BASIC_CFLAGS += -DHAVE_GETENTROPY
+endif
+
+ifneq ($(findstring rtlgenrandom,$(CSPRNG_METHOD)),)
+       BASIC_CFLAGS += -DHAVE_RTLGENRANDOM
+endif
+
+ifneq ($(findstring openssl,$(CSPRNG_METHOD)),)
+       BASIC_CFLAGS += -DHAVE_OPENSSL_CSPRNG
+endif
+
 ifneq ($(PROCFS_EXECUTABLE_PATH),)
        procfs_executable_path_SQ = $(subst ','\'',$(PROCFS_EXECUTABLE_PATH))
        BASIC_CFLAGS += '-DPROCFS_EXECUTABLE_PATH="$(procfs_executable_path_SQ)"'
index f6f43e78debd8e4dac7e483068c1f9c764beb0f5..7ce4f05bae8120d9fa258e854a8669f6ea9cb7b1 100644 (file)
--- a/README.md
+++ b/README.md
@@ -32,10 +32,16 @@ installed).
 The user discussion and development of Git take place on the Git
 mailing list -- everyone is welcome to post bug reports, feature
 requests, comments and patches to git@vger.kernel.org (read
-[Documentation/SubmittingPatches][] for instructions on patch submission).
+[Documentation/SubmittingPatches][] for instructions on patch submission
+and [Documentation/CodingGuidelines][]).
+
+Those wishing to help with error message, usage and informational message
+string translations (localization l10) should see [po/README.md][]
+(a `po` file is a Portable Object file that holds the translations).
+
 To subscribe to the list, send an email with just "subscribe git" in
-the body to majordomo@vger.kernel.org. The mailing list archives are
-available at <https://lore.kernel.org/git/>,
+the body to majordomo@vger.kernel.org (not the Git list). The mailing
+list archives are available at <https://lore.kernel.org/git/>,
 <http://marc.info/?l=git> and other archival sites.
 
 Issues which are security relevant should be disclosed privately to
@@ -64,3 +70,5 @@ and the name as (depending on your mood):
 [Documentation/giteveryday.txt]: Documentation/giteveryday.txt
 [Documentation/gitcvs-migration.txt]: Documentation/gitcvs-migration.txt
 [Documentation/SubmittingPatches]: Documentation/SubmittingPatches
+[Documentation/CodingGuidelines]: Documentation/CodingGuidelines
+[po/README.md]: po/README.md
index 573eef0cc4a86642bc92fba74763aaa913af1b2f..55d719f78451635848db00552b77542057879ff4 100644 (file)
@@ -383,6 +383,17 @@ static int is_octal(const char *p, size_t len)
        return 1;
 }
 
+static void complete_file(char marker, struct hunk *hunk)
+{
+       if (marker == '-' || marker == '+')
+               /*
+                * Last hunk ended in non-context line (i.e. it
+                * appended lines to the file, so there are no
+                * trailing context lines).
+                */
+               hunk->splittable_into++;
+}
+
 static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
 {
        struct strvec args = STRVEC_INIT;
@@ -472,6 +483,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
                        eol = pend;
 
                if (starts_with(p, "diff ")) {
+                       complete_file(marker, hunk);
                        ALLOC_GROW_BY(s->file_diff, s->file_diff_nr, 1,
                                   file_diff_alloc);
                        file_diff = s->file_diff + s->file_diff_nr - 1;
@@ -598,13 +610,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
                                file_diff->hunk->colored_end = hunk->colored_end;
                }
        }
-
-       if (marker == '-' || marker == '+')
-               /*
-                * Last hunk ended in non-context line (i.e. it appended lines
-                * to the file, so there are no trailing context lines).
-                */
-               hunk->splittable_into++;
+       complete_file(marker, hunk);
 
        /* non-colored shorter than colored? */
        if (colored_p != colored_pend) {
diff --git a/apply.c b/apply.c
index 7ffadc3b17a314095e213ad31ceda83a9915afb2..0912307bd91a5005d9a4b57e2a4324541e247edc 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -103,7 +103,8 @@ int init_apply_state(struct apply_state *state,
        state->linenr = 1;
        string_list_init_nodup(&state->fn_table);
        string_list_init_nodup(&state->limit_by_name);
-       string_list_init_nodup(&state->symlink_changes);
+       strset_init(&state->removed_symlinks);
+       strset_init(&state->kept_symlinks);
        strbuf_init(&state->root, 0);
 
        git_apply_config();
@@ -117,7 +118,8 @@ int init_apply_state(struct apply_state *state,
 void clear_apply_state(struct apply_state *state)
 {
        string_list_clear(&state->limit_by_name, 0);
-       string_list_clear(&state->symlink_changes, 0);
+       strset_clear(&state->removed_symlinks);
+       strset_clear(&state->kept_symlinks);
        strbuf_release(&state->root);
 
        /* &state->fn_table is cleared at the end of apply_patch() */
@@ -3492,7 +3494,7 @@ static int three_way_merge(struct apply_state *state,
 {
        mmfile_t base_file, our_file, their_file;
        mmbuffer_t result = { NULL };
-       int status;
+       enum ll_merge_result status;
 
        /* resolve trivial cases first */
        if (oideq(base, ours))
@@ -3509,6 +3511,9 @@ static int three_way_merge(struct apply_state *state,
                          &their_file, "theirs",
                          state->repo->index,
                          NULL);
+       if (status == LL_MERGE_BINARY_CONFLICT)
+               warning("Cannot merge binary files: %s (%s vs. %s)",
+                       path, "ours", "theirs");
        free(base_file.ptr);
        free(our_file.ptr);
        free(their_file.ptr);
@@ -3814,59 +3819,31 @@ static int check_to_create(struct apply_state *state,
        return 0;
 }
 
-static uintptr_t register_symlink_changes(struct apply_state *state,
-                                         const char *path,
-                                         uintptr_t what)
-{
-       struct string_list_item *ent;
-
-       ent = string_list_lookup(&state->symlink_changes, path);
-       if (!ent) {
-               ent = string_list_insert(&state->symlink_changes, path);
-               ent->util = (void *)0;
-       }
-       ent->util = (void *)(what | ((uintptr_t)ent->util));
-       return (uintptr_t)ent->util;
-}
-
-static uintptr_t check_symlink_changes(struct apply_state *state, const char *path)
-{
-       struct string_list_item *ent;
-
-       ent = string_list_lookup(&state->symlink_changes, path);
-       if (!ent)
-               return 0;
-       return (uintptr_t)ent->util;
-}
-
 static void prepare_symlink_changes(struct apply_state *state, struct patch *patch)
 {
        for ( ; patch; patch = patch->next) {
                if ((patch->old_name && S_ISLNK(patch->old_mode)) &&
                    (patch->is_rename || patch->is_delete))
                        /* the symlink at patch->old_name is removed */
-                       register_symlink_changes(state, patch->old_name, APPLY_SYMLINK_GOES_AWAY);
+                       strset_add(&state->removed_symlinks, patch->old_name);
 
                if (patch->new_name && S_ISLNK(patch->new_mode))
                        /* the symlink at patch->new_name is created or remains */
-                       register_symlink_changes(state, patch->new_name, APPLY_SYMLINK_IN_RESULT);
+                       strset_add(&state->kept_symlinks, patch->new_name);
        }
 }
 
 static int path_is_beyond_symlink_1(struct apply_state *state, struct strbuf *name)
 {
        do {
-               unsigned int change;
-
                while (--name->len && name->buf[name->len] != '/')
                        ; /* scan backwards */
                if (!name->len)
                        break;
                name->buf[name->len] = '\0';
-               change = check_symlink_changes(state, name->buf);
-               if (change & APPLY_SYMLINK_IN_RESULT)
+               if (strset_contains(&state->kept_symlinks, name->buf))
                        return 1;
-               if (change & APPLY_SYMLINK_GOES_AWAY)
+               if (strset_contains(&state->removed_symlinks, name->buf))
                        /*
                         * This cannot be "return 0", because we may
                         * see a new one created at a higher level.
diff --git a/apply.h b/apply.h
index 16202da16026f8ef127e406418229d1f47802b53..4052da50c0658cf6434b262bba6f557459e7950c 100644 (file)
--- a/apply.h
+++ b/apply.h
@@ -4,6 +4,7 @@
 #include "hash.h"
 #include "lockfile.h"
 #include "string-list.h"
+#include "strmap.h"
 
 struct repository;
 
@@ -25,20 +26,6 @@ enum apply_verbosity {
        verbosity_verbose = 1
 };
 
-/*
- * We need to keep track of how symlinks in the preimage are
- * manipulated by the patches.  A patch to add a/b/c where a/b
- * is a symlink should not be allowed to affect the directory
- * the symlink points at, but if the same patch removes a/b,
- * it is perfectly fine, as the patch removes a/b to make room
- * to create a directory a/b so that a/b/c can be created.
- *
- * See also "struct string_list symlink_changes" in "struct
- * apply_state".
- */
-#define APPLY_SYMLINK_GOES_AWAY 01
-#define APPLY_SYMLINK_IN_RESULT 02
-
 struct apply_state {
        const char *prefix;
 
@@ -86,7 +73,16 @@ struct apply_state {
 
        /* Various "current state" */
        int linenr; /* current line number */
-       struct string_list symlink_changes; /* we have to track symlinks */
+       /*
+        * We need to keep track of how symlinks in the preimage are
+        * manipulated by the patches.  A patch to add a/b/c where a/b
+        * is a symlink should not be allowed to affect the directory
+        * the symlink points at, but if the same patch removes a/b,
+        * it is perfectly fine, as the patch removes a/b to make room
+        * to create a directory a/b so that a/b/c can be created.
+        */
+       struct strset removed_symlinks;
+       struct strset kept_symlinks;
 
        /*
         * For "diff-stat" like behaviour, we keep track of the biggest change
diff --git a/blame.c b/blame.c
index 206c295660f29b1fd44842ef0f28caf4d2e04788..083d99fdbc831637474ea63d0d109108f73675c1 100644 (file)
--- a/blame.c
+++ b/blame.c
@@ -2615,7 +2615,7 @@ void assign_blame(struct blame_scoreboard *sb, int opt)
                else {
                        commit->object.flags |= UNINTERESTING;
                        if (commit->object.parsed)
-                               mark_parents_uninteresting(commit);
+                               mark_parents_uninteresting(sb->revs, commit);
                }
                /* treat root commit as boundary */
                if (!commit->parents && !sb->show_root)
index 8a58743ed63d039f762f56c18f5c6e6debe0faba..83379f3832c17eefd8290d26acd6dd641d1f520d 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -164,6 +164,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
 int cmd_grep(int argc, const char **argv, const char *prefix);
 int cmd_hash_object(int argc, const char **argv, const char *prefix);
 int cmd_help(int argc, const char **argv, const char *prefix);
+int cmd_hook(int argc, const char **argv, const char *prefix);
 int cmd_index_pack(int argc, const char **argv, const char *prefix);
 int cmd_init_db(int argc, const char **argv, const char *prefix);
 int cmd_interpret_trailers(int argc, const char **argv, const char *prefix);
index 84dff3e796918ada3777fd7c01114ff5c9521fec..3ffb86a43384f21cad4fdcc0d8549e37dba12227 100644 (file)
@@ -32,7 +32,6 @@ static int add_renormalize;
 static int pathspec_file_nul;
 static int include_sparse;
 static const char *pathspec_from_file;
-static int legacy_stash_p; /* support for the scripted `git stash` */
 
 struct update_callback_data {
        int flags;
@@ -388,8 +387,6 @@ 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(),
@@ -512,17 +509,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                        die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--interactive/--patch");
                exit(interactive_add(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)
index b6be1f1cb11e47dcb1012e1afe60fbae654f25cd..7de2c89ef22c7f9a4292f34ddb3278547791ebeb 100644 (file)
@@ -474,7 +474,7 @@ static int run_applypatch_msg_hook(struct am_state *state)
        int ret;
 
        assert(state->msg);
-       ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL);
+       ret = run_hooks_l("applypatch-msg", am_path(state, "final-commit"), NULL);
 
        if (!ret) {
                FREE_AND_NULL(state->msg);
@@ -1636,7 +1636,7 @@ static void do_commit(const struct am_state *state)
        const char *reflog_msg, *author, *committer = NULL;
        struct strbuf sb = STRBUF_INIT;
 
-       if (run_hook_le(NULL, "pre-applypatch", NULL))
+       if (run_hooks("pre-applypatch"))
                exit(1);
 
        if (write_cache_as_tree(&tree, 0, NULL))
@@ -1688,7 +1688,7 @@ static void do_commit(const struct am_state *state)
                fclose(fp);
        }
 
-       run_hook_le(NULL, "post-applypatch", NULL);
+       run_hooks("post-applypatch");
 
        strbuf_release(&sb);
 }
index d94050e6c188ff4594a065da87695c67c560ac94..7b3f42950ec88e56e2eac6ac31910f983501a399 100644 (file)
@@ -73,14 +73,17 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
        struct object_info oi = OBJECT_INFO_INIT;
        struct strbuf sb = STRBUF_INIT;
        unsigned flags = OBJECT_INFO_LOOKUP_REPLACE;
+       unsigned get_oid_flags = GET_OID_RECORD_PATH | GET_OID_ONLY_TO_DIE;
        const char *path = force_path;
+       const int opt_cw = (opt == 'c' || opt == 'w');
+       if (!path && opt_cw)
+               get_oid_flags |= GET_OID_REQUIRE_PATH;
 
        if (unknown_type)
                flags |= OBJECT_INFO_ALLOW_UNKNOWN_TYPE;
 
-       if (get_oid_with_context(the_repository, obj_name,
-                                GET_OID_RECORD_PATH,
-                                &oid, &obj_context))
+       if (get_oid_with_context(the_repository, obj_name, get_oid_flags, &oid,
+                                &obj_context))
                die("Not a valid object name %s", obj_name);
 
        if (!path)
@@ -112,9 +115,6 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
                return !has_object_file(&oid);
 
        case 'w':
-               if (!path)
-                       die("git cat-file --filters %s: <object> must be "
-                           "<sha1:path>", obj_name);
 
                if (filter_object(path, obj_context.mode,
                                  &oid, &buf, &size))
@@ -122,10 +122,6 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
                break;
 
        case 'c':
-               if (!path)
-                       die("git cat-file --textconv %s: <object> must be <sha1:path>",
-                           obj_name);
-
                if (textconv_object(the_repository, path, obj_context.mode,
                                    &oid, 1, &buf, &size))
                        break;
@@ -618,12 +614,6 @@ static int batch_objects(struct batch_options *opt)
        return retval;
 }
 
-static const char * const cat_file_usage[] = {
-       N_("git cat-file (-t [--allow-unknown-type] | -s [--allow-unknown-type] | -e | -p | <type> | --textconv | --filters) [--path=<path>] <object>"),
-       N_("git cat-file (--batch[=<format>] | --batch-check[=<format>]) [--follow-symlinks] [--textconv | --filters]"),
-       NULL
-};
-
 static int git_cat_file_config(const char *var, const char *value, void *cb)
 {
        if (userdiff_config(var, value) < 0)
@@ -654,90 +644,138 @@ static int batch_option_callback(const struct option *opt,
 int cmd_cat_file(int argc, const char **argv, const char *prefix)
 {
        int opt = 0;
+       int opt_cw = 0;
+       int opt_epts = 0;
        const char *exp_type = NULL, *obj_name = NULL;
        struct batch_options batch = {0};
        int unknown_type = 0;
 
+       const char * const usage[] = {
+               N_("git cat-file <type> <object>"),
+               N_("git cat-file (-e | -p) <object>"),
+               N_("git cat-file (-t | -s) [--allow-unknown-type] <object>"),
+               N_("git cat-file (--batch | --batch-check) [--batch-all-objects]\n"
+                  "             [--buffer] [--follow-symlinks] [--unordered]\n"
+                  "             [--textconv | --filters]"),
+               N_("git cat-file (--textconv | --filters)\n"
+                  "             [<rev>:<path|tree-ish> | --path=<path|tree-ish> <rev>]"),
+               NULL
+       };
        const struct option options[] = {
-               OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")),
-               OPT_CMDMODE('t', NULL, &opt, N_("show object type"), 't'),
-               OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'),
+               /* Simple queries */
+               OPT_GROUP(N_("Check object existence or emit object contents")),
                OPT_CMDMODE('e', NULL, &opt,
-                           N_("exit with zero when there's no error"), 'e'),
-               OPT_CMDMODE('p', NULL, &opt, N_("pretty-print object's content"), 'p'),
-               OPT_CMDMODE(0, "textconv", &opt,
-                           N_("for blob objects, run textconv on object's content"), 'c'),
-               OPT_CMDMODE(0, "filters", &opt,
-                           N_("for blob objects, run filters on object's content"), 'w'),
-               OPT_STRING(0, "path", &force_path, N_("blob"),
-                          N_("use a specific path for --textconv/--filters")),
+                           N_("check if <object> exists"), 'e'),
+               OPT_CMDMODE('p', NULL, &opt, N_("pretty-print <object> content"), 'p'),
+
+               OPT_GROUP(N_("Emit [broken] object attributes")),
+               OPT_CMDMODE('t', NULL, &opt, N_("show object type (one of 'blob', 'tree', 'commit', 'tag', ...)"), 't'),
+               OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'),
                OPT_BOOL(0, "allow-unknown-type", &unknown_type,
                          N_("allow -s and -t to work with broken/corrupt objects")),
-               OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")),
-               OPT_CALLBACK_F(0, "batch", &batch, "format",
-                       N_("show info and content of objects fed from the standard input"),
+               /* Batch mode */
+               OPT_GROUP(N_("Batch objects requested on stdin (or --batch-all-objects)")),
+               OPT_CALLBACK_F(0, "batch", &batch, N_("format"),
+                       N_("show full <object> or <rev> contents"),
                        PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
                        batch_option_callback),
-               OPT_CALLBACK_F(0, "batch-check", &batch, "format",
-                       N_("show info about objects fed from the standard input"),
+               OPT_CALLBACK_F(0, "batch-check", &batch, N_("format"),
+                       N_("like --batch, but don't emit <contents>"),
                        PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
                        batch_option_callback),
+               OPT_CMDMODE(0, "batch-all-objects", &opt,
+                           N_("with --batch[-check]: ignores stdin, batches all known objects"), 'b'),
+               /* Batch-specific options */
+               OPT_GROUP(N_("Change or optimize batch output")),
+               OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")),
                OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks,
-                        N_("follow in-tree symlinks (used with --batch or --batch-check)")),
-               OPT_BOOL(0, "batch-all-objects", &batch.all_objects,
-                        N_("show all objects with --batch or --batch-check")),
+                        N_("follow in-tree symlinks")),
                OPT_BOOL(0, "unordered", &batch.unordered,
-                        N_("do not order --batch-all-objects output")),
+                        N_("do not order objects before emitting them")),
+               /* Textconv options, stand-ole*/
+               OPT_GROUP(N_("Emit object (blob or tree) with conversion or filter (stand-alone, or with batch)")),
+               OPT_CMDMODE(0, "textconv", &opt,
+                           N_("run textconv on object's content"), 'c'),
+               OPT_CMDMODE(0, "filters", &opt,
+                           N_("run filters on object's content"), 'w'),
+               OPT_STRING(0, "path", &force_path, N_("blob|tree"),
+                          N_("use a <path> for (--textconv | --filters); Not with 'batch'")),
                OPT_END()
        };
 
        git_config(git_cat_file_config, NULL);
 
        batch.buffer_output = -1;
-       argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0);
-
-       if (opt) {
-               if (batch.enabled && (opt == 'c' || opt == 'w'))
-                       batch.cmdmode = opt;
-               else if (argc == 1)
-                       obj_name = argv[0];
-               else
-                       usage_with_options(cat_file_usage, options);
-       }
-       if (!opt && !batch.enabled) {
-               if (argc == 2) {
-                       exp_type = argv[0];
-                       obj_name = argv[1];
-               } else
-                       usage_with_options(cat_file_usage, options);
-       }
-       if (batch.enabled) {
-               if (batch.cmdmode != opt || argc)
-                       usage_with_options(cat_file_usage, options);
-               if (batch.cmdmode && batch.all_objects)
-                       die("--batch-all-objects cannot be combined with "
-                           "--textconv nor with --filters");
-       }
 
-       if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) {
-               usage_with_options(cat_file_usage, options);
-       }
+       argc = parse_options(argc, argv, prefix, options, usage, 0);
+       opt_cw = (opt == 'c' || opt == 'w');
+       opt_epts = (opt == 'e' || opt == 'p' || opt == 't' || opt == 's');
 
-       if (force_path && opt != 'c' && opt != 'w') {
-               error("--path=<path> needs --textconv or --filters");
-               usage_with_options(cat_file_usage, options);
-       }
+       /* --batch-all-objects? */
+       if (opt == 'b')
+               batch.all_objects = 1;
 
-       if (force_path && batch.enabled) {
-               error("options '--path=<path>' and '--batch' cannot be used together");
-               usage_with_options(cat_file_usage, options);
-       }
+       /* Option compatibility */
+       if (force_path && !opt_cw)
+               usage_msg_optf(_("'%s=<%s>' needs '%s' or '%s'"),
+                              usage, options,
+                              "--path", _("path|tree-ish"), "--filters",
+                              "--textconv");
 
+       /* Option compatibility with batch mode */
+       if (batch.enabled)
+               ;
+       else if (batch.follow_symlinks)
+               usage_msg_optf(_("'%s' requires a batch mode"), usage, options,
+                              "--follow-symlinks");
+       else if (batch.buffer_output >= 0)
+               usage_msg_optf(_("'%s' requires a batch mode"), usage, options,
+                              "--buffer");
+       else if (batch.all_objects)
+               usage_msg_optf(_("'%s' requires a batch mode"), usage, options,
+                              "--batch-all-objects");
+
+       /* Batch defaults */
        if (batch.buffer_output < 0)
                batch.buffer_output = batch.all_objects;
 
-       if (batch.enabled)
+       /* Return early if we're in batch mode? */
+       if (batch.enabled) {
+               if (opt_cw)
+                       batch.cmdmode = opt;
+               else if (opt && opt != 'b')
+                       usage_msg_optf(_("'-%c' is incompatible with batch mode"),
+                                      usage, options, opt);
+               else if (argc)
+                       usage_msg_opt(_("batch modes take no arguments"), usage,
+                                     options);
+
                return batch_objects(&batch);
+       }
+
+       if (opt) {
+               if (!argc && opt == 'c')
+                       usage_msg_optf(_("<rev> required with '%s'"),
+                                      usage, options, "--textconv");
+               else if (!argc && opt == 'w')
+                       usage_msg_optf(_("<rev> required with '%s'"),
+                                      usage, options, "--filters");
+               else if (!argc && opt_epts)
+                       usage_msg_optf(_("<object> required with '-%c'"),
+                                      usage, options, opt);
+               else if (argc == 1)
+                       obj_name = argv[0];
+               else
+                       usage_msg_opt(_("too many arguments"), usage, options);
+       } else if (!argc) {
+               usage_with_options(usage, options);
+       } else if (argc != 2) {
+               usage_msg_optf(_("only two arguments allowed in <type> <object> mode, not %d"),
+                             usage, options, argc);
+       } else if (argc) {
+               exp_type = argv[0];
+               obj_name = argv[1];
+       }
 
        if (unknown_type && opt != 't' && opt != 's')
                die("git cat-file --allow-unknown-type: use with -s or -t");
index e21620d964e5e89f7254dc4d99597d220fb3d650..97e06e8c52c012c918558cd73aa65d5750b8118b 100644 (file)
@@ -7,6 +7,7 @@
 #define USE_THE_INDEX_COMPATIBILITY_MACROS
 #include "builtin.h"
 #include "config.h"
+#include "dir.h"
 #include "lockfile.h"
 #include "quote.h"
 #include "cache-tree.h"
@@ -17,6 +18,7 @@
 #define CHECKOUT_ALL 4
 static int nul_term_line;
 static int checkout_stage; /* default to checkout stage0 */
+static int ignore_skip_worktree; /* default to 0 */
 static int to_tempfile;
 static char topath[4][TEMPORARY_FILENAME_LENGTH + 1];
 
@@ -65,6 +67,8 @@ static int checkout_file(const char *name, const char *prefix)
        int namelen = strlen(name);
        int pos = cache_name_pos(name, namelen);
        int has_same_name = 0;
+       int is_file = 0;
+       int is_skipped = 1;
        int did_checkout = 0;
        int errs = 0;
 
@@ -78,6 +82,12 @@ static int checkout_file(const char *name, const char *prefix)
                        break;
                has_same_name = 1;
                pos++;
+               if (S_ISSPARSEDIR(ce->ce_mode))
+                       break;
+               is_file = 1;
+               if (!ignore_skip_worktree && ce_skip_worktree(ce))
+                       break;
+               is_skipped = 0;
                if (ce_stage(ce) != checkout_stage
                    && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
                        continue;
@@ -106,6 +116,11 @@ static int checkout_file(const char *name, const char *prefix)
                fprintf(stderr, "git checkout-index: %s ", name);
                if (!has_same_name)
                        fprintf(stderr, "is not in the cache");
+               else if (!is_file)
+                       fprintf(stderr, "is a sparse directory");
+               else if (is_skipped)
+                       fprintf(stderr, "has skip-worktree enabled; "
+                                       "use '--ignore-skip-worktree-bits' to checkout");
                else if (checkout_stage)
                        fprintf(stderr, "does not exist at stage %d",
                                checkout_stage);
@@ -121,10 +136,27 @@ static int checkout_all(const char *prefix, int prefix_length)
        int i, errs = 0;
        struct cache_entry *last_ce = NULL;
 
-       /* TODO: audit for interaction with sparse-index. */
-       ensure_full_index(&the_index);
        for (i = 0; i < active_nr ; i++) {
                struct cache_entry *ce = active_cache[i];
+
+               if (S_ISSPARSEDIR(ce->ce_mode)) {
+                       if (!ce_skip_worktree(ce))
+                               BUG("sparse directory '%s' does not have skip-worktree set", ce->name);
+
+                       /*
+                        * If the current entry is a sparse directory and skip-worktree
+                        * entries are being checked out, expand the index and continue
+                        * the loop on the current index position (now pointing to the
+                        * first entry inside the expanded sparse directory).
+                        */
+                       if (ignore_skip_worktree) {
+                               ensure_full_index(&the_index);
+                               ce = active_cache[i];
+                       }
+               }
+
+               if (!ignore_skip_worktree && ce_skip_worktree(ce))
+                       continue;
                if (ce_stage(ce) != checkout_stage
                    && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
                        continue;
@@ -185,6 +217,8 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
        struct option builtin_checkout_index_options[] = {
                OPT_BOOL('a', "all", &all,
                        N_("check out all files in the index")),
+               OPT_BOOL(0, "ignore-skip-worktree-bits", &ignore_skip_worktree,
+                       N_("do not skip files with skip-worktree set")),
                OPT__FORCE(&force, N_("force overwrite of existing files"), 0),
                OPT__QUIET(&quiet,
                        N_("no warning for existing files and files not in index")),
@@ -212,6 +246,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
        git_config(git_default_config, NULL);
        prefix_length = prefix ? strlen(prefix) : 0;
 
+       prepare_repo_settings(the_repository);
+       the_repository->settings.command_requires_full_index = 0;
+
        if (read_cache() < 0) {
                die("invalid cache");
        }
index cc804ba8e1e6cdb8748eabc7e91575ac2ee53a4a..f6e65fedfa2bbbc3cf7d180dc26e9881c6c0c2d6 100644 (file)
@@ -9,6 +9,7 @@
 #include "config.h"
 #include "diff.h"
 #include "dir.h"
+#include "hook.h"
 #include "ll-merge.h"
 #include "lockfile.h"
 #include "merge-recursive.h"
@@ -114,7 +115,7 @@ static void branch_info_release(struct branch_info *info)
 static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
                              int changed)
 {
-       return run_hook_le(NULL, "post-checkout",
+       return run_hooks_l("post-checkout",
                           oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()),
                           oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()),
                           changed ? "1" : "0", NULL);
@@ -245,6 +246,7 @@ static int checkout_merged(int pos, const struct checkout *state,
        struct cache_entry *ce = active_cache[pos];
        const char *path = ce->name;
        mmfile_t ancestor, ours, theirs;
+       enum ll_merge_result merge_status;
        int status;
        struct object_id oid;
        mmbuffer_t result_buf;
@@ -275,13 +277,16 @@ static int checkout_merged(int pos, const struct checkout *state,
        memset(&ll_opts, 0, sizeof(ll_opts));
        git_config_get_bool("merge.renormalize", &renormalize);
        ll_opts.renormalize = renormalize;
-       status = ll_merge(&result_buf, path, &ancestor, "base",
-                         &ours, "ours", &theirs, "theirs",
-                         state->istate, &ll_opts);
+       merge_status = ll_merge(&result_buf, path, &ancestor, "base",
+                               &ours, "ours", &theirs, "theirs",
+                               state->istate, &ll_opts);
        free(ancestor.ptr);
        free(ours.ptr);
        free(theirs.ptr);
-       if (status < 0 || !result_buf.ptr) {
+       if (merge_status == LL_MERGE_BINARY_CONFLICT)
+               warning("Cannot merge binary files: %s (%s vs. %s)",
+                       path, "ours", "theirs");
+       if (merge_status < 0 || !result_buf.ptr) {
                free(result_buf.ptr);
                return error(_("path '%s': cannot merge"), path);
        }
index 3ff02bbbffeb7ed041b8a9af7f0b6f6d26c88492..5466636e66604ef5331a6f508d7d4a62e330d1e7 100644 (file)
@@ -1009,6 +1009,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
                dir.flags |= DIR_KEEP_UNTRACKED_CONTENTS;
        }
 
+       prepare_repo_settings(the_repository);
+       the_repository->settings.command_requires_full_index = 0;
+
        if (read_cache() < 0)
                die(_("index file corrupt"));
 
index 727e16e0aea435a1fe244c381d8bb7670e8e7a5a..9c29093b3528fc7f1b899868b0b44fb33ac30b06 100644 (file)
@@ -32,6 +32,7 @@
 #include "connected.h"
 #include "packfile.h"
 #include "list-objects-filter-options.h"
+#include "hook.h"
 
 /*
  * Overall FIXMEs:
@@ -705,7 +706,7 @@ static int checkout(int submodule_progress)
        if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
                die(_("unable to write new index file"));
 
-       err |= run_hook_le(NULL, "post-checkout", oid_to_hex(null_oid()),
+       err |= run_hooks_l("post-checkout", oid_to_hex(null_oid()),
                           oid_to_hex(&oid), "1", NULL);
 
        if (!err && (option_recurse_submodules.nr > 0)) {
@@ -862,7 +863,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        const struct ref *refs, *remote_head;
        struct ref *remote_head_points_at = NULL;
        const struct ref *our_head_points_at;
-       struct ref *mapped_refs;
+       struct ref *mapped_refs = NULL;
        const struct ref *ref;
        struct strbuf key = STRBUF_INIT;
        struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
@@ -1184,7 +1185,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
        refs = transport_get_remote_refs(transport, &transport_ls_refs_options);
 
-       if (refs) {
+       if (refs)
+               mapped_refs = wanted_peer_refs(refs, &remote->fetch);
+
+       if (mapped_refs) {
                int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));
 
                /*
@@ -1193,8 +1197,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                 */
                initialize_repository_version(hash_algo, 1);
                repo_set_hash_algo(the_repository, hash_algo);
-
-               mapped_refs = wanted_peer_refs(refs, &remote->fetch);
                /*
                 * transport_get_remote_refs() may return refs with null sha-1
                 * in mapped_refs (see struct transport->get_refs_list
@@ -1240,7 +1242,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                                        option_branch, remote_name);
 
                warning(_("You appear to have cloned an empty repository."));
-               mapped_refs = NULL;
                our_head_points_at = NULL;
                remote_head_points_at = NULL;
                remote_head = NULL;
@@ -1271,7 +1272,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
        if (is_local)
                clone_local(path, git_dir);
-       else if (refs && complete_refs_before_fetch) {
+       else if (mapped_refs && complete_refs_before_fetch) {
                if (transport_fetch_refs(transport, mapped_refs))
                        die(_("remote transport reported error"));
        }
index fa4683377ebbe51ee42d964d29a30011f12f1261..bb7fafca61815460ba085859322314b4c41c8b6b 100644 (file)
@@ -28,9 +28,9 @@ static const char builtin_diff_usage[] =
 "git diff [<options>] [<commit>] [--] [<path>...]\n"
 "   or: git diff [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]\n"
 "   or: git diff [<options>] [--merge-base] <commit> [<commit>...] <commit> [--] [<path>...]\n"
-"   or: git diff [<options>] <commit>...<commit>] [--] [<path>...]\n"
-"   or: git diff [<options>] <blob> <blob>]\n"
-"   or: git diff [<options>] --no-index [--] <path> <path>]\n"
+"   or: git diff [<options>] <commit>...<commit> [--] [<path>...]\n"
+"   or: git diff [<options>] <blob> <blob>\n"
+"   or: git diff [<options>] --no-index [--] <path> <path>\n"
 COMMON_DIFF_OPTIONS_HELP;
 
 static const char *blob_path(struct object_array_entry *entry)
index 5f06b21f8e97c5459fdb558302c5b9b95e99eee8..6f5e157863923da28c6394cc86f359abb97f9e01 100644 (file)
@@ -76,6 +76,7 @@ static struct transport *gtransport;
 static struct transport *gsecondary;
 static const char *submodule_prefix = "";
 static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
+static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;
 static int recurse_submodules_default = RECURSE_SUBMODULES_ON_DEMAND;
 static int shown_url = 0;
 static struct refspec refmap = REFSPEC_INIT_FETCH;
@@ -167,7 +168,7 @@ static struct option builtin_fetch_options[] = {
                 N_("prune remote-tracking branches no longer on remote")),
        OPT_BOOL('P', "prune-tags", &prune_tags,
                 N_("prune local tags no longer on remote and clobber changed tags")),
-       OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules, N_("on-demand"),
+       OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules_cli, N_("on-demand"),
                    N_("control recursive fetching of submodules"),
                    PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),
        OPT_BOOL(0, "dry-run", &dry_run,
@@ -1609,12 +1610,14 @@ static int do_fetch(struct transport *transport,
                 * don't care whether --tags was specified.
                 */
                if (rs->nr) {
-                       prune_refs(rs, ref_map, transport->url);
+                       retcode = prune_refs(rs, ref_map, transport->url);
                } else {
-                       prune_refs(&transport->remote->fetch,
-                                  ref_map,
-                                  transport->url);
+                       retcode = prune_refs(&transport->remote->fetch,
+                                            ref_map,
+                                            transport->url);
                }
+               if (retcode != 0)
+                       retcode = 1;
        }
        if (fetch_and_consume_refs(transport, ref_map, worktrees)) {
                free_refs(ref_map);
@@ -2019,6 +2022,28 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
 
        argc = parse_options(argc, argv, prefix,
                             builtin_fetch_options, builtin_fetch_usage, 0);
+
+       if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT)
+               recurse_submodules = recurse_submodules_cli;
+
+       if (negotiate_only) {
+               switch (recurse_submodules_cli) {
+               case RECURSE_SUBMODULES_OFF:
+               case RECURSE_SUBMODULES_DEFAULT:
+                       /*
+                        * --negotiate-only should never recurse into
+                        * submodules. Skip it by setting recurse_submodules to
+                        * RECURSE_SUBMODULES_OFF.
+                        */
+                       recurse_submodules = RECURSE_SUBMODULES_OFF;
+                       break;
+
+               default:
+                       die(_("options '%s' and '%s' cannot be used together"),
+                           "--negotiate-only", "--recurse-submodules");
+               }
+       }
+
        if (recurse_submodules != RECURSE_SUBMODULES_OFF) {
                int *sfjc = submodule_fetch_jobs_config == -1
                            ? &submodule_fetch_jobs_config : NULL;
@@ -2029,7 +2054,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        }
 
        if (negotiate_only && !negotiation_tip.nr)
-               die(_("--negotiate-only needs one or more --negotiate-tip=*"));
+               die(_("--negotiate-only needs one or more --negotiation-tip=*"));
 
        if (deepen_relative) {
                if (deepen_relative < 0)
@@ -2100,7 +2125,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                        gtransport->smart_options->acked_commits = &acked_commits;
                } else {
                        warning(_("protocol does not support --negotiate-only, exiting"));
-                       return 1;
+                       result = 1;
+                       goto cleanup;
                }
                if (server_options.nr)
                        gtransport->server_options = &server_options;
@@ -2156,7 +2182,16 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                strvec_clear(&options);
        }
 
-       string_list_clear(&list, 0);
+       /*
+        * Skip irrelevant tasks because we know objects were not
+        * fetched.
+        *
+        * NEEDSWORK: as a future optimization, we can return early
+        * whenever objects were not fetched e.g. if we already have all
+        * of them.
+        */
+       if (negotiate_only)
+               goto cleanup;
 
        prepare_repo_settings(the_repository);
        if (fetch_write_commit_graph > 0 ||
@@ -2175,5 +2210,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        if (enable_auto_gc)
                run_auto_maintenance(verbosity < 0);
 
+ cleanup:
+       string_list_clear(&list, 0);
        return result;
 }
index 8e60ef1eaba426eae66c99d15d5dfdcbc26efac4..ffaf0daf5d9b565e0be4c35e586ad1054f981061 100644 (file)
@@ -32,6 +32,7 @@
 #include "remote.h"
 #include "object-store.h"
 #include "exec-cmd.h"
+#include "hook.h"
 
 #define FAILED_RUN "failed to run %s"
 
@@ -394,7 +395,7 @@ static int need_to_gc(void)
        else
                return 0;
 
-       if (run_hook_le(NULL, "pre-auto-gc", NULL))
+       if (run_hooks("pre-auto-gc"))
                return 0;
        return 1;
 }
diff --git a/builtin/hook.c b/builtin/hook.c
new file mode 100644 (file)
index 0000000..54e5c6e
--- /dev/null
@@ -0,0 +1,84 @@
+#include "cache.h"
+#include "builtin.h"
+#include "config.h"
+#include "hook.h"
+#include "parse-options.h"
+#include "strbuf.h"
+#include "strvec.h"
+
+#define BUILTIN_HOOK_RUN_USAGE \
+       N_("git hook run [--ignore-missing] <hook-name> [-- <hook-args>]")
+
+static const char * const builtin_hook_usage[] = {
+       BUILTIN_HOOK_RUN_USAGE,
+       NULL
+};
+
+static const char * const builtin_hook_run_usage[] = {
+       BUILTIN_HOOK_RUN_USAGE,
+       NULL
+};
+
+static int run(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+       int ignore_missing = 0;
+       const char *hook_name;
+       struct option run_options[] = {
+               OPT_BOOL(0, "ignore-missing", &ignore_missing,
+                        N_("silently ignore missing requested <hook-name>")),
+               OPT_END(),
+       };
+       int ret;
+
+       argc = parse_options(argc, argv, prefix, run_options,
+                            builtin_hook_run_usage,
+                            PARSE_OPT_KEEP_DASHDASH);
+
+       if (!argc)
+               goto usage;
+
+       /*
+        * Having a -- for "run" when providing <hook-args> is
+        * mandatory.
+        */
+       if (argc > 1 && strcmp(argv[1], "--") &&
+           strcmp(argv[1], "--end-of-options"))
+               goto usage;
+
+       /* Add our arguments, start after -- */
+       for (i = 2 ; i < argc; i++)
+               strvec_push(&opt.args, argv[i]);
+
+       /* Need to take into account core.hooksPath */
+       git_config(git_default_config, NULL);
+
+       hook_name = argv[0];
+       if (!ignore_missing)
+               opt.error_if_missing = 1;
+       ret = run_hooks_opt(hook_name, &opt);
+       if (ret < 0) /* error() return */
+               ret = 1;
+       return ret;
+usage:
+       usage_with_options(builtin_hook_run_usage, run_options);
+}
+
+int cmd_hook(int argc, const char **argv, const char *prefix)
+{
+       struct option builtin_hook_options[] = {
+               OPT_END(),
+       };
+
+       argc = parse_options(argc, argv, NULL, builtin_hook_options,
+                            builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+       if (!argc)
+               goto usage;
+
+       if (!strcmp(argv[0], "run"))
+               return run(argc, argv, prefix);
+
+usage:
+       usage_with_options(builtin_hook_usage, builtin_hook_options);
+}
index 4b493408cc5d1253e84fd17dddec58340d38aa7e..093d0d26553aa88b0d36601d6b7e622a04a9e3bf 100644 (file)
@@ -35,6 +35,7 @@
 #include "repository.h"
 #include "commit-reach.h"
 #include "range-diff.h"
+#include "tmp-objdir.h"
 
 #define MAIL_DEFAULT_WRAP 72
 #define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100
@@ -422,6 +423,13 @@ static int cmd_log_walk(struct rev_info *rev)
        int saved_nrl = 0;
        int saved_dcctc = 0;
 
+       if (rev->remerge_diff) {
+               rev->remerge_objdir = tmp_objdir_create("remerge-diff");
+               if (!rev->remerge_objdir)
+                       die(_("unable to create temporary object directory"));
+               tmp_objdir_replace_primary_odb(rev->remerge_objdir, 1);
+       }
+
        if (rev->early_output)
                setup_early_output();
 
@@ -464,6 +472,11 @@ static int cmd_log_walk(struct rev_info *rev)
        rev->diffopt.no_free = 0;
        diff_free(&rev->diffopt);
 
+       if (rev->remerge_diff) {
+               tmp_objdir_destroy(rev->remerge_objdir);
+               rev->remerge_objdir = NULL;
+       }
+
        if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF &&
            rev->diffopt.flags.check_failed) {
                return 02;
@@ -1958,6 +1971,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                die(_("--name-status does not make sense"));
        if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF)
                die(_("--check does not make sense"));
+       if (rev.remerge_diff)
+               die(_("--remerge-diff does not make sense"));
 
        if (!use_patch_format &&
                (!rev.diffopt.output_format ||
index 74e53cf20a776e23efaa36702dffec408e4d29fa..133831d42fdeb676d946350cfb7d8af93a966141 100644 (file)
@@ -490,7 +490,7 @@ static void finish(struct commit *head_commit,
        }
 
        /* Run a post-merge hook */
-       run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL);
+       run_hooks_l("post-merge", squash ? "1" : "0", NULL);
 
        apply_autostash(git_path_merge_autostash(the_repository));
        strbuf_release(&reflog_message);
@@ -1273,7 +1273,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
        struct commit_list *common = NULL;
        const char *best_strategy = NULL, *wt_strategy = NULL;
-       struct commit_list *remoteheads, *p;
+       struct commit_list *remoteheads = NULL, *p;
        void *branch_to_free;
        int orig_argc = argc;
 
@@ -1752,6 +1752,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                ret = suggest_conflicts();
 
 done:
+       if (!automerge_was_ok) {
+               free_commit_list(common);
+               free_commit_list(remoteheads);
+       }
        strbuf_release(&buf);
        free(branch_to_free);
        return ret;
index 27f60153a6c732c9a47ed47eb79f4e736f714841..138e3c30a2b997adaf96b1a80d3cee741b8de982 100644 (file)
@@ -527,7 +527,7 @@ static void name_rev_line(char *p, struct name_ref_data *data)
 int cmd_name_rev(int argc, const char **argv, const char *prefix)
 {
        struct object_array revs = OBJECT_ARRAY_INIT;
-       int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;
+       int all = 0, annotate_stdin = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;
        struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP };
        struct option opts[] = {
                OPT_BOOL(0, "name-only", &data.name_only, N_("print only ref-based names (no object names)")),
@@ -538,7 +538,8 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
                                   N_("ignore refs matching <pattern>")),
                OPT_GROUP(""),
                OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")),
-               OPT_BOOL(0, "stdin", &transform_stdin, N_("read from stdin")),
+               OPT_BOOL(0, "stdin", &transform_stdin, N_("deprecated: use annotate-stdin instead")),
+               OPT_BOOL(0, "annotate-stdin", &annotate_stdin, N_("annotate text from stdin")),
                OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")),
                OPT_BOOL(0, "always",     &always,
                           N_("show abbreviated commit object as fallback")),
@@ -554,11 +555,19 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
        init_commit_rev_name(&rev_names);
        git_config(git_default_config, NULL);
        argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0);
-       if (all + transform_stdin + !!argc > 1) {
+
+       if (transform_stdin) {
+               warning("--stdin is deprecated. Please use --annotate-stdin instead, "
+                                       "which is functionally equivalent.\n"
+                                       "This option will be removed in a future release.");
+               annotate_stdin = 1;
+       }
+
+       if (all + annotate_stdin + !!argc > 1) {
                error("Specify either a list, or --all, not both!");
                usage_with_options(name_rev_usage, opts);
        }
-       if (all || transform_stdin)
+       if (all || annotate_stdin)
                cutoff = 0;
 
        for (; argc; argc--, argv++) {
@@ -613,15 +622,14 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
        for_each_ref(name_ref, &data);
        name_tips();
 
-       if (transform_stdin) {
-               char buffer[2048];
+       if (annotate_stdin) {
+               struct strbuf sb = STRBUF_INIT;
 
-               while (!feof(stdin)) {
-                       char *p = fgets(buffer, sizeof(buffer), stdin);
-                       if (!p)
-                               break;
-                       name_rev_line(p, &data);
+               while (strbuf_getline(&sb, stdin) != EOF) {
+                       strbuf_addch(&sb, '\n');
+                       name_rev_line(sb.buf, &data);
                }
+               strbuf_release(&sb);
        } else if (all) {
                int i, max;
 
index 822ffff51fbd3aa23abc166ab016fb00933f8e9a..881fcf32732caf810789c254d31eb05058ca0187 100644 (file)
@@ -32,8 +32,12 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after)
        n = strspn(q, digits);
        if (q[n] == ',') {
                q += n + 1;
+               *p_before = atoi(q);
                n = strspn(q, digits);
+       } else {
+               *p_before = 1;
        }
+
        if (n == 0 || q[n] != ' ' || q[n+1] != '+')
                return 0;
 
@@ -41,13 +45,14 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after)
        n = strspn(r, digits);
        if (r[n] == ',') {
                r += n + 1;
+               *p_after = atoi(r);
                n = strspn(r, digits);
+       } else {
+               *p_after = 1;
        }
        if (n == 0)
                return 0;
 
-       *p_before = atoi(q);
-       *p_after = atoi(r);
        return 1;
 }
 
index 100cbf9fb85b59fee6a8f9f90ced02c9abdaac6e..8f37880a48ca85d64dbdfa15d9c5397743d79ab2 100644 (file)
@@ -1038,14 +1038,13 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
                oidclr(&orig_head);
 
        if (opt_rebase) {
-               int autostash = config_autostash;
-               if (opt_autostash != -1)
-                       autostash = opt_autostash;
+               if (opt_autostash == -1)
+                       opt_autostash = config_autostash;
 
                if (is_null_oid(&orig_head) && !is_cache_unborn())
                        die(_("Updating an unborn branch with changes added to the index."));
 
-               if (!autostash)
+               if (!opt_autostash)
                        require_clean_work_tree(the_repository,
                                N_("pull with rebase"),
                                _("please commit or stash them."), 1, 0);
index 36490d06c8ac6ad4c10596b7e2a4556ec7aaa8c8..2e6d8fa34ee9861187135653ba2cb9db3f03947b 100644 (file)
@@ -28,6 +28,7 @@
 #include "sequencer.h"
 #include "rebase-interactive.h"
 #include "reset.h"
+#include "hook.h"
 
 #define DEFAULT_REFLOG_ACTION "rebase"
 
@@ -1712,7 +1713,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
        /* If a hook exists, give it a chance to interrupt*/
        if (!ok_to_skip_pre_rebase &&
-           run_hook_le(NULL, "pre-rebase", options.upstream_arg,
+           run_hooks_l("pre-rebase", options.upstream_arg,
                        argc ? argv[0] : NULL, NULL))
                die(_("The pre-rebase hook refused to rebase."));
 
index 9f4a0b816cf9b6acd077a10a728a83e4048b9b5e..c427ca09aafa69810fa7213cebb6375bb6a21b92 100644 (file)
@@ -581,32 +581,19 @@ static char *prepare_push_cert_nonce(const char *path, timestamp_t stamp)
        return strbuf_detach(&buf, NULL);
 }
 
-/*
- * NEEDSWORK: reuse find_commit_header() from jk/commit-author-parsing
- * after dropping "_commit" from its name and possibly moving it out
- * of commit.c
- */
 static char *find_header(const char *msg, size_t len, const char *key,
                         const char **next_line)
 {
-       int key_len = strlen(key);
-       const char *line = msg;
-
-       while (line && line < msg + len) {
-               const char *eol = strchrnul(line, '\n');
-
-               if ((msg + len <= eol) || line == eol)
-                       return NULL;
-               if (line + key_len < eol &&
-                   !memcmp(line, key, key_len) && line[key_len] == ' ') {
-                       int offset = key_len + 1;
-                       if (next_line)
-                               *next_line = *eol ? eol + 1 : eol;
-                       return xmemdupz(line + offset, (eol - line) - offset);
-               }
-               line = *eol ? eol + 1 : NULL;
-       }
-       return NULL;
+       size_t out_len;
+       const char *val = find_header_mem(msg, len, key, &out_len);
+
+       if (!val)
+               return NULL;
+
+       if (next_line)
+               *next_line = val + out_len + 1;
+
+       return xmemdupz(val, out_len);
 }
 
 /*
@@ -1424,9 +1411,12 @@ static const char *push_to_checkout(unsigned char *hash,
                                    struct strvec *env,
                                    const char *work_tree)
 {
+       struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
        strvec_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree));
-       if (run_hook_le(env->v, push_to_checkout_hook,
-                       hash_to_hex(hash), NULL))
+       strvec_pushv(&opt.env, env->v);
+       strvec_push(&opt.args, hash_to_hex(hash));
+       if (run_hooks_opt(push_to_checkout_hook, &opt))
                return "push-to-checkout hook declined";
        else
                return NULL;
index a4b1dd27e13c93c670489f11c7c20b980952ba2a..85b838720c364ae54c5ebf410a58921e6f8fd73e 100644 (file)
 #include "reachable.h"
 #include "worktree.h"
 
-/* NEEDSWORK: switch to using parse_options */
-static const char reflog_expire_usage[] =
-N_("git reflog expire [--expire=<time>] "
-   "[--expire-unreachable=<time>] "
-   "[--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] "
-   "[--verbose] [--all] <refs>...");
-static const char reflog_delete_usage[] =
-N_("git reflog delete [--rewrite] [--updateref] "
-   "[--dry-run | -n] [--verbose] <refs>...");
 static const char reflog_exists_usage[] =
 N_("git reflog exists <ref>");
 
@@ -29,6 +20,7 @@ static timestamp_t default_reflog_expire_unreachable;
 
 struct cmd_reflog_expire_cb {
        int stalefix;
+       int explicit_expiry;
        timestamp_t expire_total;
        timestamp_t expire_unreachable;
        int recno;
@@ -520,18 +512,18 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)
        return 0;
 }
 
-static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, const char *ref)
+static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, const char *ref)
 {
        struct reflog_expire_cfg *ent;
 
-       if (slot == (EXPIRE_TOTAL|EXPIRE_UNREACH))
+       if (cb->explicit_expiry == (EXPIRE_TOTAL|EXPIRE_UNREACH))
                return; /* both given explicitly -- nothing to tweak */
 
        for (ent = reflog_expire_cfg; ent; ent = ent->next) {
                if (!wildmatch(ent->pattern, ref, 0)) {
-                       if (!(slot & EXPIRE_TOTAL))
+                       if (!(cb->explicit_expiry & EXPIRE_TOTAL))
                                cb->expire_total = ent->expire_total;
-                       if (!(slot & EXPIRE_UNREACH))
+                       if (!(cb->explicit_expiry & EXPIRE_UNREACH))
                                cb->expire_unreachable = ent->expire_unreachable;
                        return;
                }
@@ -541,29 +533,89 @@ static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, c
         * If unconfigured, make stash never expire
         */
        if (!strcmp(ref, "refs/stash")) {
-               if (!(slot & EXPIRE_TOTAL))
+               if (!(cb->explicit_expiry & EXPIRE_TOTAL))
                        cb->expire_total = 0;
-               if (!(slot & EXPIRE_UNREACH))
+               if (!(cb->explicit_expiry & EXPIRE_UNREACH))
                        cb->expire_unreachable = 0;
                return;
        }
 
        /* Nothing matched -- use the default value */
-       if (!(slot & EXPIRE_TOTAL))
+       if (!(cb->explicit_expiry & EXPIRE_TOTAL))
                cb->expire_total = default_reflog_expire;
-       if (!(slot & EXPIRE_UNREACH))
+       if (!(cb->explicit_expiry & EXPIRE_UNREACH))
                cb->expire_unreachable = default_reflog_expire_unreachable;
 }
 
+static const char * reflog_expire_usage[] = {
+       N_("git reflog expire [--expire=<time>] "
+          "[--expire-unreachable=<time>] "
+          "[--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] "
+          "[--verbose] [--all] <refs>..."),
+       NULL
+};
+
+static int expire_unreachable_callback(const struct option *opt,
+                                const char *arg,
+                                int unset)
+{
+       struct cmd_reflog_expire_cb *cmd = opt->value;
+
+       if (parse_expiry_date(arg, &cmd->expire_unreachable))
+               die(_("invalid timestamp '%s' given to '--%s'"),
+                   arg, opt->long_name);
+
+       cmd->explicit_expiry |= EXPIRE_UNREACH;
+       return 0;
+}
+
+static int expire_total_callback(const struct option *opt,
+                                const char *arg,
+                                int unset)
+{
+       struct cmd_reflog_expire_cb *cmd = opt->value;
+
+       if (parse_expiry_date(arg, &cmd->expire_total))
+               die(_("invalid timestamp '%s' given to '--%s'"),
+                   arg, opt->long_name);
+
+       cmd->explicit_expiry |= EXPIRE_TOTAL;
+       return 0;
+}
+
 static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
 {
        struct cmd_reflog_expire_cb cmd = { 0 };
        timestamp_t now = time(NULL);
        int i, status, do_all, all_worktrees = 1;
-       int explicit_expiry = 0;
        unsigned int flags = 0;
        int verbose = 0;
        reflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;
+       const struct option options[] = {
+               OPT_BIT(0, "dry-run", &flags, N_("do not actually prune any entries"),
+                       EXPIRE_REFLOGS_DRY_RUN),
+               OPT_BIT(0, "rewrite", &flags,
+                       N_("rewrite the old SHA1 with the new SHA1 of the entry that now precedes it"),
+                       EXPIRE_REFLOGS_REWRITE),
+               OPT_BIT(0, "updateref", &flags,
+                       N_("update the reference to the value of the top reflog entry"),
+                       EXPIRE_REFLOGS_UPDATE_REF),
+               OPT_BOOL(0, "verbose", &verbose, N_("print extra information on screen.")),
+               OPT_CALLBACK_F(0, "expire", &cmd, N_("timestamp"),
+                              N_("prune entries older than the specified time"),
+                              PARSE_OPT_NONEG,
+                              expire_total_callback),
+               OPT_CALLBACK_F(0, "expire-unreachable", &cmd, N_("timestamp"),
+                              N_("prune entries older than <time> that are not reachable from the current tip of the branch"),
+                              PARSE_OPT_NONEG,
+                              expire_unreachable_callback),
+               OPT_BOOL(0, "stale-fix", &cmd.stalefix,
+                        N_("prune any reflog entries that point to broken commits")),
+               OPT_BOOL(0, "all", &do_all, N_("process the reflogs of all references")),
+               OPT_BOOL(1, "single-worktree", &all_worktrees,
+                        N_("limits processing to reflogs from the current worktree only.")),
+               OPT_END()
+       };
 
        default_reflog_expire_unreachable = now - 30 * 24 * 3600;
        default_reflog_expire = now - 90 * 24 * 3600;
@@ -572,45 +624,11 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
        save_commit_buffer = 0;
        do_all = status = 0;
 
+       cmd.explicit_expiry = 0;
        cmd.expire_total = default_reflog_expire;
        cmd.expire_unreachable = default_reflog_expire_unreachable;
 
-       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 (skip_prefix(arg, "--expire=", &arg)) {
-                       if (parse_expiry_date(arg, &cmd.expire_total))
-                               die(_("'%s' is not a valid timestamp"), arg);
-                       explicit_expiry |= EXPIRE_TOTAL;
-               }
-               else if (skip_prefix(arg, "--expire-unreachable=", &arg)) {
-                       if (parse_expiry_date(arg, &cmd.expire_unreachable))
-                               die(_("'%s' is not a valid timestamp"), arg);
-                       explicit_expiry |= EXPIRE_UNREACH;
-               }
-               else if (!strcmp(arg, "--stale-fix"))
-                       cmd.stalefix = 1;
-               else if (!strcmp(arg, "--rewrite"))
-                       flags |= EXPIRE_REFLOGS_REWRITE;
-               else if (!strcmp(arg, "--updateref"))
-                       flags |= EXPIRE_REFLOGS_UPDATE_REF;
-               else if (!strcmp(arg, "--all"))
-                       do_all = 1;
-               else if (!strcmp(arg, "--single-worktree"))
-                       all_worktrees = 0;
-               else if (!strcmp(arg, "--verbose"))
-                       verbose = 1;
-               else if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               else if (arg[0] == '-')
-                       usage(_(reflog_expire_usage));
-               else
-                       break;
-       }
+       argc = parse_options(argc, argv, prefix, options, reflog_expire_usage, 0);
 
        if (verbose)
                should_prune_fn = should_expire_reflog_ent_verbose;
@@ -657,7 +675,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
                                .dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),
                        };
 
-                       set_reflog_expiry_param(&cb.cmd, explicit_expiry, item->string);
+                       set_reflog_expiry_param(&cb.cmd,  item->string);
                        status |= reflog_expire(item->string, flags,
                                                reflog_expiry_prepare,
                                                should_prune_fn,
@@ -667,7 +685,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
                string_list_clear(&collected.reflogs, 0);
        }
 
-       for (; i < argc; i++) {
+       for (i = 0; i < argc; i++) {
                char *ref;
                struct expire_reflog_policy_cb cb = { .cmd = cmd };
 
@@ -675,7 +693,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
                        status |= error(_("%s points nowhere!"), argv[i]);
                        continue;
                }
-               set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref);
+               set_reflog_expiry_param(&cb.cmd, ref);
                status |= reflog_expire(ref, flags,
                                        reflog_expiry_prepare,
                                        should_prune_fn,
@@ -696,6 +714,12 @@ static int count_reflog_ent(struct object_id *ooid, struct object_id *noid,
        return 0;
 }
 
+static const char * reflog_delete_usage[] = {
+       N_("git reflog delete [--rewrite] [--updateref] "
+          "[--dry-run | -n] [--verbose] <refs>..."),
+       NULL
+};
+
 static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
 {
        struct cmd_reflog_expire_cb cmd = { 0 };
@@ -703,34 +727,28 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
        unsigned int flags = 0;
        int verbose = 0;
        reflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;
-
-       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 (!strcmp(arg, "--rewrite"))
-                       flags |= EXPIRE_REFLOGS_REWRITE;
-               else if (!strcmp(arg, "--updateref"))
-                       flags |= EXPIRE_REFLOGS_UPDATE_REF;
-               else if (!strcmp(arg, "--verbose"))
-                       verbose = 1;
-               else if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               else if (arg[0] == '-')
-                       usage(_(reflog_delete_usage));
-               else
-                       break;
-       }
+       const struct option options[] = {
+               OPT_BIT(0, "dry-run", &flags, N_("do not actually prune any entries"),
+                       EXPIRE_REFLOGS_DRY_RUN),
+               OPT_BIT(0, "rewrite", &flags,
+                       N_("rewrite the old SHA1 with the new SHA1 of the entry that now precedes it"),
+                       EXPIRE_REFLOGS_REWRITE),
+               OPT_BIT(0, "updateref", &flags,
+                       N_("update the reference to the value of the top reflog entry"),
+                       EXPIRE_REFLOGS_UPDATE_REF),
+               OPT_BOOL(0, "verbose", &verbose, N_("print extra information on screen.")),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, prefix, options, reflog_delete_usage, 0);
 
        if (verbose)
                should_prune_fn = should_expire_reflog_ent_verbose;
 
-       if (argc - i < 1)
+       if (argc < 1)
                return error(_("no reflog specified to delete"));
 
-       for ( ; i < argc; i++) {
+       for (i = 0; i < argc; i++) {
                const char *spec = strstr(argv[i], "@{");
                char *ep, *ref;
                int recno;
index b97745ee94e5a30a25a477f432a275d6ae953ee2..75b8d86481f2f32f02d1a84ec66b43a709e0cb49 100644 (file)
@@ -204,10 +204,16 @@ static int pathspec_needs_expanded_index(const struct pathspec *pathspec)
                        /*
                         * Special case: if the pattern is a path inside the cone
                         * followed by only wildcards, the pattern cannot match
-                        * partial sparse directories, so we don't expand the index.
+                        * partial sparse directories, so we know we don't need to
+                        * expand the index.
+                        *
+                        * Examples:
+                        * - in-cone/foo***: doesn't need expanded index
+                        * - not-in-cone/bar*: may need expanded index
+                        * - **.c: may need expanded index
                         */
-                       if (path_in_cone_mode_sparse_checkout(item.original, &the_index) &&
-                           strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len)
+                       if (strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len &&
+                           path_in_cone_mode_sparse_checkout(item.original, &the_index))
                                continue;
 
                        for (pos = 0; pos < active_nr; pos++) {
index 679c10703684044aa02555227b5cdcdf92591d9b..a311483a7d268d52bdfe10a7628a64ed5e6025d2 100644 (file)
@@ -185,6 +185,8 @@ static void clean_tracked_sparse_directories(struct repository *r)
                                item->string);
                }
 
+               strvec_clear(&s);
+               clear_pathspec(&p);
                dir_clear(&dir);
        }
 
@@ -471,6 +473,9 @@ static int sparse_checkout_init(int argc, const char **argv)
                FILE *fp;
 
                /* assume we are in a fresh repo, but update the sparse-checkout file */
+               if (safe_create_leading_directories(sparse_filename))
+                       die(_("unable to create leading directories of %s"),
+                           sparse_filename);
                fp = xfopen(sparse_filename, "w");
                if (!fp)
                        die(_("failed to open '%s'"), sparse_filename);
index 86cd0b456e7752d7ffdb578ce892ca17c4e9c6a9..5897febfbec202d23fb0da2dfdc4c9cc1867127b 100644 (file)
@@ -788,7 +788,6 @@ static int list_stash(int argc, const char **argv, const char *prefix)
 static int show_stat = 1;
 static int show_patch;
 static int show_include_untracked;
-static int use_legacy_stash;
 
 static int git_stash_config(const char *var, const char *value, void *cb)
 {
@@ -804,10 +803,6 @@ static int git_stash_config(const char *var, const char *value, void *cb)
                show_include_untracked = git_config_bool(var, value);
                return 0;
        }
-       if (!strcmp(var, "stash.usebuiltin")) {
-               use_legacy_stash = !git_config_bool(var, value);
-               return 0;
-       }
        return git_diff_basic_config(var, value, cb);
 }
 
@@ -1782,11 +1777,6 @@ int cmd_stash(int argc, const char **argv, const char *prefix)
 
        git_config(git_stash_config, NULL);
 
-       if (use_legacy_stash ||
-           !git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1))
-               warning(_("the stash.useBuiltin support has been removed!\n"
-                         "See its entry in 'git help config' for details."));
-
        argc = parse_options(argc, argv, prefix, options, git_stash_usage,
                             PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
 
@@ -1819,8 +1809,8 @@ int cmd_stash(int argc, const char **argv, const char *prefix)
        else if (!strcmp(argv[0], "save"))
                return !!save_stash(argc, argv, prefix);
        else if (*argv[0] != '-')
-               usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
-                             git_stash_usage, options);
+               usage_msg_optf(_("unknown subcommand: %s"),
+                              git_stash_usage, options, argv[0]);
 
        /* Assume 'stash push' */
        strvec_push(&args, "push");
index 187203e8bb53cbeaf80d2f4b89c04134a2a5c95f..aafe7eeac2a9391d29cbdbbe380b374adce2caf5 100644 (file)
@@ -606,7 +606,7 @@ static struct cache_entry *read_one_ent(const char *which,
                        error("%s: not in %s branch.", path, which);
                return NULL;
        }
-       if (mode == S_IFDIR) {
+       if (!the_index.sparse_index && mode == S_IFDIR) {
                if (which)
                        error("%s: not a blob in %s branch.", path, which);
                return NULL;
@@ -743,8 +743,6 @@ static int do_reupdate(int ac, const char **av,
                 */
                has_head = 0;
  redo:
-       /* TODO: audit for interaction with sparse-index. */
-       ensure_full_index(&the_index);
        for (pos = 0; pos < active_nr; pos++) {
                const struct cache_entry *ce = active_cache[pos];
                struct cache_entry *old = NULL;
@@ -761,6 +759,16 @@ static int do_reupdate(int ac, const char **av,
                        discard_cache_entry(old);
                        continue; /* unchanged */
                }
+
+               /* At this point, we know the contents of the sparse directory are
+                * modified with respect to HEAD, so we expand the index and restart
+                * to process each path individually
+                */
+               if (S_ISSPARSEDIR(ce->ce_mode)) {
+                       ensure_full_index(&the_index);
+                       goto redo;
+               }
+
                /* Be careful.  The working tree may not have the
                 * path anymore, in which case, under 'allow_remove',
                 * or worse yet 'allow_replace', active_nr may decrease.
@@ -787,6 +795,17 @@ static int refresh(struct refresh_params *o, unsigned int flag)
        setup_work_tree();
        read_cache();
        *o->has_errors |= refresh_cache(o->flags | flag);
+       if (has_racy_timestamp(&the_index)) {
+               /*
+                * Even if nothing else has changed, updating the file
+                * increases the chance that racy timestamps become
+                * non-racy, helping future run-time performance.
+                * We do that even in case of "errors" returned by
+                * refresh_cache() as these are no actual errors.
+                * cmd_status() does the same.
+                */
+               active_cache_changed |= SOMETHING_CHANGED;
+       }
        return 0;
 }
 
@@ -1077,6 +1096,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 
        git_config(git_default_config, NULL);
 
+       prepare_repo_settings(r);
+       the_repository->settings.command_requires_full_index = 0;
+
        /* we will diagnose later if it turns out that we need to update it */
        newfd = hold_locked_index(&lock_file, 0);
        if (newfd < 0)
index 2838254f7f2e10f780ea8608d591c6fd8e8f5521..0d0809276fe1b566140ce2f75a704c06cd0aa690 100644 (file)
@@ -382,21 +382,17 @@ done:
         * is_junk is cleared, but do return appropriate code when hook fails.
         */
        if (!ret && opts->checkout) {
-               const char *hook = find_hook("post-checkout");
-               if (hook) {
-                       const char *env[] = { "GIT_DIR", "GIT_WORK_TREE", NULL };
-                       struct child_process cp = CHILD_PROCESS_INIT;
-                       cp.no_stdin = 1;
-                       cp.stdout_to_stderr = 1;
-                       cp.dir = path;
-                       strvec_pushv(&cp.env_array, env);
-                       cp.trace2_hook_name = "post-checkout";
-                       strvec_pushl(&cp.args, absolute_path(hook),
-                                    oid_to_hex(null_oid()),
-                                    oid_to_hex(&commit->object.oid),
-                                    "1", NULL);
-                       ret = run_command(&cp);
-               }
+               struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
+               strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL);
+               strvec_pushl(&opt.args,
+                            oid_to_hex(null_oid()),
+                            oid_to_hex(&commit->object.oid),
+                            "1",
+                            NULL);
+               opt.dir = path;
+
+               ret = run_hooks_opt("post-checkout", &opt);
        }
 
        strvec_clear(&child_env);
diff --git a/cache.h b/cache.h
index 281f00ab1b161dc71d0bcdae91222e9429ba0342..825ec17198ce45a02c2c47fbd25cc21e542f9cac 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -18,7 +18,6 @@
 #include "repository.h"
 #include "mem-pool.h"
 
-#include <zlib.h>
 typedef struct git_zstream {
        z_stream z;
        unsigned long avail_in;
@@ -889,6 +888,7 @@ void *read_blob_data_from_index(struct index_state *, const char *, unsigned lon
 #define CE_MATCH_IGNORE_FSMONITOR 0X20
 int is_racy_timestamp(const struct index_state *istate,
                      const struct cache_entry *ce);
+int has_racy_timestamp(struct index_state *istate);
 int ie_match_stat(struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
 int ie_modified(struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
 
@@ -1375,6 +1375,7 @@ struct object_context {
 #define GET_OID_FOLLOW_SYMLINKS 0100
 #define GET_OID_RECORD_PATH     0200
 #define GET_OID_ONLY_TO_DIE    04000
+#define GET_OID_REQUIRE_PATH  010000
 
 #define GET_OID_DISAMBIGUATORS \
        (GET_OID_COMMIT | GET_OID_COMMITTISH | \
index 9d28ab50fb4462a1b064e8c89cd5f13518fd86cd..cbc2f8f1caa6c0ddf69824b03e9394aecbcac250 100755 (executable)
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -197,7 +197,6 @@ esac
 case "$jobname" in
 linux32)
        CC=gcc
-       MAKEFLAGS="$MAKEFLAGS NO_UNCOMPRESS2=1"
        ;;
 linux-musl)
        CC=gcc
index 675c28f0bd038e1b017649a96851056ef6a0fd83..9bd6f3c48f4d1853e4cc5df7a9685696f63d72ce 100644 (file)
@@ -103,6 +103,7 @@ git-grep                                mainporcelain           info
 git-gui                                 mainporcelain
 git-hash-object                         plumbingmanipulators
 git-help                                ancillaryinterrogators          complete
+git-hook                                purehelpers
 git-http-backend                        synchingrepositories
 git-http-fetch                          synchelpers
 git-http-push                           synchelpers
index a348f085b2b853d2f14d6848f3ff57edf85601b8..d400f5dfa2b1f9016f90c5aee9665b1f08316048 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -21,6 +21,7 @@
 #include "commit-reach.h"
 #include "run-command.h"
 #include "shallow.h"
+#include "hook.h"
 
 static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
 
@@ -1631,12 +1632,20 @@ struct commit_list **commit_list_append(struct commit *commit,
        return &new_commit->next;
 }
 
-const char *find_commit_header(const char *msg, const char *key, size_t *out_len)
+const char *find_header_mem(const char *msg, size_t len,
+                       const char *key, size_t *out_len)
 {
        int key_len = strlen(key);
        const char *line = msg;
 
-       while (line) {
+       /*
+        * NEEDSWORK: It's possible for strchrnul() to scan beyond the range
+        * given by len. However, current callers are safe because they compute
+        * len by scanning a NUL-terminated block of memory starting at msg.
+        * Nonetheless, it would be better to ensure the function does not look
+        * at msg beyond the len provided by the caller.
+        */
+       while (line && line < msg + len) {
                const char *eol = strchrnul(line, '\n');
 
                if (line == eol)
@@ -1653,6 +1662,10 @@ const char *find_commit_header(const char *msg, const char *key, size_t *out_len
        return NULL;
 }
 
+const char *find_commit_header(const char *msg, const char *key, size_t *out_len)
+{
+       return find_header_mem(msg, strlen(msg), key, out_len);
+}
 /*
  * Inspect the given string and determine the true "end" of the log message, in
  * order to find where to put a new Signed-off-by trailer.  Ignored are
@@ -1702,22 +1715,22 @@ size_t ignore_non_trailer(const char *buf, size_t len)
 int run_commit_hook(int editor_is_used, const char *index_file,
                    const char *name, ...)
 {
-       struct strvec hook_env = STRVEC_INIT;
+       struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
        va_list args;
-       int ret;
+       const char *arg;
 
-       strvec_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file);
+       strvec_pushf(&opt.env, "GIT_INDEX_FILE=%s", index_file);
 
        /*
         * Let the hook know that no editor will be launched.
         */
        if (!editor_is_used)
-               strvec_push(&hook_env, "GIT_EDITOR=:");
+               strvec_push(&opt.env, "GIT_EDITOR=:");
 
        va_start(args, name);
-       ret = run_hook_ve(hook_env.v, name, args);
+       while ((arg = va_arg(args, const char *)))
+               strvec_push(&opt.args, arg);
        va_end(args);
-       strvec_clear(&hook_env);
 
-       return ret;
+       return run_hooks_opt(name, &opt);
 }
index 3ea32766bcb00ada1c55f948ab6cc503b04c72a7..38cc5426615fdf1efd5b407e7507991db45100c2 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -290,12 +290,17 @@ void free_commit_extra_headers(struct commit_extra_header *extra);
 
 /*
  * Search the commit object contents given by "msg" for the header "key".
+ * Reads up to "len" bytes of "msg".
  * Returns a pointer to the start of the header contents, or NULL. The length
  * of the header, up to the first newline, is returned via out_len.
  *
  * Note that some headers (like mergetag) may be multi-line. It is the caller's
  * responsibility to parse further in this case!
  */
+const char *find_header_mem(const char *msg, size_t len,
+                       const char *key,
+                       size_t *out_len);
+
 const char *find_commit_header(const char *msg, const char *key,
                               size_t *out_len);
 
index 52d1f0a73dd8292d8e02d7b9135d6f8c1606e324..0f7ff30f5f37b39d8f112f18b207db873f4b19fb 100644 (file)
@@ -49,21 +49,15 @@ int git_qsort_s(void *b, size_t n, size_t s,
                int (*cmp)(const void *, const void *, void *), void *ctx)
 {
        const size_t size = st_mult(n, s);
-       char buf[1024];
+       char *tmp;
 
        if (!n)
                return 0;
        if (!b || !cmp)
                return -1;
 
-       if (size < sizeof(buf)) {
-               /* The temporary array fits on the small on-stack buffer. */
-               msort_with_tmp(b, n, s, cmp, buf, ctx);
-       } else {
-               /* It's somewhat large, so malloc it.  */
-               char *tmp = xmalloc(size);
-               msort_with_tmp(b, n, s, cmp, tmp, ctx);
-               free(tmp);
-       }
+       tmp = xmalloc(size);
+       msort_with_tmp(b, n, s, cmp, tmp, ctx);
+       free(tmp);
        return 0;
 }
index 4fceecf14ce599b585801bcb1ebc0b4e2c22af06..936a80a5f007d4d6e764f6dc68d649ea1e72094e 100644 (file)
@@ -3,6 +3,12 @@
  */
 
 #undef NOGDI
+
+/*
+ * Including the appropriate header file for RtlGenRandom causes MSVC to see a
+ * redefinition of types in an incompatible way when including headers below.
+ */
+#undef HAVE_RTLGENRANDOM
 #include "../git-compat-util.h"
 #include <wingdi.h>
 #include <winreg.h>
index 722610b971804abf47130d07bbd6c0b084c5036f..77a1b08048463da25ba8d6b36031ccb7e8cce7b5 100644 (file)
@@ -1,3 +1,6 @@
+#include "git-compat-util.h"
+
+#if ZLIB_VERNUM < 0x1290
 /* taken from zlib's uncompr.c
 
    commit cacf7f1d4e3d44d871b605da3b647f07d718623f
 
 */
 
-#include "../reftable/system.h"
-#define z_const
-
 /*
  * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler
  * For conditions of distribution and use, see copyright notice in zlib.h
  */
 
-#include <zlib.h>
-
 /* clang-format off */
 
 /* ===========================================================================
@@ -93,3 +91,6 @@ int ZEXPORT uncompress2 (
           err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR :
           err;
 }
+#else
+static void *dummy_variable = &dummy_variable;
+#endif
index 2bffa8d4a01ba1f281d6e6fd95f35bf133cbd9c5..e0c03d154c916225d30fd87d7008c91582ac4409 100644 (file)
--- a/config.c
+++ b/config.c
@@ -120,6 +120,22 @@ static long config_buf_ftell(struct config_source *conf)
        return conf->u.buf.pos;
 }
 
+struct config_include_data {
+       int depth;
+       config_fn_t fn;
+       void *data;
+       const struct config_options *opts;
+       struct git_config_source *config_source;
+
+       /*
+        * All remote URLs discovered when reading all config files.
+        */
+       struct string_list *remote_urls;
+};
+#define CONFIG_INCLUDE_INIT { 0 }
+
+static int git_config_include(const char *var, const char *value, void *data);
+
 #define MAX_INCLUDE_DEPTH 10
 static const char include_depth_advice[] = N_(
 "exceeded maximum include depth (%d) while including\n"
@@ -294,9 +310,92 @@ static int include_by_branch(const char *cond, size_t cond_len)
        return ret;
 }
 
-static int include_condition_is_true(const struct config_options *opts,
+static int add_remote_url(const char *var, const char *value, void *data)
+{
+       struct string_list *remote_urls = data;
+       const char *remote_name;
+       size_t remote_name_len;
+       const char *key;
+
+       if (!parse_config_key(var, "remote", &remote_name, &remote_name_len,
+                             &key) &&
+           remote_name &&
+           !strcmp(key, "url"))
+               string_list_append(remote_urls, value);
+       return 0;
+}
+
+static void populate_remote_urls(struct config_include_data *inc)
+{
+       struct config_options opts;
+
+       struct config_source *store_cf = cf;
+       struct key_value_info *store_kvi = current_config_kvi;
+       enum config_scope store_scope = current_parsing_scope;
+
+       opts = *inc->opts;
+       opts.unconditional_remote_url = 1;
+
+       cf = NULL;
+       current_config_kvi = NULL;
+       current_parsing_scope = 0;
+
+       inc->remote_urls = xmalloc(sizeof(*inc->remote_urls));
+       string_list_init_dup(inc->remote_urls);
+       config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
+
+       cf = store_cf;
+       current_config_kvi = store_kvi;
+       current_parsing_scope = store_scope;
+}
+
+static int forbid_remote_url(const char *var, const char *value, void *data)
+{
+       const char *remote_name;
+       size_t remote_name_len;
+       const char *key;
+
+       if (!parse_config_key(var, "remote", &remote_name, &remote_name_len,
+                             &key) &&
+           remote_name &&
+           !strcmp(key, "url"))
+               die(_("remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url"));
+       return 0;
+}
+
+static int at_least_one_url_matches_glob(const char *glob, int glob_len,
+                                        struct string_list *remote_urls)
+{
+       struct strbuf pattern = STRBUF_INIT;
+       struct string_list_item *url_item;
+       int found = 0;
+
+       strbuf_add(&pattern, glob, glob_len);
+       for_each_string_list_item(url_item, remote_urls) {
+               if (!wildmatch(pattern.buf, url_item->string, WM_PATHNAME)) {
+                       found = 1;
+                       break;
+               }
+       }
+       strbuf_release(&pattern);
+       return found;
+}
+
+static int include_by_remote_url(struct config_include_data *inc,
+               const char *cond, size_t cond_len)
+{
+       if (inc->opts->unconditional_remote_url)
+               return 1;
+       if (!inc->remote_urls)
+               populate_remote_urls(inc);
+       return at_least_one_url_matches_glob(cond, cond_len,
+                                            inc->remote_urls);
+}
+
+static int include_condition_is_true(struct config_include_data *inc,
                                     const char *cond, size_t cond_len)
 {
+       const struct config_options *opts = inc->opts;
 
        if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
                return include_by_gitdir(opts, cond, cond_len, 0);
@@ -304,12 +403,15 @@ static int include_condition_is_true(const struct config_options *opts,
                return include_by_gitdir(opts, cond, cond_len, 1);
        else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
                return include_by_branch(cond, cond_len);
+       else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
+                                  &cond_len))
+               return include_by_remote_url(inc, cond, cond_len);
 
        /* unknown conditionals are always false */
        return 0;
 }
 
-int git_config_include(const char *var, const char *value, void *data)
+static int git_config_include(const char *var, const char *value, void *data)
 {
        struct config_include_data *inc = data;
        const char *cond, *key;
@@ -328,9 +430,15 @@ int git_config_include(const char *var, const char *value, void *data)
                ret = handle_path_include(value, inc);
 
        if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
-           (cond && include_condition_is_true(inc->opts, cond, cond_len)) &&
-           !strcmp(key, "path"))
+           cond && include_condition_is_true(inc, cond, cond_len) &&
+           !strcmp(key, "path")) {
+               config_fn_t old_fn = inc->fn;
+
+               if (inc->opts->unconditional_remote_url)
+                       inc->fn = forbid_remote_url;
                ret = handle_path_include(value, inc);
+               inc->fn = old_fn;
+       }
 
        return ret;
 }
@@ -1929,11 +2037,13 @@ int config_with_options(config_fn_t fn, void *data,
                        const struct config_options *opts)
 {
        struct config_include_data inc = CONFIG_INCLUDE_INIT;
+       int ret;
 
        if (opts->respect_includes) {
                inc.fn = fn;
                inc.data = data;
                inc.opts = opts;
+               inc.config_source = config_source;
                fn = git_config_include;
                data = &inc;
        }
@@ -1946,17 +2056,23 @@ int config_with_options(config_fn_t fn, void *data,
         * regular lookup sequence.
         */
        if (config_source && config_source->use_stdin) {
-               return git_config_from_stdin(fn, data);
+               ret = git_config_from_stdin(fn, data);
        } else if (config_source && config_source->file) {
-               return git_config_from_file(fn, config_source->file, data);
+               ret = git_config_from_file(fn, config_source->file, data);
        } else if (config_source && config_source->blob) {
                struct repository *repo = config_source->repo ?
                        config_source->repo : the_repository;
-               return git_config_from_blob_ref(fn, repo, config_source->blob,
+               ret = git_config_from_blob_ref(fn, repo, config_source->blob,
                                                data);
+       } else {
+               ret = do_git_config_sequence(opts, fn, data);
        }
 
-       return do_git_config_sequence(opts, fn, data);
+       if (inc.remote_urls) {
+               string_list_clear(inc.remote_urls, 0);
+               FREE_AND_NULL(inc.remote_urls);
+       }
+       return ret;
 }
 
 static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
index f119de01309ccf5ce5b0a6434d38fa70c472bab0..ab0106d2875b729057f76162b9f641aaa11dfc84 100644 (file)
--- a/config.h
+++ b/config.h
@@ -89,6 +89,15 @@ struct config_options {
        unsigned int ignore_worktree : 1;
        unsigned int ignore_cmdline : 1;
        unsigned int system_gently : 1;
+
+       /*
+        * For internal use. Include all includeif.hasremoteurl paths without
+        * checking if the repo has that remote URL, and when doing so, verify
+        * that files included in this way do not configure any remote URLs
+        * themselves.
+        */
+       unsigned int unconditional_remote_url : 1;
+
        const char *commondir;
        const char *git_dir;
        config_parser_event_fn_t event_fn;
@@ -126,6 +135,8 @@ int git_default_config(const char *, const char *, void *);
 /**
  * Read a specific file in git-config format.
  * This function takes the same callback and data parameters as `git_config`.
+ *
+ * Unlike git_config(), this function does not respect includes.
  */
 int git_config_from_file(config_fn_t fn, const char *, void *);
 
@@ -158,6 +169,8 @@ void read_very_early_config(config_fn_t cb, void *data);
  * will first feed the user-wide one to the callback, and then the
  * repo-specific one; by overwriting, the higher-priority repo-specific
  * value is left at the end).
+ *
+ * Unlike git_config_from_file(), this function respects includes.
  */
 void git_config(config_fn_t fn, void *);
 
@@ -338,39 +351,6 @@ const char *current_config_origin_type(void);
 const char *current_config_name(void);
 int current_config_line(void);
 
-/**
- * Include Directives
- * ------------------
- *
- * By default, the config parser does not respect include directives.
- * However, a caller can use the special `git_config_include` wrapper
- * callback to support them. To do so, you simply wrap your "real" callback
- * function and data pointer in a `struct config_include_data`, and pass
- * the wrapper to the regular config-reading functions. For example:
- *
- * -------------------------------------------
- * int read_file_with_include(const char *file, config_fn_t fn, void *data)
- * {
- * struct config_include_data inc = CONFIG_INCLUDE_INIT;
- * inc.fn = fn;
- * inc.data = data;
- * return git_config_from_file(git_config_include, file, &inc);
- * }
- * -------------------------------------------
- *
- * `git_config` respects includes automatically. The lower-level
- * `git_config_from_file` does not.
- *
- */
-struct config_include_data {
-       int depth;
-       config_fn_t fn;
-       void *data;
-       const struct config_options *opts;
-};
-#define CONFIG_INCLUDE_INIT { 0 }
-int git_config_include(const char *name, const char *value, void *data);
-
 /*
  * Match and parse a config key of the form:
  *
index c48db45106c8dc0e4138ccb8ecff016f3f7d0faa..4352ea39e9b9b7e2103560729202c63cb1fd90d4 100644 (file)
@@ -66,7 +66,6 @@ ifeq ($(uname_S),Linux)
        # centos7/rhel7 provides gcc 4.8.5 and zlib 1.2.7.
        ifneq ($(findstring .el7.,$(uname_R)),)
                BASIC_CFLAGS += -std=c99
-               NO_UNCOMPRESS2 = YesPlease
        endif
 endif
 ifeq ($(uname_S),GNU/kFreeBSD)
@@ -146,6 +145,7 @@ ifeq ($(uname_S),Darwin)
        HAVE_BSD_SYSCTL = YesPlease
        FREAD_READS_DIRECTORIES = UnfortunatelyYes
        HAVE_NS_GET_EXECUTABLE_PATH = YesPlease
+       CSPRNG_METHOD = arc4random
 
        # Workaround for `gettext` being keg-only and not even being linked via
        # `brew link --force gettext`, should be obsolete as of
@@ -261,15 +261,12 @@ ifeq ($(uname_S),FreeBSD)
        HAVE_PATHS_H = YesPlease
        HAVE_BSD_SYSCTL = YesPlease
        HAVE_BSD_KERN_PROC_SYSCTL = YesPlease
+       CSPRNG_METHOD = arc4random
        PAGER_ENV = LESS=FRX LV=-c MORE=FRX
        FREAD_READS_DIRECTORIES = UnfortunatelyYes
        FILENO_IS_A_MACRO = UnfortunatelyYes
 endif
 ifeq ($(uname_S),OpenBSD)
-       # Versions < 7.0 need compatibility layer
-       ifeq ($(shell expr "$(uname_R)" : "[1-6]\."),2)
-               NO_UNCOMPRESS2 = UnfortunatelyYes
-       endif
        NO_STRCASESTR = YesPlease
        NO_MEMMEM = YesPlease
        USE_ST_TIMESPEC = YesPlease
@@ -279,6 +276,7 @@ ifeq ($(uname_S),OpenBSD)
        HAVE_PATHS_H = YesPlease
        HAVE_BSD_SYSCTL = YesPlease
        HAVE_BSD_KERN_PROC_SYSCTL = YesPlease
+       CSPRNG_METHOD = arc4random
        PROCFS_EXECUTABLE_PATH = /proc/curproc/file
        FREAD_READS_DIRECTORIES = UnfortunatelyYes
        FILENO_IS_A_MACRO = UnfortunatelyYes
@@ -290,6 +288,7 @@ ifeq ($(uname_S),MirBSD)
        NEEDS_LIBICONV = YesPlease
        HAVE_PATHS_H = YesPlease
        HAVE_BSD_SYSCTL = YesPlease
+       CSPRNG_METHOD = arc4random
 endif
 ifeq ($(uname_S),NetBSD)
        ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2)
@@ -301,6 +300,7 @@ ifeq ($(uname_S),NetBSD)
        HAVE_PATHS_H = YesPlease
        HAVE_BSD_SYSCTL = YesPlease
        HAVE_BSD_KERN_PROC_SYSCTL = YesPlease
+       CSPRNG_METHOD = arc4random
        PROCFS_EXECUTABLE_PATH = /proc/curproc/exe
 endif
 ifeq ($(uname_S),AIX)
@@ -430,6 +430,7 @@ ifeq ($(uname_S),Windows)
        NO_STRTOUMAX = YesPlease
        NO_MKDTEMP = YesPlease
        NO_INTTYPES_H = YesPlease
+       CSPRNG_METHOD = rtlgenrandom
        # VS2015 with UCRT claims that snprintf and friends are C99 compliant,
        # so we don't need this:
        #
@@ -525,7 +526,6 @@ ifeq ($(uname_S),Interix)
        endif
 endif
 ifeq ($(uname_S),Minix)
-       NO_UNCOMPRESS2 = YesPlease
        NO_IPV6 = YesPlease
        NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
        NO_NSEC = YesPlease
@@ -581,7 +581,6 @@ ifeq ($(uname_S),NONSTOP_KERNEL)
        NO_SETENV = YesPlease
        NO_UNSETENV = YesPlease
        NO_MKDTEMP = YesPlease
-       NO_UNCOMPRESS2 = YesPlease
        # Currently libiconv-1.9.1.
        OLD_ICONV = UnfortunatelyYes
        NO_REGEX = NeedsStartEnd
@@ -599,6 +598,7 @@ ifeq ($(uname_S),NONSTOP_KERNEL)
        NO_MMAP = YesPlease
        NO_POLL = YesPlease
        NO_INTPTR_T = UnfortunatelyYes
+       CSPRNG_METHOD = openssl
        SANE_TOOL_PATH = /usr/coreutils/bin:/usr/local/bin
        SHELL_PATH = /usr/coreutils/bin/bash
 endif
@@ -634,6 +634,7 @@ ifeq ($(uname_S),MINGW)
        NO_POSIX_GOODIES = UnfortunatelyYes
        DEFAULT_HELP_FORMAT = html
        HAVE_PLATFORM_PROCINFO = YesPlease
+       CSPRNG_METHOD = rtlgenrandom
        BASIC_LDFLAGS += -municode
        COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
index d60d494ee4c81b7ccbe3a1e36285a41125e56107..5ee25ec95c898847adaaef258774df3da218f004 100644 (file)
@@ -664,22 +664,9 @@ AC_LINK_IFELSE([ZLIBTEST_SRC],
        NO_DEFLATE_BOUND=yes])
 LIBS="$old_LIBS"
 
-AC_DEFUN([ZLIBTEST_UNCOMPRESS2_SRC], [
-AC_LANG_PROGRAM([#include <zlib.h>],
- [uncompress2(NULL,NULL,NULL,NULL);])])
-AC_MSG_CHECKING([for uncompress2 in -lz])
-old_LIBS="$LIBS"
-LIBS="$LIBS -lz"
-AC_LINK_IFELSE([ZLIBTEST_UNCOMPRESS2_SRC],
-       [AC_MSG_RESULT([yes])],
-       [AC_MSG_RESULT([no])
-       NO_UNCOMPRESS2=yes])
-LIBS="$old_LIBS"
-
 GIT_UNSTASH_FLAGS($ZLIB_PATH)
 
 GIT_CONF_SUBST([NO_DEFLATE_BOUND])
-GIT_CONF_SUBST([NO_UNCOMPRESS2])
 
 #
 # Define NEEDS_SOCKET if linking with libc is not enough (SunOS,
index 5100f56bb37a41f82f895cb14f6311d5081a3482..e44232f85d36607b5be51ffa5b9659021ae63133 100644 (file)
@@ -260,7 +260,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
                                _CONSOLE DETECT_MSYS_TTY STRIP_EXTENSION=".exe"  NO_SYMLINK_HEAD UNRELIABLE_FSTAT
                                NOGDI OBJECT_CREATION_MODE=1 __USE_MINGW_ANSI_STDIO=0
                                USE_NED_ALLOCATOR OVERRIDE_STRDUP MMAP_PREVENTS_DELETE USE_WIN32_MMAP
-                               UNICODE _UNICODE HAVE_WPGMPTR ENSURE_MSYSTEM_IS_SET)
+                               UNICODE _UNICODE HAVE_WPGMPTR ENSURE_MSYSTEM_IS_SET HAVE_RTLGENRANDOM)
        list(APPEND compat_SOURCES compat/mingw.c compat/winansi.c compat/win32/path-utils.c
                compat/win32/pthread.c compat/win32mmap.c compat/win32/syslog.c
                compat/win32/trace2_win32_process_info.c compat/win32/dirent.c
index 2436b8eb6b9fc99e326cf8b2b48190205c283e06..49a328aa8a4efef2272f7755506179dd1fc1129f 100644 (file)
@@ -2991,9 +2991,37 @@ _git_show_branch ()
        __git_complete_revlist
 }
 
+__gitcomp_directories ()
+{
+       local _tmp_dir _tmp_completions _found=0
+
+       # Get the directory of the current token; this differs from dirname
+       # in that it keeps up to the final trailing slash.  If no slash found
+       # that's fine too.
+       [[ "$cur" =~ .*/ ]]
+       _tmp_dir=$BASH_REMATCH
+
+       # Find possible directory completions, adding trailing '/' characters,
+       # de-quoting, and handling unusual characters.
+       while IFS= read -r -d $'\0' c ; do
+               # If there are directory completions, find ones that start
+               # with "$cur", the current token, and put those in COMPREPLY
+               if [[ $c == "$cur"* ]]; then
+                       COMPREPLY+=("$c/")
+                       _found=1
+               fi
+       done < <(git ls-tree -z -d --name-only HEAD $_tmp_dir)
+
+       if [[ $_found == 0 ]] && [[ "$cur" =~ /$ ]]; then
+               # No possible further completions any deeper, so assume we're at
+               # a leaf directory and just consider it complete
+               __gitcomp_direct_append "$cur "
+       fi
+}
+
 _git_sparse_checkout ()
 {
-       local subcommands="list init set disable"
+       local subcommands="list init set disable add reapply"
        local subcommand="$(__git_find_on_cmdline "$subcommands")"
        if [ -z "$subcommand" ]; then
                __gitcomp "$subcommands"
@@ -3001,14 +3029,14 @@ _git_sparse_checkout ()
        fi
 
        case "$subcommand,$cur" in
-       init,--*)
-               __gitcomp "--cone"
-               ;;
-       set,--*)
-               __gitcomp "--stdin"
-               ;;
-       *)
+       *,--*)
+               __gitcomp_builtin sparse-checkout_$subcommand "" "--"
                ;;
+       set,*|add,*)
+               if [ "$(__git config core.sparseCheckoutCone)" == "true" ] ||
+               [ -n "$(__git_find_on_cmdline --cone)" ]; then
+                       __gitcomp_directories
+               fi
        esac
 }
 
index 1ce9c2b00e8058bba4b5bb33a875cddf40b1047b..7db2a97416e03c2a869cd8216d43684ebdb9264d 100644 (file)
@@ -808,6 +808,25 @@ int cmd_main(int argc, const char **argv)
        struct strbuf scalar_usage = STRBUF_INIT;
        int i;
 
+       while (argc > 1 && *argv[1] == '-') {
+               if (!strcmp(argv[1], "-C")) {
+                       if (argc < 3)
+                               die(_("-C requires a <directory>"));
+                       if (chdir(argv[2]) < 0)
+                               die_errno(_("could not change to '%s'"),
+                                         argv[2]);
+                       argc -= 2;
+                       argv += 2;
+               } else if (!strcmp(argv[1], "-c")) {
+                       if (argc < 3)
+                               die(_("-c requires a <key>=<value> argument"));
+                       git_config_push_parameter(argv[2]);
+                       argc -= 2;
+                       argv += 2;
+               } else
+                       break;
+       }
+
        if (argc > 1) {
                argv++;
                argc--;
@@ -818,7 +837,8 @@ int cmd_main(int argc, const char **argv)
        }
 
        strbuf_addstr(&scalar_usage,
-                     N_("scalar <command> [<options>]\n\nCommands:\n"));
+                     N_("scalar [-C <directory>] [-c <key>=<value>] "
+                        "<command> [<options>]\n\nCommands:\n"));
        for (i = 0; builtins[i].name; i++)
                strbuf_addf(&scalar_usage, "\t%s\n", builtins[i].name);
 
index f416d637289c2cfae93c3c32184a1b1a5177b4e0..cf4e5b889cc364e992b1f28405442ed5950103fa 100644 (file)
@@ -36,6 +36,16 @@ The `scalar` command implements various subcommands, and different options
 depending on the subcommand. With the exception of `clone`, `list` and
 `reconfigure --all`, all subcommands expect to be run in an enlistment.
 
+The following options can be specified _before_ the subcommand:
+
+-C <directory>::
+       Before running the subcommand, change the working directory. This
+       option imitates the same option of linkgit:git[1].
+
+-c <key>=<value>::
+       For the duration of running the specified subcommand, configure this
+       setting. This option imitates the same option of linkgit:git[1].
+
 COMMANDS
 --------
 
index 2e1502ad45e1d3995b5100a188e540241e100a78..89781568f43abf301b6df3a932b064c44ecd3e80 100755 (executable)
@@ -85,4 +85,12 @@ test_expect_success 'scalar delete with enlistment' '
        test_path_is_missing cloned
 '
 
+test_expect_success 'scalar supports -c/-C' '
+       test_when_finished "scalar delete sub" &&
+       git init sub &&
+       scalar -C sub -c status.aheadBehind=bogus register &&
+       test -z "$(git -C sub config --local status.aheadBehind)" &&
+       test true = "$(git -C sub config core.preloadIndex)"
+'
+
 test_done
index 71f1fd94bde8b0cd1653677612b0f21e5b36bcd4..1af1d9653e94b39623f8435957c6e7537c41c60a 100755 (executable)
@@ -975,10 +975,10 @@ cmd_merge () {
 
        if test -n "$arg_addmerge_message"
        then
-               git merge -Xsubtree="$arg_prefix" \
+               git merge --no-ff -Xsubtree="$arg_prefix" \
                        --message="$arg_addmerge_message" "$rev"
        else
-               git merge -Xsubtree="$arg_prefix" $rev
+               git merge --no-ff -Xsubtree="$arg_prefix" $rev
        fi
 }
 
index 5060ccd890bd307b1276e367e3b70872718e3af7..a833fd747ad50e4b61704cff3662fdb47f2b4481 100644 (file)
@@ -17,12 +17,14 @@ static void suppress(struct rev_info *revs)
        revs->combined_all_paths = 0;
        revs->merges_imply_patch = 0;
        revs->merges_need_diff = 0;
+       revs->remerge_diff = 0;
 }
 
 static void set_separate(struct rev_info *revs)
 {
        suppress(revs);
        revs->separate_merges = 1;
+       revs->simplify_history = 0;
 }
 
 static void set_first_parent(struct rev_info *revs)
@@ -45,6 +47,13 @@ static void set_dense_combined(struct rev_info *revs)
        revs->dense_combined_merges = 1;
 }
 
+static void set_remerge_diff(struct rev_info *revs)
+{
+       suppress(revs);
+       revs->remerge_diff = 1;
+       revs->simplify_history = 0;
+}
+
 static diff_merges_setup_func_t func_by_opt(const char *optarg)
 {
        if (!strcmp(optarg, "off") || !strcmp(optarg, "none"))
@@ -57,6 +66,8 @@ static diff_merges_setup_func_t func_by_opt(const char *optarg)
                return set_combined;
        else if (!strcmp(optarg, "cc") || !strcmp(optarg, "dense-combined"))
                return set_dense_combined;
+       else if (!strcmp(optarg, "r") || !strcmp(optarg, "remerge"))
+               return set_remerge_diff;
        else if (!strcmp(optarg, "m") || !strcmp(optarg, "on"))
                return set_to_default;
        return NULL;
@@ -110,6 +121,9 @@ int diff_merges_parse_opts(struct rev_info *revs, const char **argv)
        } else if (!strcmp(arg, "--cc")) {
                set_dense_combined(revs);
                revs->merges_imply_patch = 1;
+       } else if (!strcmp(arg, "--remerge-diff")) {
+               set_remerge_diff(revs);
+               revs->merges_imply_patch = 1;
        } else if (!strcmp(arg, "--no-diff-merges")) {
                suppress(revs);
        } else if (!strcmp(arg, "--combined-all-paths")) {
diff --git a/diff.c b/diff.c
index c862771a58939f0cd2a20b2056c8d93b17c7be26..7d5cfd325eacca2441a4aeeb5c90b6f619ef478a 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -28,6 +28,7 @@
 #include "help.h"
 #include "promisor-remote.h"
 #include "dir.h"
+#include "strmap.h"
 
 #ifdef NO_FAST_WORKING_DIRECTORY
 #define FAST_WORKING_DIRECTORY 0
@@ -3353,6 +3354,31 @@ struct userdiff_driver *get_textconv(struct repository *r,
        return userdiff_get_textconv(r, one->driver);
 }
 
+static struct strbuf *additional_headers(struct diff_options *o,
+                                        const char *path)
+{
+       if (!o->additional_path_headers)
+               return NULL;
+       return strmap_get(o->additional_path_headers, path);
+}
+
+static void add_formatted_headers(struct strbuf *msg,
+                                 struct strbuf *more_headers,
+                                 const char *line_prefix,
+                                 const char *meta,
+                                 const char *reset)
+{
+       char *next, *newline;
+
+       for (next = more_headers->buf; *next; next = newline) {
+               newline = strchrnul(next, '\n');
+               strbuf_addf(msg, "%s%s%.*s%s\n", line_prefix, meta,
+                           (int)(newline - next), next, reset);
+               if (*newline)
+                       newline++;
+       }
+}
+
 static void builtin_diff(const char *name_a,
                         const char *name_b,
                         struct diff_filespec *one,
@@ -3411,6 +3437,17 @@ static void builtin_diff(const char *name_a,
        b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
        lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
        lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
+       if (!DIFF_FILE_VALID(one) && !DIFF_FILE_VALID(two)) {
+               /*
+                * We should only reach this point for pairs from
+                * create_filepairs_for_header_only_notifications().  For
+                * these, we should avoid the "/dev/null" special casing
+                * above, meaning we avoid showing such pairs as either
+                * "new file" or "deleted file" below.
+                */
+               lbl[0] = a_one;
+               lbl[1] = b_two;
+       }
        strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, meta, a_one, b_two, reset);
        if (lbl[0][0] == '/') {
                /* /dev/null */
@@ -4275,6 +4312,7 @@ static void fill_metainfo(struct strbuf *msg,
        const char *set = diff_get_color(use_color, DIFF_METAINFO);
        const char *reset = diff_get_color(use_color, DIFF_RESET);
        const char *line_prefix = diff_line_prefix(o);
+       struct strbuf *more_headers = NULL;
 
        *must_show_header = 1;
        strbuf_init(msg, PATH_MAX * 2 + 300);
@@ -4311,6 +4349,11 @@ static void fill_metainfo(struct strbuf *msg,
        default:
                *must_show_header = 0;
        }
+       if ((more_headers = additional_headers(o, name))) {
+               add_formatted_headers(msg, more_headers,
+                                     line_prefix, set, reset);
+               *must_show_header = 1;
+       }
        if (one && two && !oideq(&one->oid, &two->oid)) {
                const unsigned hexsz = the_hash_algo->hexsz;
                int abbrev = o->abbrev ? o->abbrev : DEFAULT_ABBREV;
@@ -4570,6 +4613,43 @@ void repo_diff_setup(struct repository *r, struct diff_options *options)
        prep_parse_options(options);
 }
 
+static const char diff_status_letters[] = {
+       DIFF_STATUS_ADDED,
+       DIFF_STATUS_COPIED,
+       DIFF_STATUS_DELETED,
+       DIFF_STATUS_MODIFIED,
+       DIFF_STATUS_RENAMED,
+       DIFF_STATUS_TYPE_CHANGED,
+       DIFF_STATUS_UNKNOWN,
+       DIFF_STATUS_UNMERGED,
+       DIFF_STATUS_FILTER_AON,
+       DIFF_STATUS_FILTER_BROKEN,
+       '\0',
+};
+
+static unsigned int filter_bit['Z' + 1];
+
+static void prepare_filter_bits(void)
+{
+       int i;
+
+       if (!filter_bit[DIFF_STATUS_ADDED]) {
+               for (i = 0; diff_status_letters[i]; i++)
+                       filter_bit[(int) diff_status_letters[i]] = (1 << i);
+       }
+}
+
+static unsigned filter_bit_tst(char status, const struct diff_options *opt)
+{
+       return opt->filter & filter_bit[(int) status];
+}
+
+unsigned diff_filter_bit(char status)
+{
+       prepare_filter_bits();
+       return filter_bit[(int) status];
+}
+
 void diff_setup_done(struct diff_options *options)
 {
        unsigned check_mask = DIFF_FORMAT_NAME |
@@ -4683,6 +4763,12 @@ void diff_setup_done(struct diff_options *options)
        if (!options->use_color || external_diff())
                options->color_moved = 0;
 
+       if (options->filter_not) {
+               if (!options->filter)
+                       options->filter = ~filter_bit[DIFF_STATUS_FILTER_AON];
+               options->filter &= ~options->filter_not;
+       }
+
        FREE_AND_NULL(options->parseopts);
 }
 
@@ -4774,43 +4860,6 @@ static int parse_dirstat_opt(struct diff_options *options, const char *params)
        return 1;
 }
 
-static const char diff_status_letters[] = {
-       DIFF_STATUS_ADDED,
-       DIFF_STATUS_COPIED,
-       DIFF_STATUS_DELETED,
-       DIFF_STATUS_MODIFIED,
-       DIFF_STATUS_RENAMED,
-       DIFF_STATUS_TYPE_CHANGED,
-       DIFF_STATUS_UNKNOWN,
-       DIFF_STATUS_UNMERGED,
-       DIFF_STATUS_FILTER_AON,
-       DIFF_STATUS_FILTER_BROKEN,
-       '\0',
-};
-
-static unsigned int filter_bit['Z' + 1];
-
-static void prepare_filter_bits(void)
-{
-       int i;
-
-       if (!filter_bit[DIFF_STATUS_ADDED]) {
-               for (i = 0; diff_status_letters[i]; i++)
-                       filter_bit[(int) diff_status_letters[i]] = (1 << i);
-       }
-}
-
-static unsigned filter_bit_tst(char status, const struct diff_options *opt)
-{
-       return opt->filter & filter_bit[(int) status];
-}
-
-unsigned diff_filter_bit(char status)
-{
-       prepare_filter_bits();
-       return filter_bit[(int) status];
-}
-
 static int diff_opt_diff_filter(const struct option *option,
                                const char *optarg, int unset)
 {
@@ -4820,21 +4869,6 @@ static int diff_opt_diff_filter(const struct option *option,
        BUG_ON_OPT_NEG(unset);
        prepare_filter_bits();
 
-       /*
-        * If there is a negation e.g. 'd' in the input, and we haven't
-        * initialized the filter field with another --diff-filter, start
-        * from full set of bits, except for AON.
-        */
-       if (!opt->filter) {
-               for (i = 0; (optch = optarg[i]) != '\0'; i++) {
-                       if (optch < 'a' || 'z' < optch)
-                               continue;
-                       opt->filter = (1 << (ARRAY_SIZE(diff_status_letters) - 1)) - 1;
-                       opt->filter &= ~filter_bit[DIFF_STATUS_FILTER_AON];
-                       break;
-               }
-       }
-
        for (i = 0; (optch = optarg[i]) != '\0'; i++) {
                unsigned int bit;
                int negate;
@@ -4851,7 +4885,7 @@ static int diff_opt_diff_filter(const struct option *option,
                        return error(_("unknown change class '%c' in --diff-filter=%s"),
                                     optarg[i], optarg);
                if (negate)
-                       opt->filter &= ~bit;
+                       opt->filter_not |= bit;
                else
                        opt->filter |= bit;
        }
@@ -5803,12 +5837,27 @@ int diff_unmodified_pair(struct diff_filepair *p)
 
 static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
 {
-       if (diff_unmodified_pair(p))
+       int include_conflict_headers =
+           (additional_headers(o, p->one->path) &&
+            (!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o)));
+
+       /*
+        * Check if we can return early without showing a diff.  Note that
+        * diff_filepair only stores {oid, path, mode, is_valid}
+        * information for each path, and thus diff_unmodified_pair() only
+        * considers those bits of info.  However, we do not want pairs
+        * created by create_filepairs_for_header_only_notifications()
+        * (which always look like unmodified pairs) to be ignored, so
+        * return early if both p is unmodified AND we don't want to
+        * include_conflict_headers.
+        */
+       if (diff_unmodified_pair(p) && !include_conflict_headers)
                return;
 
+       /* Actually, we can also return early to avoid showing tree diffs */
        if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
            (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
-               return; /* no tree diffs in patch format */
+               return;
 
        run_diff(p, o);
 }
@@ -5839,10 +5888,17 @@ static void diff_flush_checkdiff(struct diff_filepair *p,
        run_checkdiff(p, o);
 }
 
-int diff_queue_is_empty(void)
+int diff_queue_is_empty(struct diff_options *o)
 {
        struct diff_queue_struct *q = &diff_queued_diff;
        int i;
+       int include_conflict_headers =
+           (o->additional_path_headers &&
+            (!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o)));
+
+       if (include_conflict_headers)
+               return 0;
+
        for (i = 0; i < q->nr; i++)
                if (!diff_unmodified_pair(q->queue[i]))
                        return 0;
@@ -6276,6 +6332,54 @@ void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)
                warning(_(rename_limit_advice), varname, needed);
 }
 
+static void create_filepairs_for_header_only_notifications(struct diff_options *o)
+{
+       struct strset present;
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct hashmap_iter iter;
+       struct strmap_entry *e;
+       int i;
+
+       strset_init_with_options(&present, /*pool*/ NULL, /*strdup*/ 0);
+
+       /*
+        * Find out which paths exist in diff_queued_diff, preferring
+        * one->path for any pair that has multiple paths.
+        */
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               char *path = p->one->path ? p->one->path : p->two->path;
+
+               if (strmap_contains(o->additional_path_headers, path))
+                       strset_add(&present, path);
+       }
+
+       /*
+        * Loop over paths in additional_path_headers; for each NOT already
+        * in diff_queued_diff, create a synthetic filepair and insert that
+        * into diff_queued_diff.
+        */
+       strmap_for_each_entry(o->additional_path_headers, &iter, e) {
+               if (!strset_contains(&present, e->key)) {
+                       struct diff_filespec *one, *two;
+                       struct diff_filepair *p;
+
+                       one = alloc_filespec(e->key);
+                       two = alloc_filespec(e->key);
+                       fill_filespec(one, null_oid(), 0, 0);
+                       fill_filespec(two, null_oid(), 0, 0);
+                       p = diff_queue(q, one, two);
+                       p->status = DIFF_STATUS_MODIFIED;
+               }
+       }
+
+       /* Re-sort the filepairs */
+       diffcore_fix_diff_index();
+
+       /* Cleanup */
+       strset_clear(&present);
+}
+
 static void diff_flush_patch_all_file_pairs(struct diff_options *o)
 {
        int i;
@@ -6288,6 +6392,9 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)
        if (o->color_moved)
                o->emitted_symbols = &esm;
 
+       if (o->additional_path_headers)
+               create_filepairs_for_header_only_notifications(o);
+
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
                if (check_pair_status(p))
@@ -6358,7 +6465,7 @@ void diff_flush(struct diff_options *options)
         * Order: raw, stat, summary, patch
         * or:    name/name-status/checkdiff (other bits clear)
         */
-       if (!q->nr)
+       if (!q->nr && !options->additional_path_headers)
                goto free_queue;
 
        if (output_format & (DIFF_FORMAT_RAW |
diff --git a/diff.h b/diff.h
index 8ba85c5e60562b081ad7c12ddcddbd37e2004551..8ae18e5ab1ef31b5ff8911a3e1fb671bd4043482 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -283,7 +283,7 @@ struct diff_options {
        struct diff_flags flags;
 
        /* diff-filter bits */
-       unsigned int filter;
+       unsigned int filter, filter_not;
 
        int use_color;
 
@@ -395,6 +395,7 @@ struct diff_options {
 
        struct repository *repo;
        struct option *parseopts;
+       struct strmap *additional_path_headers;
 
        int no_free;
 };
@@ -593,7 +594,7 @@ void diffcore_fix_diff_index(void);
 "                show all files diff when -S is used and hit is found.\n" \
 "  -a  --text    treat all files as text.\n"
 
-int diff_queue_is_empty(void);
+int diff_queue_is_empty(struct diff_options *o);
 void diff_flush(struct diff_options*);
 void diff_free(struct diff_options*);
 void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc);
index 273390229fe4c442915e911b421339e2391fc37c..874797d767bb1abd8144be9412e7e258f3d8ad9d 100644 (file)
@@ -18,7 +18,7 @@ void fetch_negotiator_init(struct repository *r,
                noop_negotiator_init(negotiator);
                return;
 
-       case FETCH_NEGOTIATION_DEFAULT:
+       case FETCH_NEGOTIATION_CONSECUTIVE:
                default_negotiator_init(negotiator);
                return;
        }
index 1229c8296b92547b89bc1cc29e6f2415fa1370af..876907b9df4461059422797aea45385bf1d05de9 100644 (file)
 #endif
 #include <windows.h>
 #define GIT_WINDOWS_NATIVE
+#ifdef HAVE_RTLGENRANDOM
+/* This is required to get access to RtlGenRandom. */
+#define SystemFunction036 NTAPI SystemFunction036
+#include <NTSecAPI.h>
+#undef SystemFunction036
+#endif
 #endif
 
 #include <unistd.h>
 #else
 #include <stdint.h>
 #endif
+#ifdef HAVE_ARC4RANDOM_LIBBSD
+#include <bsd/stdlib.h>
+#endif
+#ifdef HAVE_GETRANDOM
+#include <sys/random.h>
+#endif
 #ifdef NO_INTPTR_T
 /*
  * On I16LP32, ILP32 and LP64 "long" is the safe bet, however
@@ -1386,6 +1398,18 @@ void unleak_memory(const void *ptr, size_t len);
 #define UNLEAK(var) do {} while (0)
 #endif
 
+#define z_const
+#include <zlib.h>
+
+#if ZLIB_VERNUM < 0x1290
+/*
+ * This is uncompress2, which is only available in zlib >= 1.2.9
+ * (released as of early 2017). See compat/zlib-uncompress2.c.
+ */
+int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source,
+               uLong *sourceLen);
+#endif
+
 /*
  * This include must come after system headers, since it introduces macros that
  * replace system names.
@@ -1432,4 +1456,11 @@ static inline void *container_of_or_null_offset(void *ptr, size_t offset)
 
 void sleep_millisec(int millisec);
 
+/*
+ * Generate len bytes from the system cryptographically secure PRNG.
+ * Returns 0 on success and -1 on error, setting errno.  The inability to
+ * satisfy the full request is an error.
+ */
+int csprng_bytes(void *buf, size_t len);
+
 #endif
index cb37545455e9d8b782c078582a7e5a8b902af845..a9b1f9044108e4dce94b865f1f777039d8755613 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -108,10 +108,7 @@ def p4_build_cmd(cmd):
         # Provide a way to not pass this option by setting git-p4.retries to 0
         real_cmd += ["-r", str(retries)]
 
-    if not isinstance(cmd, list):
-        real_cmd = ' '.join(real_cmd) + ' ' + cmd
-    else:
-        real_cmd += cmd
+    real_cmd += cmd
 
     # now check that we can actually talk to the server
     global p4_access_checked
@@ -223,153 +220,93 @@ def decode_path(path):
 
 def run_git_hook(cmd, param=[]):
     """Execute a hook if the hook exists."""
+    args = ['git', 'hook', 'run', '--ignore-missing', cmd]
+    if param:
+        args.append("--")
+        for p in param:
+            args.append(p)
+    return subprocess.call(args) == 0
+
+def write_pipe(c, stdin, *k, **kw):
     if verbose:
-        sys.stderr.write("Looking for hook: %s\n" % cmd)
-        sys.stderr.flush()
-
-    hooks_path = gitConfig("core.hooksPath")
-    if len(hooks_path) <= 0:
-        hooks_path = os.path.join(os.environ["GIT_DIR"], "hooks")
-
-    if not isinstance(param, list):
-        param=[param]
-
-    # resolve hook file name, OS depdenent
-    hook_file = os.path.join(hooks_path, cmd)
-    if platform.system() == 'Windows':
-        if not os.path.isfile(hook_file):
-            # look for the file with an extension
-            files = glob.glob(hook_file + ".*")
-            if not files:
-                return True
-            files.sort()
-            hook_file = files.pop()
-            while hook_file.upper().endswith(".SAMPLE"):
-                # The file is a sample hook. We don't want it
-                if len(files) > 0:
-                    hook_file = files.pop()
-                else:
-                    return True
-
-    if not os.path.isfile(hook_file) or not os.access(hook_file, os.X_OK):
-        return True
-
-    return run_hook_command(hook_file, param) == 0
-
-def run_hook_command(cmd, param):
-    """Executes a git hook command
-       cmd = the command line file to be executed. This can be
-       a file that is run by OS association.
-
-       param = a list of parameters to pass to the cmd command
-
-       On windows, the extension is checked to see if it should
-       be run with the Git for Windows Bash shell.  If there
-       is no file extension, the file is deemed a bash shell
-       and will be handed off to sh.exe. Otherwise, Windows
-       will be called with the shell to handle the file assocation.
-
-       For non Windows operating systems, the file is called
-       as an executable.
-    """
-    cli = [cmd] + param
-    use_shell = False
-    if platform.system() == 'Windows':
-        (root,ext) = os.path.splitext(cmd)
-        if ext == "":
-            exe_path = os.environ.get("EXEPATH")
-            if exe_path is None:
-                exe_path = ""
-            else:
-                exe_path = os.path.join(exe_path, "bin")
-            cli = [os.path.join(exe_path, "SH.EXE")] + cli
-        else:
-            use_shell = True
-    return subprocess.call(cli, shell=use_shell)
-
-
-def write_pipe(c, stdin):
-    if verbose:
-        sys.stderr.write('Writing pipe: %s\n' % str(c))
+        sys.stderr.write('Writing pipe: {}\n'.format(' '.join(c)))
 
-    expand = not isinstance(c, list)
-    p = subprocess.Popen(c, stdin=subprocess.PIPE, shell=expand)
+    p = subprocess.Popen(c, stdin=subprocess.PIPE, *k, **kw)
     pipe = p.stdin
     val = pipe.write(stdin)
     pipe.close()
     if p.wait():
-        die('Command failed: %s' % str(c))
+        die('Command failed: {}'.format(' '.join(c)))
 
     return val
 
-def p4_write_pipe(c, stdin):
+def p4_write_pipe(c, stdin, *k, **kw):
     real_cmd = p4_build_cmd(c)
     if bytes is not str and isinstance(stdin, str):
         stdin = encode_text_stream(stdin)
-    return write_pipe(real_cmd, stdin)
+    return write_pipe(real_cmd, stdin, *k, **kw)
 
-def read_pipe_full(c):
+def read_pipe_full(c, *k, **kw):
     """ Read output from  command. Returns a tuple
         of the return status, stdout text and stderr
         text.
     """
     if verbose:
-        sys.stderr.write('Reading pipe: %s\n' % str(c))
+        sys.stderr.write('Reading pipe: {}\n'.format(' '.join(c)))
 
-    expand = not isinstance(c, list)
-    p = subprocess.Popen(c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=expand)
+    p = subprocess.Popen(
+        c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, *k, **kw)
     (out, err) = p.communicate()
     return (p.returncode, out, decode_text_stream(err))
 
-def read_pipe(c, ignore_error=False, raw=False):
+def read_pipe(c, ignore_error=False, raw=False, *k, **kw):
     """ Read output from  command. Returns the output text on
         success. On failure, terminates execution, unless
         ignore_error is True, when it returns an empty string.
 
         If raw is True, do not attempt to decode output text.
     """
-    (retcode, out, err) = read_pipe_full(c)
+    (retcode, out, err) = read_pipe_full(c, *k, **kw)
     if retcode != 0:
         if ignore_error:
             out = ""
         else:
-            die('Command failed: %s\nError: %s' % (str(c), err))
+            die('Command failed: {}\nError: {}'.format(' '.join(c), err))
     if not raw:
         out = decode_text_stream(out)
     return out
 
-def read_pipe_text(c):
+def read_pipe_text(c, *k, **kw):
     """ Read output from a command with trailing whitespace stripped.
         On error, returns None.
     """
-    (retcode, out, err) = read_pipe_full(c)
+    (retcode, out, err) = read_pipe_full(c, *k, **kw)
     if retcode != 0:
         return None
     else:
         return decode_text_stream(out).rstrip()
 
-def p4_read_pipe(c, ignore_error=False, raw=False):
+def p4_read_pipe(c, ignore_error=False, raw=False, *k, **kw):
     real_cmd = p4_build_cmd(c)
-    return read_pipe(real_cmd, ignore_error, raw=raw)
+    return read_pipe(real_cmd, ignore_error, raw=raw, *k, **kw)
 
-def read_pipe_lines(c, raw=False):
+def read_pipe_lines(c, raw=False, *k, **kw):
     if verbose:
-        sys.stderr.write('Reading pipe: %s\n' % str(c))
+        sys.stderr.write('Reading pipe: {}\n'.format(' '.join(c)))
 
-    expand = not isinstance(c, list)
-    p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
+    p = subprocess.Popen(c, stdout=subprocess.PIPE, *k, **kw)
     pipe = p.stdout
     lines = pipe.readlines()
     if not raw:
         lines = [decode_text_stream(line) for line in lines]
     if pipe.close() or p.wait():
-        die('Command failed: %s' % str(c))
+        die('Command failed: {}'.format(' '.join(c)))
     return lines
 
-def p4_read_pipe_lines(c):
+def p4_read_pipe_lines(c, *k, **kw):
     """Specifically invoke p4 on the command supplied. """
     real_cmd = p4_build_cmd(c)
-    return read_pipe_lines(real_cmd)
+    return read_pipe_lines(real_cmd, *k, **kw)
 
 def p4_has_command(cmd):
     """Ask p4 for help on this command.  If it returns an error, the
@@ -400,23 +337,22 @@ def p4_has_move_command():
     # assume it failed because @... was invalid changelist
     return True
 
-def system(cmd, ignore_error=False):
-    expand = not isinstance(cmd, list)
+def system(cmd, ignore_error=False, *k, **kw):
     if verbose:
-        sys.stderr.write("executing %s\n" % str(cmd))
-    retcode = subprocess.call(cmd, shell=expand)
+        sys.stderr.write("executing {}\n".format(
+            ' '.join(cmd) if isinstance(cmd, list) else cmd))
+    retcode = subprocess.call(cmd, *k, **kw)
     if retcode and not ignore_error:
-        raise CalledProcessError(retcode, cmd)
+        raise subprocess.CalledProcessError(retcode, cmd)
 
     return retcode
 
-def p4_system(cmd):
+def p4_system(cmd, *k, **kw):
     """Specifically invoke p4 as the system command. """
     real_cmd = p4_build_cmd(cmd)
-    expand = not isinstance(real_cmd, list)
-    retcode = subprocess.call(real_cmd, shell=expand)
+    retcode = subprocess.call(real_cmd, *k, **kw)
     if retcode:
-        raise CalledProcessError(retcode, real_cmd)
+        raise subprocess.CalledProcessError(retcode, real_cmd)
 
 def die_bad_access(s):
     die("failure accessing depot: {0}".format(s.rstrip()))
@@ -735,18 +671,11 @@ def isModeExecChanged(src_mode, dst_mode):
     return isModeExec(src_mode) != isModeExec(dst_mode)
 
 def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
-        errors_as_exceptions=False):
-
-    if not isinstance(cmd, list):
-        cmd = "-G " + cmd
-        expand = True
-    else:
-        cmd = ["-G"] + cmd
-        expand = False
+        errors_as_exceptions=False, *k, **kw):
 
-    cmd = p4_build_cmd(cmd)
+    cmd = p4_build_cmd(["-G"] + cmd)
     if verbose:
-        sys.stderr.write("Opening pipe: %s\n" % str(cmd))
+        sys.stderr.write("Opening pipe: {}\n".format(' '.join(cmd)))
 
     # Use a temporary file to avoid deadlocks without
     # subprocess.communicate(), which would put another copy
@@ -763,10 +692,8 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
         stdin_file.flush()
         stdin_file.seek(0)
 
-    p4 = subprocess.Popen(cmd,
-                          shell=expand,
-                          stdin=stdin_file,
-                          stdout=subprocess.PIPE)
+    p4 = subprocess.Popen(
+        cmd, stdin=stdin_file, stdout=subprocess.PIPE, *k, **kw)
 
     result = []
     try:
@@ -819,8 +746,8 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
 
     return result
 
-def p4Cmd(cmd):
-    list = p4CmdList(cmd)
+def p4Cmd(cmd, *k, **kw):
+    list = p4CmdList(cmd, *k, **kw)
     result = {}
     for entry in list:
         result.update(entry)
@@ -869,7 +796,7 @@ def isValidGitDir(path):
     return git_dir(path) != None
 
 def parseRevision(ref):
-    return read_pipe("git rev-parse %s" % ref).strip()
+    return read_pipe(["git", "rev-parse", ref]).strip()
 
 def branchExists(ref):
     rev = read_pipe(["git", "rev-parse", "-q", "--verify", ref],
@@ -975,11 +902,11 @@ def p4BranchesInGit(branchesAreInRemotes=True):
 
     branches = {}
 
-    cmdline = "git rev-parse --symbolic "
+    cmdline = ["git", "rev-parse", "--symbolic"]
     if branchesAreInRemotes:
-        cmdline += "--remotes"
+        cmdline.append("--remotes")
     else:
-        cmdline += "--branches"
+        cmdline.append("--branches")
 
     for line in read_pipe_lines(cmdline):
         line = line.strip()
@@ -1044,7 +971,7 @@ def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent
 
     originPrefix = "origin/p4/"
 
-    for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
+    for line in read_pipe_lines(["git", "rev-parse", "--symbolic", "--remotes"]):
         line = line.strip()
         if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
             continue
@@ -1082,7 +1009,7 @@ def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent
                               remoteHead, ','.join(settings['depot-paths'])))
 
         if update:
-            system("git update-ref %s %s" % (remoteHead, originHead))
+            system(["git", "update-ref", remoteHead, originHead])
 
 def originP4BranchesExist():
         return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
@@ -1196,7 +1123,7 @@ def getClientSpec():
     """Look at the p4 client spec, create a View() object that contains
        all the mappings, and return it."""
 
-    specList = p4CmdList("client -o")
+    specList = p4CmdList(["client", "-o"])
     if len(specList) != 1:
         die('Output from "client -o" is %d lines, expecting 1' %
             len(specList))
@@ -1225,7 +1152,7 @@ def getClientSpec():
 def getClientRoot():
     """Grab the client directory."""
 
-    output = p4CmdList("client -o")
+    output = p4CmdList(["client", "-o"])
     if len(output) != 1:
         die('Output from "client -o" is %d lines, expecting 1' % len(output))
 
@@ -1480,7 +1407,7 @@ class P4UserMap:
         if self.myP4UserId:
             return self.myP4UserId
 
-        results = p4CmdList("user -o")
+        results = p4CmdList(["user", "-o"])
         for r in results:
             if 'User' in r:
                 self.myP4UserId = r['User']
@@ -1505,7 +1432,7 @@ class P4UserMap:
         self.users = {}
         self.emails = {}
 
-        for output in p4CmdList("users"):
+        for output in p4CmdList(["users"]):
             if "User" not in output:
                 continue
             self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
@@ -1629,7 +1556,7 @@ class P4Submit(Command, P4UserMap):
             die("Large file system not supported for git-p4 submit command. Please remove it from config.")
 
     def check(self):
-        if len(p4CmdList("opened ...")) > 0:
+        if len(p4CmdList(["opened", "..."])) > 0:
             die("You have files opened with perforce! Close them before starting the sync.")
 
     def separate_jobs_from_description(self, message):
@@ -1733,7 +1660,7 @@ class P4Submit(Command, P4UserMap):
         # then gets used to patch up the username in the change. If the same
         # client spec is being used by multiple processes then this might go
         # wrong.
-        results = p4CmdList("client -o")        # find the current client
+        results = p4CmdList(["client", "-o"])        # find the current client
         client = None
         for r in results:
             if 'Client' in r:
@@ -1749,7 +1676,7 @@ class P4Submit(Command, P4UserMap):
 
     def modifyChangelistUser(self, changelist, newUser):
         # fixup the user field of a changelist after it has been submitted.
-        changes = p4CmdList("change -o %s" % changelist)
+        changes = p4CmdList(["change", "-o", changelist])
         if len(changes) != 1:
             die("Bad output from p4 change modifying %s to user %s" %
                 (changelist, newUser))
@@ -1760,7 +1687,7 @@ class P4Submit(Command, P4UserMap):
         # p4 does not understand format version 3 and above
         input = marshal.dumps(c, 2)
 
-        result = p4CmdList("change -f -i", stdin=input)
+        result = p4CmdList(["change", "-f", "-i"], stdin=input)
         for r in result:
             if 'code' in r:
                 if r['code'] == 'error':
@@ -1866,7 +1793,7 @@ class P4Submit(Command, P4UserMap):
         if "P4EDITOR" in os.environ and (os.environ.get("P4EDITOR") != ""):
             editor = os.environ.get("P4EDITOR")
         else:
-            editor = read_pipe("git var GIT_EDITOR").strip()
+            editor = read_pipe(["git", "var", "GIT_EDITOR"]).strip()
         system(["sh", "-c", ('%s "$@"' % editor), editor, template_file])
 
         # If the file was not saved, prompt to see if this patch should
@@ -1924,7 +1851,8 @@ class P4Submit(Command, P4UserMap):
 
         (p4User, gitEmail) = self.p4UserForCommit(id)
 
-        diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (self.diffOpts, id, id))
+        diff = read_pipe_lines(
+            ["git", "diff-tree", "-r"] + self.diffOpts + ["{}^".format(id), id])
         filesToAdd = set()
         filesToChangeType = set()
         filesToDelete = set()
@@ -2060,7 +1988,7 @@ class P4Submit(Command, P4UserMap):
         #
         # Apply the patch for real, and do add/delete/+x handling.
         #
-        system(applyPatchCmd)
+        system(applyPatchCmd, shell=True)
 
         for f in filesToChangeType:
             p4_edit(f, "-t", "auto")
@@ -2410,17 +2338,17 @@ class P4Submit(Command, P4UserMap):
         #
         if self.detectRenames:
             # command-line -M arg
-            self.diffOpts = "-M"
+            self.diffOpts = ["-M"]
         else:
             # If not explicitly set check the config variable
             detectRenames = gitConfig("git-p4.detectRenames")
 
             if detectRenames.lower() == "false" or detectRenames == "":
-                self.diffOpts = ""
+                self.diffOpts = []
             elif detectRenames.lower() == "true":
-                self.diffOpts = "-M"
+                self.diffOpts = ["-M"]
             else:
-                self.diffOpts = "-M%s" % detectRenames
+                self.diffOpts = ["-M{}".format(detectRenames)]
 
         # no command-line arg for -C or --find-copies-harder, just
         # config variables
@@ -2428,12 +2356,12 @@ class P4Submit(Command, P4UserMap):
         if detectCopies.lower() == "false" or detectCopies == "":
             pass
         elif detectCopies.lower() == "true":
-            self.diffOpts += " -C"
+            self.diffOpts.append("-C")
         else:
-            self.diffOpts += " -C%s" % detectCopies
+            self.diffOpts.append("-C{}".format(detectCopies))
 
         if gitConfigBool("git-p4.detectCopiesHarder"):
-            self.diffOpts += " --find-copies-harder"
+            self.diffOpts.append("--find-copies-harder")
 
         num_shelves = len(self.update_shelve)
         if num_shelves > 0 and num_shelves != len(commits):
@@ -3381,12 +3309,9 @@ class P4Sync(Command, P4UserMap):
         lostAndFoundBranches = set()
 
         user = gitConfig("git-p4.branchUser")
-        if len(user) > 0:
-            command = "branches -u %s" % user
-        else:
-            command = "branches"
 
-        for info in p4CmdList(command):
+        for info in p4CmdList(
+            ["branches"] + (["-u", user] if len(user) > 0 else [])):
             details = p4Cmd(["branch", "-o", info["branch"]])
             viewIdx = 0
             while "View%s" % viewIdx in details:
@@ -3477,7 +3402,8 @@ class P4Sync(Command, P4UserMap):
         while True:
             if self.verbose:
                 print("trying: earliest %s latest %s" % (earliestCommit, latestCommit))
-            next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
+            next = read_pipe(["git", "rev-list", "--bisect",
+                latestCommit, earliestCommit]).strip()
             if len(next) == 0:
                 if self.verbose:
                     print("argh")
@@ -3633,7 +3559,7 @@ class P4Sync(Command, P4UserMap):
             if self.hasOrigin:
                 if not self.silent:
                     print('Syncing with origin first, using "git fetch origin"')
-                system("git fetch origin")
+                system(["git", "fetch", "origin"])
 
     def importHeadRevision(self, revision):
         print("Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch))
@@ -3800,8 +3726,8 @@ class P4Sync(Command, P4UserMap):
         if len(self.branch) == 0:
             self.branch = self.refPrefix + "master"
             if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
-                system("git update-ref %s refs/heads/p4" % self.branch)
-                system("git branch -D p4")
+                system(["git", "update-ref", self.branch, "refs/heads/p4"])
+                system(["git", "branch", "-D", "p4"])
 
         # accept either the command-line option, or the configuration variable
         if self.useClientSpec:
@@ -4004,7 +3930,7 @@ class P4Sync(Command, P4UserMap):
         # Cleanup temporary branches created during import
         if self.tempBranches != []:
             for branch in self.tempBranches:
-                read_pipe("git update-ref -d %s" % branch)
+                read_pipe(["git", "update-ref", "-d", branch])
             os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation))
 
         # Create a symbolic ref p4/HEAD pointing to p4/<branch> to allow
@@ -4036,7 +3962,7 @@ class P4Rebase(Command):
     def rebase(self):
         if os.system("git update-index --refresh") != 0:
             die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up to date or stash away all your changes with git stash.");
-        if len(read_pipe("git diff-index HEAD --")) > 0:
+        if len(read_pipe(["git", "diff-index", "HEAD", "--"])) > 0:
             die("You have uncommitted changes. Please commit them before rebasing or stash them away with git stash.");
 
         [upstream, settings] = findUpstreamBranchPoint()
@@ -4047,9 +3973,10 @@ class P4Rebase(Command):
         upstream = re.sub("~[0-9]+$", "", upstream)
 
         print("Rebasing the current branch onto %s" % upstream)
-        oldHead = read_pipe("git rev-parse HEAD").strip()
-        system("git rebase %s" % upstream)
-        system("git diff-tree --stat --summary -M %s HEAD --" % oldHead)
+        oldHead = read_pipe(["git", "rev-parse", "HEAD"]).strip()
+        system(["git", "rebase", upstream])
+        system(["git", "diff-tree", "--stat", "--summary", "-M", oldHead,
+            "HEAD", "--"])
         return True
 
 class P4Clone(P4Sync):
@@ -4110,7 +4037,7 @@ class P4Clone(P4Sync):
             init_cmd.append("--bare")
         retcode = subprocess.call(init_cmd)
         if retcode:
-            raise CalledProcessError(retcode, init_cmd)
+            raise subprocess.CalledProcessError(retcode, init_cmd)
 
         if not P4Sync.run(self, depotPaths):
             return False
@@ -4126,7 +4053,7 @@ class P4Clone(P4Sync):
 
         # auto-set this variable if invoked with --use-client-spec
         if self.useClientSpec_from_options:
-            system("git config --bool git-p4.useclientspec true")
+            system(["git", "config", "--bool", "git-p4.useclientspec", "true"])
 
         return True
 
@@ -4257,10 +4184,7 @@ class P4Branches(Command):
         if originP4BranchesExist():
             createOrUpdateBranchesFromOrigin()
 
-        cmdline = "git rev-parse --symbolic "
-        cmdline += " --remotes"
-
-        for line in read_pipe_lines(cmdline):
+        for line in read_pipe_lines(["git", "rev-parse", "--symbolic", "--remotes"]):
             line = line.strip()
 
             if not line.startswith('p4/') or line == "p4/HEAD":
@@ -4343,9 +4267,9 @@ def main():
             cmd.gitdir = os.path.abspath(".git")
             if not isValidGitDir(cmd.gitdir):
                 # "rev-parse --git-dir" without arguments will try $PWD/.git
-                cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
+                cmd.gitdir = read_pipe(["git", "rev-parse", "--git-dir"]).strip()
                 if os.path.exists(cmd.gitdir):
-                    cdup = read_pipe("git rev-parse --show-cdup").strip()
+                    cdup = read_pipe(["git", "rev-parse", "--show-cdup"]).strip()
                     if len(cdup) > 0:
                         chdir(cdup);
 
index 04087221aa7407bce8f722eaef82131692845159..a98460bdb96580fe52e3029499c946874ac86ffc 100755 (executable)
@@ -225,13 +225,13 @@ my $multiedit;
 my $editor;
 
 sub system_or_msg {
-       my ($args, $msg) = @_;
+       my ($args, $msg, $cmd_name) = @_;
        system(@$args);
        my $signalled = $? & 127;
        my $exit_code = $? >> 8;
        return unless $signalled or $exit_code;
 
-       my @sprintf_args = ($args->[0], $exit_code);
+       my @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code);
        if (defined $msg) {
                # Quiet the 'redundant' warning category, except we
                # need to support down to Perl 5.8, so we can't do a
@@ -2075,10 +2075,10 @@ sub validate_patch {
        my ($fn, $xfer_encoding) = @_;
 
        if ($repo) {
+               my $hook_name = 'sendemail-validate';
                my $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');
                require File::Spec;
-               my $validate_hook = File::Spec->catfile($hooks_path,
-                                           'sendemail-validate');
+               my $validate_hook = File::Spec->catfile($hooks_path, $hook_name);
                my $hook_error;
                if (-x $validate_hook) {
                        require Cwd;
@@ -2088,13 +2088,19 @@ sub validate_patch {
                        chdir($repo->wc_path() or $repo->repo_path())
                                or die("chdir: $!");
                        local $ENV{"GIT_DIR"} = $repo->repo_path();
-                       $hook_error = system_or_msg([$validate_hook, $target]);
+                       my @cmd = ("git", "hook", "run", "--ignore-missing",
+                                   $hook_name, "--");
+                       my @cmd_msg = (@cmd, "<patch>");
+                       my @cmd_run = (@cmd, $target);
+                       $hook_error = system_or_msg(\@cmd_run, undef, "@cmd_msg");
                        chdir($cwd_save) or die("chdir: $!");
                }
                if ($hook_error) {
-                       die sprintf(__("fatal: %s: rejected by sendemail-validate hook\n" .
-                                      "%s\n" .
-                                      "warning: no patches were sent\n"), $fn, $hook_error);
+                       $hook_error = sprintf(__("fatal: %s: rejected by %s hook\n" .
+                                                $hook_error . "\n" .
+                                                "warning: no patches were sent\n"),
+                                             $fn, $hook_name);
+                       die $hook_error;
                }
        }
 
index b93f39288ce41f06b0e1f564d833a40f6c8577b9..d92df37e9924719b2aa6d1c84a88d56811d2bd4f 100644 (file)
@@ -101,7 +101,6 @@ $LONG_USAGE")"
        case "$1" in
                -h)
                echo "$LONG_USAGE"
-               case "$0" in *git-legacy-stash) exit 129;; esac
                exit
        esac
 fi
diff --git a/git.c b/git.c
index edda922ce6d423b8b734b47b979f4912b9a4d281..340665d4a044dbdb71981b0699102efa97895556 100644 (file)
--- a/git.c
+++ b/git.c
@@ -541,6 +541,7 @@ static struct cmd_struct commands[] = {
        { "grep", cmd_grep, RUN_SETUP_GENTLY },
        { "hash-object", cmd_hash_object },
        { "help", cmd_help },
+       { "hook", cmd_hook, RUN_SETUP },
        { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT },
        { "init", cmd_init_db },
        { "init-db", cmd_init_db },
index b52eb0e2e04b37c6868f7573d750b688a4ec0e68..17b1e44baa6a97a73fa5566d201de46c856c13ff 100644 (file)
@@ -433,7 +433,6 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,
        struct tempfile *buffer_file;
        int ret = -1;
        const char *line;
-       size_t trust_size;
        char *principal;
        struct strbuf ssh_principals_out = STRBUF_INIT;
        struct strbuf ssh_principals_err = STRBUF_INIT;
@@ -502,15 +501,30 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,
                ret = -1;
        } else {
                /* Check every principal we found (one per line) */
-               for (line = ssh_principals_out.buf; *line;
-                    line = strchrnul(line + 1, '\n')) {
-                       while (*line == '\n')
-                               line++;
-                       if (!*line)
-                               break;
-
-                       trust_size = strcspn(line, "\n");
-                       principal = xmemdupz(line, trust_size);
+               const char *next;
+               for (line = ssh_principals_out.buf;
+                    *line;
+                    line = next) {
+                       const char *end_of_text;
+
+                       next = end_of_text = strchrnul(line, '\n');
+
+                        /* Did we find a LF, and did we have CR before it? */
+                       if (*end_of_text &&
+                           line < end_of_text &&
+                           end_of_text[-1] == '\r')
+                               end_of_text--;
+
+                       /* Unless we hit NUL, skip over the LF we found */
+                       if (*next)
+                               next++;
+
+                       /* Not all lines are data.  Skip empty ones */
+                       if (line == end_of_text)
+                               continue;
+
+                       /* We now know we have an non-empty line. Process it */
+                       principal = xmemdupz(line, end_of_text - line);
 
                        child_process_init(&ssh_keygen);
                        strbuf_release(&ssh_keygen_out);
diff --git a/grep.c b/grep.c
index 7bb0360869a64bca7af1e2d7faecb937a63b7910..5bec7fd7935dedc94bbde1e8efe67745e2022702 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -595,6 +595,35 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
        }
 }
 
+static struct grep_expr *grep_not_expr(struct grep_expr *expr)
+{
+       struct grep_expr *z = xcalloc(1, sizeof(*z));
+       z->node = GREP_NODE_NOT;
+       z->u.unary = expr;
+       return z;
+}
+
+static struct grep_expr *grep_binexp(enum grep_expr_node kind,
+                                    struct grep_expr *left,
+                                    struct grep_expr *right)
+{
+       struct grep_expr *z = xcalloc(1, sizeof(*z));
+       z->node = kind;
+       z->u.binary.left = left;
+       z->u.binary.right = right;
+       return z;
+}
+
+static struct grep_expr *grep_or_expr(struct grep_expr *left, struct grep_expr *right)
+{
+       return grep_binexp(GREP_NODE_OR, left, right);
+}
+
+static struct grep_expr *grep_and_expr(struct grep_expr *left, struct grep_expr *right)
+{
+       return grep_binexp(GREP_NODE_AND, left, right);
+}
+
 static struct grep_expr *compile_pattern_or(struct grep_pat **);
 static struct grep_expr *compile_pattern_atom(struct grep_pat **list)
 {
@@ -638,12 +667,10 @@ static struct grep_expr *compile_pattern_not(struct grep_pat **list)
                if (!p->next)
                        die("--not not followed by pattern expression");
                *list = p->next;
-               CALLOC_ARRAY(x, 1);
-               x->node = GREP_NODE_NOT;
-               x->u.unary = compile_pattern_not(list);
-               if (!x->u.unary)
+               x = compile_pattern_not(list);
+               if (!x)
                        die("--not followed by non pattern expression");
-               return x;
+               return grep_not_expr(x);
        default:
                return compile_pattern_atom(list);
        }
@@ -652,7 +679,7 @@ static struct grep_expr *compile_pattern_not(struct grep_pat **list)
 static struct grep_expr *compile_pattern_and(struct grep_pat **list)
 {
        struct grep_pat *p;
-       struct grep_expr *x, *y, *z;
+       struct grep_expr *x, *y;
 
        x = compile_pattern_not(list);
        p = *list;
@@ -665,11 +692,7 @@ static struct grep_expr *compile_pattern_and(struct grep_pat **list)
                y = compile_pattern_and(list);
                if (!y)
                        die("--and not followed by pattern expression");
-               CALLOC_ARRAY(z, 1);
-               z->node = GREP_NODE_AND;
-               z->u.binary.left = x;
-               z->u.binary.right = y;
-               return z;
+               return grep_and_expr(x, y);
        }
        return x;
 }
@@ -677,7 +700,7 @@ static struct grep_expr *compile_pattern_and(struct grep_pat **list)
 static struct grep_expr *compile_pattern_or(struct grep_pat **list)
 {
        struct grep_pat *p;
-       struct grep_expr *x, *y, *z;
+       struct grep_expr *x, *y;
 
        x = compile_pattern_and(list);
        p = *list;
@@ -685,11 +708,7 @@ static struct grep_expr *compile_pattern_or(struct grep_pat **list)
                y = compile_pattern_or(list);
                if (!y)
                        die("not a pattern expression %s", p->pattern);
-               CALLOC_ARRAY(z, 1);
-               z->node = GREP_NODE_OR;
-               z->u.binary.left = x;
-               z->u.binary.right = y;
-               return z;
+               return grep_or_expr(x, y);
        }
        return x;
 }
@@ -699,14 +718,6 @@ static struct grep_expr *compile_pattern_expr(struct grep_pat **list)
        return compile_pattern_or(list);
 }
 
-static struct grep_expr *grep_not_expr(struct grep_expr *expr)
-{
-       struct grep_expr *z = xcalloc(1, sizeof(*z));
-       z->node = GREP_NODE_NOT;
-       z->u.unary = expr;
-       return z;
-}
-
 static struct grep_expr *grep_true_expr(void)
 {
        struct grep_expr *z = xcalloc(1, sizeof(*z));
@@ -714,15 +725,6 @@ static struct grep_expr *grep_true_expr(void)
        return z;
 }
 
-static struct grep_expr *grep_or_expr(struct grep_expr *left, struct grep_expr *right)
-{
-       struct grep_expr *z = xcalloc(1, sizeof(*z));
-       z->node = GREP_NODE_OR;
-       z->u.binary.left = left;
-       z->u.binary.right = right;
-       return z;
-}
-
 static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 {
        struct grep_pat *p;
diff --git a/hook.c b/hook.c
index 55e1145a4b7b8c59276f082ab0b240817702ace4..69a215b2c3c2c3b95a799a56325de9111873b39e 100644 (file)
--- a/hook.c
+++ b/hook.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "hook.h"
 #include "run-command.h"
+#include "config.h"
 
 const char *find_hook(const char *name)
 {
@@ -40,3 +41,133 @@ int hook_exists(const char *name)
 {
        return !!find_hook(name);
 }
+
+static int pick_next_hook(struct child_process *cp,
+                         struct strbuf *out,
+                         void *pp_cb,
+                         void **pp_task_cb)
+{
+       struct hook_cb_data *hook_cb = pp_cb;
+       const char *hook_path = hook_cb->hook_path;
+
+       if (!hook_path)
+               return 0;
+
+       cp->no_stdin = 1;
+       strvec_pushv(&cp->env_array, hook_cb->options->env.v);
+       cp->stdout_to_stderr = 1;
+       cp->trace2_hook_name = hook_cb->hook_name;
+       cp->dir = hook_cb->options->dir;
+
+       strvec_push(&cp->args, hook_path);
+       strvec_pushv(&cp->args, hook_cb->options->args.v);
+
+       /* Provide context for errors if necessary */
+       *pp_task_cb = (char *)hook_path;
+
+       /*
+        * This pick_next_hook() will be called again, we're only
+        * running one hook, so indicate that no more work will be
+        * done.
+        */
+       hook_cb->hook_path = NULL;
+
+       return 1;
+}
+
+static int notify_start_failure(struct strbuf *out,
+                               void *pp_cb,
+                               void *pp_task_cp)
+{
+       struct hook_cb_data *hook_cb = pp_cb;
+       const char *hook_path = pp_task_cp;
+
+       hook_cb->rc |= 1;
+
+       strbuf_addf(out, _("Couldn't start hook '%s'\n"),
+                   hook_path);
+
+       return 1;
+}
+
+static int notify_hook_finished(int result,
+                               struct strbuf *out,
+                               void *pp_cb,
+                               void *pp_task_cb)
+{
+       struct hook_cb_data *hook_cb = pp_cb;
+
+       hook_cb->rc |= result;
+
+       return 0;
+}
+
+static void run_hooks_opt_clear(struct run_hooks_opt *options)
+{
+       strvec_clear(&options->env);
+       strvec_clear(&options->args);
+}
+
+int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
+{
+       struct strbuf abs_path = STRBUF_INIT;
+       struct hook_cb_data cb_data = {
+               .rc = 0,
+               .hook_name = hook_name,
+               .options = options,
+       };
+       const char *const hook_path = find_hook(hook_name);
+       int jobs = 1;
+       int ret = 0;
+
+       if (!options)
+               BUG("a struct run_hooks_opt must be provided to run_hooks");
+
+       if (!hook_path && !options->error_if_missing)
+               goto cleanup;
+
+       if (!hook_path) {
+               ret = error("cannot find a hook named %s", hook_name);
+               goto cleanup;
+       }
+
+       cb_data.hook_path = hook_path;
+       if (options->dir) {
+               strbuf_add_absolute_path(&abs_path, hook_path);
+               cb_data.hook_path = abs_path.buf;
+       }
+
+       run_processes_parallel_tr2(jobs,
+                                  pick_next_hook,
+                                  notify_start_failure,
+                                  notify_hook_finished,
+                                  &cb_data,
+                                  "hook",
+                                  hook_name);
+       ret = cb_data.rc;
+cleanup:
+       strbuf_release(&abs_path);
+       run_hooks_opt_clear(options);
+       return ret;
+}
+
+int run_hooks(const char *hook_name)
+{
+       struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
+       return run_hooks_opt(hook_name, &opt);
+}
+
+int run_hooks_l(const char *hook_name, ...)
+{
+       struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+       va_list ap;
+       const char *arg;
+
+       va_start(ap, hook_name);
+       while ((arg = va_arg(ap, const char *)))
+               strvec_push(&opt.args, arg);
+       va_end(ap);
+
+       return run_hooks_opt(hook_name, &opt);
+}
diff --git a/hook.h b/hook.h
index 6aa36fc7ff9b07e9a97cbfccaa53b81452fe5b37..18d90aedf14066eeac2fc93144d02ea8a6545207 100644 (file)
--- a/hook.h
+++ b/hook.h
@@ -1,5 +1,37 @@
 #ifndef HOOK_H
 #define HOOK_H
+#include "strvec.h"
+
+struct run_hooks_opt
+{
+       /* Environment vars to be set for each hook */
+       struct strvec env;
+
+       /* Args to be passed to each hook */
+       struct strvec args;
+
+       /* Emit an error if the hook is missing */
+       unsigned int error_if_missing:1;
+
+       /**
+        * An optional initial working directory for the hook,
+        * translates to "struct child_process"'s "dir" member.
+        */
+       const char *dir;
+};
+
+#define RUN_HOOKS_OPT_INIT { \
+       .env = STRVEC_INIT, \
+       .args = STRVEC_INIT, \
+}
+
+struct hook_cb_data {
+       /* rc reflects the cumulative failure state */
+       int rc;
+       const char *hook_name;
+       const char *hook_path;
+       struct run_hooks_opt *options;
+};
 
 /*
  * Returns the path to the hook file, or NULL if the hook is missing
@@ -13,4 +45,29 @@ const char *find_hook(const char *name);
  */
 int hook_exists(const char *hookname);
 
+/**
+ * Takes a `hook_name`, resolves it to a path with find_hook(), and
+ * runs the hook for you with the options specified in "struct
+ * run_hooks opt". Will free memory associated with the "struct run_hooks_opt".
+ *
+ * Returns the status code of the run hook, or a negative value on
+ * error().
+ */
+int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);
+
+/**
+ * A wrapper for run_hooks_opt() which provides a dummy "struct
+ * run_hooks_opt" initialized with "RUN_HOOKS_OPT_INIT".
+ */
+int run_hooks(const char *hook_name);
+
+/**
+ * Like run_hooks(), a wrapper for run_hooks_opt().
+ *
+ * In addition to the wrapping behavior provided by run_hooks(), this
+ * wrapper takes a list of strings terminated by a NULL
+ * argument. These things will be used as positional arguments to the
+ * hook. This function behaves like the old run_hook_le() API.
+ */
+int run_hooks_l(const char *hook_name, ...);
 #endif
index 261657578c756c94f27ba03392ef82f4173d5460..a937cec59a6e8cd80f2272940ed0960a08d35125 100644 (file)
@@ -14,7 +14,7 @@
 
 struct ll_merge_driver;
 
-typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
+typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,
                           mmbuffer_t *result,
                           const char *path,
                           mmfile_t *orig, const char *orig_name,
@@ -49,7 +49,7 @@ void reset_merge_attributes(void)
 /*
  * Built-in low-levels
  */
-static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
+static enum ll_merge_result ll_binary_merge(const struct ll_merge_driver *drv_unused,
                           mmbuffer_t *result,
                           const char *path,
                           mmfile_t *orig, const char *orig_name,
@@ -58,6 +58,7 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
                           const struct ll_merge_options *opts,
                           int marker_size)
 {
+       enum ll_merge_result ret;
        mmfile_t *stolen;
        assert(opts);
 
@@ -68,16 +69,19 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
         */
        if (opts->virtual_ancestor) {
                stolen = orig;
+               ret = LL_MERGE_OK;
        } else {
                switch (opts->variant) {
                default:
-                       warning("Cannot merge binary files: %s (%s vs. %s)",
-                               path, name1, name2);
-                       /* fallthru */
+                       ret = LL_MERGE_BINARY_CONFLICT;
+                       stolen = src1;
+                       break;
                case XDL_MERGE_FAVOR_OURS:
+                       ret = LL_MERGE_OK;
                        stolen = src1;
                        break;
                case XDL_MERGE_FAVOR_THEIRS:
+                       ret = LL_MERGE_OK;
                        stolen = src2;
                        break;
                }
@@ -87,16 +91,10 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
        result->size = stolen->size;
        stolen->ptr = NULL;
 
-       /*
-        * With -Xtheirs or -Xours, we have cleanly merged;
-        * otherwise we got a conflict.
-        */
-       return opts->variant == XDL_MERGE_FAVOR_OURS ||
-              opts->variant == XDL_MERGE_FAVOR_THEIRS ?
-              0 : 1;
+       return ret;
 }
 
-static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
+static enum ll_merge_result ll_xdl_merge(const struct ll_merge_driver *drv_unused,
                        mmbuffer_t *result,
                        const char *path,
                        mmfile_t *orig, const char *orig_name,
@@ -105,7 +103,9 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
                        const struct ll_merge_options *opts,
                        int marker_size)
 {
+       enum ll_merge_result ret;
        xmparam_t xmp;
+       int status;
        assert(opts);
 
        if (orig->size > MAX_XDIFF_SIZE ||
@@ -133,10 +133,12 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
        xmp.ancestor = orig_name;
        xmp.file1 = name1;
        xmp.file2 = name2;
-       return xdl_merge(orig, src1, src2, &xmp, result);
+       status = xdl_merge(orig, src1, src2, &xmp, result);
+       ret = (status > 0) ? LL_MERGE_CONFLICT : status;
+       return ret;
 }
 
-static int ll_union_merge(const struct ll_merge_driver *drv_unused,
+static enum ll_merge_result ll_union_merge(const struct ll_merge_driver *drv_unused,
                          mmbuffer_t *result,
                          const char *path,
                          mmfile_t *orig, const char *orig_name,
@@ -178,7 +180,7 @@ static void create_temp(mmfile_t *src, char *path, size_t len)
 /*
  * User defined low-level merge driver support.
  */
-static int ll_ext_merge(const struct ll_merge_driver *fn,
+static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
                        mmbuffer_t *result,
                        const char *path,
                        mmfile_t *orig, const char *orig_name,
@@ -194,6 +196,7 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
        const char *args[] = { NULL, NULL };
        int status, fd, i;
        struct stat st;
+       enum ll_merge_result ret;
        assert(opts);
 
        sq_quote_buf(&path_sq, path);
@@ -236,7 +239,8 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
                unlink_or_warn(temp[i]);
        strbuf_release(&cmd);
        strbuf_release(&path_sq);
-       return status;
+       ret = (status > 0) ? LL_MERGE_CONFLICT : status;
+       return ret;
 }
 
 /*
@@ -362,7 +366,7 @@ static void normalize_file(mmfile_t *mm, const char *path, struct index_state *i
        }
 }
 
-int ll_merge(mmbuffer_t *result_buf,
+enum ll_merge_result ll_merge(mmbuffer_t *result_buf,
             const char *path,
             mmfile_t *ancestor, const char *ancestor_label,
             mmfile_t *ours, const char *our_label,
index aceb1b24132306a272182556ee15003a1f0d4e08..e4a20e81a3aea90b64e65b1f33a38efa595d9a2a 100644 (file)
@@ -82,13 +82,20 @@ struct ll_merge_options {
        long xdl_opts;
 };
 
+enum ll_merge_result {
+       LL_MERGE_ERROR = -1,
+       LL_MERGE_OK = 0,
+       LL_MERGE_CONFLICT,
+       LL_MERGE_BINARY_CONFLICT,
+};
+
 /**
  * Perform a three-way single-file merge in core.  This is a thin wrapper
  * around `xdl_merge` that takes the path and any merge backend specified in
  * `.gitattributes` or `.git/info/attributes` into account.
  * Returns 0 for a clean merge.
  */
-int ll_merge(mmbuffer_t *result_buf,
+enum ll_merge_result ll_merge(mmbuffer_t *result_buf,
             const char *path,
             mmfile_t *ancestor, const char *ancestor_label,
             mmfile_t *ours, const char *our_label,
index d3e7a40b648c7dc6eb30880ac3e04908342a8b09..25165e2a915e0394cbfc2347d5f3eca83842beff 100644 (file)
@@ -1,12 +1,15 @@
 #include "cache.h"
+#include "commit-reach.h"
 #include "config.h"
 #include "diff.h"
 #include "object-store.h"
 #include "repository.h"
+#include "tmp-objdir.h"
 #include "commit.h"
 #include "tag.h"
 #include "graph.h"
 #include "log-tree.h"
+#include "merge-ort.h"
 #include "reflog-walk.h"
 #include "refs.h"
 #include "string-list.h"
@@ -16,6 +19,7 @@
 #include "line-log.h"
 #include "help.h"
 #include "range-diff.h"
+#include "strmap.h"
 
 static struct decoration name_decoration = { "object names" };
 static int decoration_loaded;
@@ -849,7 +853,7 @@ int log_tree_diff_flush(struct rev_info *opt)
        opt->shown_dashes = 0;
        diffcore_std(&opt->diffopt);
 
-       if (diff_queue_is_empty()) {
+       if (diff_queue_is_empty(&opt->diffopt)) {
                int saved_fmt = opt->diffopt.output_format;
                opt->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
                diff_flush(&opt->diffopt);
@@ -904,6 +908,106 @@ static int do_diff_combined(struct rev_info *opt, struct commit *commit)
        return !opt->loginfo;
 }
 
+static void setup_additional_headers(struct diff_options *o,
+                                    struct strmap *all_headers)
+{
+       struct hashmap_iter iter;
+       struct strmap_entry *entry;
+
+       /*
+        * Make o->additional_path_headers contain the subset of all_headers
+        * that match o->pathspec.  If there aren't any that match o->pathspec,
+        * then make o->additional_path_headers be NULL.
+        */
+
+       if (!o->pathspec.nr) {
+               o->additional_path_headers = all_headers;
+               return;
+       }
+
+       o->additional_path_headers = xmalloc(sizeof(struct strmap));
+       strmap_init_with_options(o->additional_path_headers, NULL, 0);
+       strmap_for_each_entry(all_headers, &iter, entry) {
+               if (match_pathspec(the_repository->index, &o->pathspec,
+                                  entry->key, strlen(entry->key),
+                                  0 /* prefix */, NULL /* seen */,
+                                  0 /* is_dir */))
+                       strmap_put(o->additional_path_headers,
+                                  entry->key, entry->value);
+       }
+       if (!strmap_get_size(o->additional_path_headers)) {
+               strmap_clear(o->additional_path_headers, 0);
+               FREE_AND_NULL(o->additional_path_headers);
+       }
+}
+
+static void cleanup_additional_headers(struct diff_options *o)
+{
+       if (!o->pathspec.nr) {
+               o->additional_path_headers = NULL;
+               return;
+       }
+       if (!o->additional_path_headers)
+               return;
+
+       strmap_clear(o->additional_path_headers, 0);
+       FREE_AND_NULL(o->additional_path_headers);
+}
+
+static int do_remerge_diff(struct rev_info *opt,
+                          struct commit_list *parents,
+                          struct object_id *oid,
+                          struct commit *commit)
+{
+       struct merge_options o;
+       struct commit_list *bases;
+       struct merge_result res = {0};
+       struct pretty_print_context ctx = {0};
+       struct commit *parent1 = parents->item;
+       struct commit *parent2 = parents->next->item;
+       struct strbuf parent1_desc = STRBUF_INIT;
+       struct strbuf parent2_desc = STRBUF_INIT;
+
+       /* Setup merge options */
+       init_merge_options(&o, the_repository);
+       o.show_rename_progress = 0;
+       o.record_conflict_msgs_as_headers = 1;
+       o.msg_header_prefix = "remerge";
+
+       ctx.abbrev = DEFAULT_ABBREV;
+       format_commit_message(parent1, "%h (%s)", &parent1_desc, &ctx);
+       format_commit_message(parent2, "%h (%s)", &parent2_desc, &ctx);
+       o.branch1 = parent1_desc.buf;
+       o.branch2 = parent2_desc.buf;
+
+       /* Parse the relevant commits and get the merge bases */
+       parse_commit_or_die(parent1);
+       parse_commit_or_die(parent2);
+       bases = get_merge_bases(parent1, parent2);
+
+       /* Re-merge the parents */
+       merge_incore_recursive(&o, bases, parent1, parent2, &res);
+
+       /* Show the diff */
+       setup_additional_headers(&opt->diffopt, res.path_messages);
+       diff_tree_oid(&res.tree->object.oid, oid, "", &opt->diffopt);
+       log_tree_diff_flush(opt);
+
+       /* Cleanup */
+       cleanup_additional_headers(&opt->diffopt);
+       strbuf_release(&parent1_desc);
+       strbuf_release(&parent2_desc);
+       merge_finalize(&o, &res);
+
+       /* Clean up the contents of the temporary object directory */
+       if (opt->remerge_objdir)
+               tmp_objdir_discard_objects(opt->remerge_objdir);
+       else
+               BUG("did a remerge diff without remerge_objdir?!?");
+
+       return !opt->loginfo;
+}
+
 /*
  * Show the diff of a commit.
  *
@@ -938,6 +1042,18 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
        }
 
        if (is_merge) {
+               int octopus = (parents->next->next != NULL);
+
+               if (opt->remerge_diff) {
+                       if (octopus) {
+                               show_log(opt);
+                               fprintf(opt->diffopt.file,
+                                       "diff: warning: Skipping remerge-diff "
+                                       "for octopus merges.\n");
+                               return 1;
+                       }
+                       return do_remerge_diff(opt, parents, oid, commit);
+               }
                if (opt->combine_merges)
                        return do_diff_combined(opt, commit);
                if (opt->separate_merges) {
index ccdcad2e3d622fdd9491d3d1cbdc26f673bb41df..599d8e895f81216e0e0cb44696734cb2a157309b 100644 (file)
@@ -7,6 +7,26 @@
 
 #define BLOCK_GROWTH_SIZE (1024 * 1024 - sizeof(struct mp_block))
 
+/*
+ * The inner union is an approximation for C11's max_align_t, and the
+ * struct + offsetof computes _Alignof. This can all just be replaced
+ * with _Alignof(max_align_t) if/when C11 is part of the baseline.
+ * Note that _Alignof(X) need not be the same as sizeof(X); it's only
+ * required to be a (possibly trivial) factor. They are the same for
+ * most architectures, but m68k for example has only 2-byte alignment
+ * for its 4-byte and 8-byte types, so using sizeof would waste space.
+ *
+ * Add more types to the union if the current set is insufficient.
+ */
+struct git_max_alignment {
+       char unalign;
+       union {
+               uintmax_t max_align_uintmax;
+               void *max_align_pointer;
+       } aligned;
+};
+#define GIT_MAX_ALIGNMENT offsetof(struct git_max_alignment, aligned)
+
 /*
  * Allocate a new mp_block and insert it after the block specified in
  * `insert_after`. If `insert_after` is NULL, then insert block at the
@@ -69,9 +89,9 @@ void *mem_pool_alloc(struct mem_pool *pool, size_t len)
        struct mp_block *p = NULL;
        void *r;
 
-       /* round up to a 'uintmax_t' alignment */
-       if (len & (sizeof(uintmax_t) - 1))
-               len += sizeof(uintmax_t) - (len & (sizeof(uintmax_t) - 1));
+       /* round up to a 'GIT_MAX_ALIGNMENT' alignment */
+       if (len & (GIT_MAX_ALIGNMENT - 1))
+               len += GIT_MAX_ALIGNMENT - (len & (GIT_MAX_ALIGNMENT - 1));
 
        if (pool->mp_block &&
            pool->mp_block->end - pool->mp_block->next_free >= len)
index ee0a0e90c94682f4d990b298d27d734d01d90581..8138090f81cf726ee9834daa7edbfc5b542aec44 100644 (file)
@@ -36,7 +36,7 @@ static void *three_way_filemerge(struct index_state *istate,
                                 mmfile_t *their,
                                 unsigned long *size)
 {
-       int merge_status;
+       enum ll_merge_result merge_status;
        mmbuffer_t res;
 
        /*
@@ -50,6 +50,9 @@ static void *three_way_filemerge(struct index_state *istate,
                                istate, NULL);
        if (merge_status < 0)
                return NULL;
+       if (merge_status == LL_MERGE_BINARY_CONFLICT)
+               warning("Cannot merge binary files: %s (%s vs. %s)",
+                       path, ".our", ".their");
 
        *size = res.size;
        return res.ptr;
index c319797021938a9df635c8db146ac6555331c7db..d85b1cd99e9a4c9527cca8ef684d4b8aed0f6a07 100644 (file)
@@ -634,17 +634,51 @@ static void path_msg(struct merge_options *opt,
                     const char *fmt, ...)
 {
        va_list ap;
-       struct strbuf *sb = strmap_get(&opt->priv->output, path);
+       struct strbuf *sb, *dest;
+       struct strbuf tmp = STRBUF_INIT;
+
+       if (opt->record_conflict_msgs_as_headers && omittable_hint)
+               return; /* Do not record mere hints in headers */
+       if (opt->record_conflict_msgs_as_headers && opt->priv->call_depth)
+               return; /* Do not record inner merge issues in headers */
+       sb = strmap_get(&opt->priv->output, path);
        if (!sb) {
                sb = xmalloc(sizeof(*sb));
                strbuf_init(sb, 0);
                strmap_put(&opt->priv->output, path, sb);
        }
 
+       dest = (opt->record_conflict_msgs_as_headers ? &tmp : sb);
+
        va_start(ap, fmt);
-       strbuf_vaddf(sb, fmt, ap);
+       strbuf_vaddf(dest, fmt, ap);
        va_end(ap);
 
+       if (opt->record_conflict_msgs_as_headers) {
+               int i_sb = 0, i_tmp = 0;
+
+               /* Start with the specified prefix */
+               if (opt->msg_header_prefix)
+                       strbuf_addf(sb, "%s ", opt->msg_header_prefix);
+
+               /* Copy tmp to sb, adding spaces after newlines */
+               strbuf_grow(sb, sb->len + 2*tmp.len); /* more than sufficient */
+               for (; i_tmp < tmp.len; i_tmp++, i_sb++) {
+                       /* Copy next character from tmp to sb */
+                       sb->buf[sb->len + i_sb] = tmp.buf[i_tmp];
+
+                       /* If we copied a newline, add a space */
+                       if (tmp.buf[i_tmp] == '\n')
+                               sb->buf[++i_sb] = ' ';
+               }
+               /* Update length and ensure it's NUL-terminated */
+               sb->len += i_sb;
+               sb->buf[sb->len] = '\0';
+
+               strbuf_release(&tmp);
+       }
+
+       /* Add final newline character to sb */
        strbuf_addch(sb, '\n');
 }
 
@@ -1743,7 +1777,7 @@ static int merge_3way(struct merge_options *opt,
        mmfile_t orig, src1, src2;
        struct ll_merge_options ll_opts = {0};
        char *base, *name1, *name2;
-       int merge_status;
+       enum ll_merge_result merge_status;
 
        if (!opt->priv->attr_index.initialized)
                initialize_attr_index(opt);
@@ -1787,6 +1821,10 @@ static int merge_3way(struct merge_options *opt,
        merge_status = ll_merge(result_buf, path, &orig, base,
                                &src1, name1, &src2, name2,
                                &opt->priv->attr_index, &ll_opts);
+       if (merge_status == LL_MERGE_BINARY_CONFLICT)
+               path_msg(opt, path, 0,
+                        "warning: Cannot merge binary files: %s (%s vs. %s)",
+                        path, name1, name2);
 
        free(base);
        free(name1);
@@ -2416,7 +2454,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
                 */
                ci->path_conflict = 1;
                if (pair->status == 'A')
-                       path_msg(opt, new_path, 0,
+                       path_msg(opt, new_path, 1,
                                 _("CONFLICT (file location): %s added in %s "
                                   "inside a directory that was renamed in %s, "
                                   "suggesting it should perhaps be moved to "
@@ -2424,7 +2462,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
                                 old_path, branch_with_new_path,
                                 branch_with_dir_rename, new_path);
                else
-                       path_msg(opt, new_path, 0,
+                       path_msg(opt, new_path, 1,
                                 _("CONFLICT (file location): %s renamed to %s "
                                   "in %s, inside a directory that was renamed "
                                   "in %s, suggesting it should perhaps be "
@@ -3060,6 +3098,10 @@ static int detect_and_process_renames(struct merge_options *opt,
        trace2_region_enter("merge", "regular renames", opt->repo);
        detection_run |= detect_regular_renames(opt, MERGE_SIDE1);
        detection_run |= detect_regular_renames(opt, MERGE_SIDE2);
+       if (renames->needed_limit) {
+               renames->cached_pairs_valid_side = 0;
+               renames->redo_after_renames = 0;
+       }
        if (renames->redo_after_renames && detection_run) {
                int i, side;
                struct diff_filepair *p;
@@ -4255,6 +4297,9 @@ void merge_switch_to_result(struct merge_options *opt,
                struct string_list olist = STRING_LIST_INIT_NODUP;
                int i;
 
+               if (opt->record_conflict_msgs_as_headers)
+                       BUG("Either display conflict messages or record them as headers, not both");
+
                trace2_region_enter("merge", "display messages", opt->repo);
 
                /* Hack to pre-allocate olist to the desired size */
@@ -4356,6 +4401,9 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
        assert(opt->recursive_variant >= MERGE_VARIANT_NORMAL &&
               opt->recursive_variant <= MERGE_VARIANT_THEIRS);
 
+       if (opt->msg_header_prefix)
+               assert(opt->record_conflict_msgs_as_headers);
+
        /*
         * detect_renames, verbosity, buffer_output, and obuf are ignored
         * fields that were used by "recursive" rather than "ort" -- but
@@ -4556,6 +4604,7 @@ redo:
        trace2_region_leave("merge", "process_entries", opt->repo);
 
        /* Set return values */
+       result->path_messages = &opt->priv->output;
        result->tree = parse_tree_indirect(&working_tree_oid);
        /* existence of conflicted entries implies unclean */
        result->clean &= strmap_empty(&opt->priv->conflicted);
@@ -4575,7 +4624,7 @@ static void merge_ort_internal(struct merge_options *opt,
                               struct commit *h2,
                               struct merge_result *result)
 {
-       struct commit_list *iter;
+       struct commit *next;
        struct commit *merged_merge_bases;
        const char *ancestor_name;
        struct strbuf merge_base_abbrev = STRBUF_INIT;
@@ -4604,7 +4653,8 @@ static void merge_ort_internal(struct merge_options *opt,
                ancestor_name = merge_base_abbrev.buf;
        }
 
-       for (iter = merge_bases; iter; iter = iter->next) {
+       for (next = pop_commit(&merge_bases); next;
+            next = pop_commit(&merge_bases)) {
                const char *saved_b1, *saved_b2;
                struct commit *prev = merged_merge_bases;
 
@@ -4621,7 +4671,7 @@ static void merge_ort_internal(struct merge_options *opt,
                saved_b2 = opt->branch2;
                opt->branch1 = "Temporary merge branch 1";
                opt->branch2 = "Temporary merge branch 2";
-               merge_ort_internal(opt, NULL, prev, iter->item, result);
+               merge_ort_internal(opt, NULL, prev, next, result);
                if (result->clean < 0)
                        return;
                opt->branch1 = saved_b1;
@@ -4632,8 +4682,7 @@ static void merge_ort_internal(struct merge_options *opt,
                                                         result->tree,
                                                         "merged tree");
                commit_list_insert(prev, &merged_merge_bases->parents);
-               commit_list_insert(iter->item,
-                                  &merged_merge_bases->parents->next);
+               commit_list_insert(next, &merged_merge_bases->parents->next);
 
                clear_or_reinit_internal_opts(opt->priv, 1);
        }
index c011864ffeb115b43a26940a8f0b24d33c8e8182..fe599b8786891c74cbc5e911689f4a4d319152cd 100644 (file)
@@ -5,6 +5,7 @@
 
 struct commit;
 struct tree;
+struct strmap;
 
 struct merge_result {
        /*
@@ -23,6 +24,15 @@ struct merge_result {
         */
        struct tree *tree;
 
+       /*
+        * Special messages and conflict notices for various paths
+        *
+        * This is a map of pathnames to strbufs.  It contains various
+        * warning/conflict/notice messages (possibly multiple per path)
+        * that callers may want to use.
+        */
+       struct strmap *path_messages;
+
        /*
         * Additional metadata used by merge_switch_to_result() or future calls
         * to merge_incore_*().  Includes data needed to update the index (if
index d9457797dbb73bfed720ac9ca0b9bcf56575c62a..9ec1e6d043a2f85f61422138c97b379e1a5b70c9 100644 (file)
@@ -1044,7 +1044,7 @@ static int merge_3way(struct merge_options *opt,
        mmfile_t orig, src1, src2;
        struct ll_merge_options ll_opts = {0};
        char *base, *name1, *name2;
-       int merge_status;
+       enum ll_merge_result merge_status;
 
        ll_opts.renormalize = opt->renormalize;
        ll_opts.extra_marker_size = extra_marker_size;
@@ -1090,6 +1090,9 @@ static int merge_3way(struct merge_options *opt,
        merge_status = ll_merge(result_buf, a->path, &orig, base,
                                &src1, name1, &src2, name2,
                                opt->repo->index, &ll_opts);
+       if (merge_status == LL_MERGE_BINARY_CONFLICT)
+               warning("Cannot merge binary files: %s (%s vs. %s)",
+                       a->path, name1, name2);
 
        free(base);
        free(name1);
@@ -3711,6 +3714,10 @@ static int merge_start(struct merge_options *opt, struct tree *head)
 
        assert(opt->priv == NULL);
 
+       /* Not supported; option specific to merge-ort */
+       assert(!opt->record_conflict_msgs_as_headers);
+       assert(!opt->msg_header_prefix);
+
        /* Sanity check on repo state; index must match head */
        if (repo_index_has_changes(opt->repo, head, &sb)) {
                err(opt, _("Your local changes to the following files would be overwritten by merge:\n  %s"),
index 0795a1d3ec1809ea42fd11c1e3ad35467050d286..b88000e3c25277d07d20b7ba29755b9670cd28ff 100644 (file)
@@ -46,6 +46,8 @@ struct merge_options {
        /* miscellaneous control options */
        const char *subtree_shift;
        unsigned renormalize : 1;
+       unsigned record_conflict_msgs_as_headers : 1;
+       const char *msg_header_prefix;
 
        /* internal fields used by the implementation */
        struct merge_options_internal *priv;
diff --git a/midx.c b/midx.c
index 837b46b2af5fd766b981964ca27be427225b7591..6e6cb0eb0494f543f4e2ea2e4fd5521950776f09 100644 (file)
--- a/midx.c
+++ b/midx.c
@@ -33,6 +33,7 @@
 #define MIDX_CHUNKID_OIDLOOKUP 0x4f49444c /* "OIDL" */
 #define MIDX_CHUNKID_OBJECTOFFSETS 0x4f4f4646 /* "OOFF" */
 #define MIDX_CHUNKID_LARGEOFFSETS 0x4c4f4646 /* "LOFF" */
+#define MIDX_CHUNKID_REVINDEX 0x52494458 /* "RIDX" */
 #define MIDX_CHUNK_FANOUT_SIZE (sizeof(uint32_t) * 256)
 #define MIDX_CHUNK_OFFSET_WIDTH (2 * sizeof(uint32_t))
 #define MIDX_CHUNK_LARGE_OFFSET_WIDTH (sizeof(uint64_t))
@@ -161,6 +162,9 @@ struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local
 
        pair_chunk(cf, MIDX_CHUNKID_LARGEOFFSETS, &m->chunk_large_offsets);
 
+       if (git_env_bool("GIT_TEST_MIDX_READ_RIDX", 1))
+               pair_chunk(cf, MIDX_CHUNKID_REVINDEX, &m->chunk_revindex);
+
        m->num_objects = ntohl(m->chunk_oid_fanout[255]);
 
        CALLOC_ARRAY(m->pack_names, m->num_packs);
@@ -833,6 +837,18 @@ static int write_midx_large_offsets(struct hashfile *f,
        return 0;
 }
 
+static int write_midx_revindex(struct hashfile *f,
+                              void *data)
+{
+       struct write_midx_context *ctx = data;
+       uint32_t i;
+
+       for (i = 0; i < ctx->entries_nr; i++)
+               hashwrite_be32(f, ctx->pack_order[i]);
+
+       return 0;
+}
+
 struct midx_pack_order_data {
        uint32_t nr;
        uint32_t pack;
@@ -1403,16 +1419,21 @@ static int write_midx_internal(const char *object_dir,
                        (size_t)ctx.num_large_offsets * MIDX_CHUNK_LARGE_OFFSET_WIDTH,
                        write_midx_large_offsets);
 
+       if (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP)) {
+               ctx.pack_order = midx_pack_order(&ctx);
+               add_chunk(cf, MIDX_CHUNKID_REVINDEX,
+                         ctx.entries_nr * sizeof(uint32_t),
+                         write_midx_revindex);
+       }
+
        write_midx_header(f, get_num_chunks(cf), ctx.nr - dropped_packs);
        write_chunkfile(cf, &ctx);
 
        finalize_hashfile(f, midx_hash, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
        free_chunkfile(cf);
 
-       if (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP))
-               ctx.pack_order = midx_pack_order(&ctx);
-
-       if (flags & MIDX_WRITE_REV_INDEX)
+       if (flags & MIDX_WRITE_REV_INDEX &&
+           git_env_bool("GIT_TEST_MIDX_WRITE_REV", 0))
                write_midx_reverse_index(midx_name.buf, midx_hash, &ctx);
        if (flags & MIDX_WRITE_BITMAP) {
                if (write_midx_bitmap(midx_name.buf, midx_hash, &ctx,
diff --git a/midx.h b/midx.h
index b7d79a515c63a73422755737d789b0c2febe22a9..22e8e53288ec226f7673d8a0f277961d5bb1d856 100644 (file)
--- a/midx.h
+++ b/midx.h
@@ -36,6 +36,7 @@ struct multi_pack_index {
        const unsigned char *chunk_oid_lookup;
        const unsigned char *chunk_object_offsets;
        const unsigned char *chunk_large_offsets;
+       const unsigned char *chunk_revindex;
 
        const char **pack_names;
        struct packed_git **packs;
index b4a3a903e86f3fdc38064e5f16c63974d2a191e4..01d596920eaf69f94325ea3cd7678963b67b7101 100644 (file)
@@ -344,7 +344,7 @@ static int ll_merge_in_worktree(struct notes_merge_options *o,
 {
        mmbuffer_t result_buf;
        mmfile_t base, local, remote;
-       int status;
+       enum ll_merge_result status;
 
        read_mmblob(&base, &p->base);
        read_mmblob(&local, &p->local);
@@ -358,6 +358,9 @@ static int ll_merge_in_worktree(struct notes_merge_options *o,
        free(local.ptr);
        free(remote.ptr);
 
+       if (status == LL_MERGE_BINARY_CONFLICT)
+               warning("Cannot merge binary files: %s (%s vs. %s)",
+                       oid_to_hex(&p->obj), o->local_ref, o->remote_ref);
        if ((status < 0) || !result_buf.ptr)
                die("Failed to execute internal merge");
 
index fdff4601b2c70cc7e4585096534382ddc5024990..92862eeb1ac74f0a3eeaca91ffac9771331bbf22 100644 (file)
@@ -1795,13 +1795,13 @@ static enum get_oid_result get_oid_with_context_1(struct repository *repo,
        const char *cp;
        int only_to_die = flags & GET_OID_ONLY_TO_DIE;
 
-       if (only_to_die)
-               flags |= GET_OID_QUIETLY;
-
        memset(oc, 0, sizeof(*oc));
        oc->mode = S_IFINVALID;
        strbuf_init(&oc->symlink_path, 0);
        ret = get_oid_1(repo, name, namelen, oid, flags);
+       if (!ret && flags & GET_OID_REQUIRE_PATH)
+               die(_("<object>:<path> required, only <object> '%s' given"),
+                   name);
        if (!ret)
                return ret;
        /*
@@ -1932,7 +1932,7 @@ void maybe_die_on_misspelt_object_name(struct repository *r,
 {
        struct object_context oc;
        struct object_id oid;
-       get_oid_with_context_1(r, name, GET_OID_ONLY_TO_DIE,
+       get_oid_with_context_1(r, name, GET_OID_ONLY_TO_DIE | GET_OID_QUIETLY,
                               prefix, &oid, &oc);
 }
 
index f772d3cb7f7c181a9b703a9535f9ec55dfd60513..9c666cdb8bd9203f0749b54b6c012ba33896341e 100644 (file)
@@ -358,7 +358,9 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git,
 cleanup:
        munmap(bitmap_git->map, bitmap_git->map_size);
        bitmap_git->map_size = 0;
+       bitmap_git->map_pos = 0;
        bitmap_git->map = NULL;
+       bitmap_git->midx = NULL;
        return -1;
 }
 
@@ -405,6 +407,8 @@ static int open_pack_bitmap_1(struct bitmap_index *bitmap_git, struct packed_git
                munmap(bitmap_git->map, bitmap_git->map_size);
                bitmap_git->map = NULL;
                bitmap_git->map_size = 0;
+               bitmap_git->map_pos = 0;
+               bitmap_git->pack = NULL;
                return -1;
        }
 
index 70d0fbafcbf954e33b58e6fb79dd173282f59eea..08dc1601679f1011f7ff38b7a721dbb4dd116c85 100644 (file)
@@ -298,9 +298,29 @@ int load_midx_revindex(struct multi_pack_index *m)
 {
        struct strbuf revindex_name = STRBUF_INIT;
        int ret;
+
        if (m->revindex_data)
                return 0;
 
+       if (m->chunk_revindex) {
+               /*
+                * If the MIDX `m` has a `RIDX` chunk, then use its contents for
+                * the reverse index instead of trying to load a separate `.rev`
+                * file.
+                *
+                * Note that we do *not* set `m->revindex_map` here, since we do
+                * not want to accidentally call munmap() in the middle of the
+                * MIDX.
+                */
+               trace2_data_string("load_midx_revindex", the_repository,
+                                  "source", "midx");
+               m->revindex_data = (const uint32_t *)m->chunk_revindex;
+               return 0;
+       }
+
+       trace2_data_string("load_midx_revindex", the_repository,
+                          "source", "rev");
+
        get_midx_rev_filename(&revindex_name, m);
 
        ret = load_revindex_from_disk(revindex_name.buf,
index a8283037be966596184862aa358bff6fd37194f3..2437ad3bcdd5b6ff06bf3f46fb00a1106958cca3 100644 (file)
@@ -1079,3 +1079,16 @@ void NORETURN usage_msg_opt(const char *msg,
        die_message("%s\n", msg); /* The extra \n is intentional */
        usage_with_options(usagestr, options);
 }
+
+void NORETURN usage_msg_optf(const char * const fmt,
+                            const char * const *usagestr,
+                            const struct option *options, ...)
+{
+       struct strbuf msg = STRBUF_INIT;
+       va_list ap;
+       va_start(ap, options);
+       strbuf_vaddf(&msg, fmt, ap);
+       va_end(ap);
+
+       usage_msg_opt(msg.buf, usagestr, options);
+}
index e22846d3b7be06fd2af2f81532633d6d773f0bab..659a4c28b2d2afa515a1efb0a75843b538fad498 100644 (file)
@@ -85,6 +85,11 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
  *   token to explain the kind of argument this option wants. Does not
  *   begin in capital letter, and does not end with a full stop.
  *   Should be wrapped by N_() for translation.
+ *   Is automatically enclosed in brackets when printed, unless it
+ *   contains any of the following characters: ()<>[]|
+ *   E.g. "name" is shown as "<name>" to indicate that a name value
+ *   needs to be supplied, not the literal string "name", but
+ *   "<start>,<end>" and "(this|that)" are printed verbatim.
  *
  * `help`::
  *   the short help associated to what the option does.
@@ -225,6 +230,16 @@ NORETURN void usage_msg_opt(const char *msg,
                            const char * const *usagestr,
                            const struct option *options);
 
+/**
+ * usage_msg_optf() is like usage_msg_opt() except that the first
+ * argument is a format string, and optional format arguments follow
+ * after the 3rd option.
+ */
+__attribute__((format (printf,1,4)))
+void NORETURN usage_msg_optf(const char *fmt,
+                            const char * const *usagestr,
+                            const struct option *options, ...);
+
 /*
  * Use these assertions for callbacks that expect to be called with NONEG and
  * NOARG respectively, and do not otherwise handle the "unset" and "arg"
index 090a7df63fc004ecff6fc3425c1fb13006fb499d..080cdc2a21d32da831524a088edf0635357c78df 100644 (file)
@@ -1686,6 +1686,16 @@ sub _setup_git_cmd_env {
 # by searching for it at proper places.
 sub _execv_git_cmd { exec('git', @_); }
 
+sub _is_sig {
+       my ($v, $n) = @_;
+
+       # We are avoiding a "use POSIX qw(SIGPIPE SIGABRT)" in the hot
+       # Git.pm codepath.
+       require POSIX;
+       no strict 'refs';
+       $v == *{"POSIX::$n"}->();
+}
+
 # Close pipe to a subprocess.
 sub _cmd_close {
        my $ctx = shift @_;
@@ -1698,9 +1708,16 @@ sub _cmd_close {
                } elsif ($? >> 8) {
                        # The caller should pepper this.
                        throw Git::Error::Command($ctx, $? >> 8);
+               } elsif ($? & 127 && _is_sig($? & 127, "SIGPIPE")) {
+                       # we might e.g. closed a live stream; the command
+                       # dying of SIGPIPE would drive us here.
+               } elsif ($? & 127 && _is_sig($? & 127, "SIGABRT")) {
+                       die sprintf('BUG?: got SIGABRT ($? = %d, $? & 127 = %d) when closing pipe',
+                                   $?, $? & 127);
+               } elsif ($? & 127) {
+                       die sprintf('got signal ($? = %d, $? & 127 = %d) when closing pipe',
+                                   $?, $? & 127);
                }
-               # else we might e.g. closed a live stream; the command
-               # dying of SIGPIPE would drive us here.
        }
 }
 
index cbe73f14e5e7efc63b20e80a2702fb5c0dea9a5d..79b9b99ebf7d65b6663014deaadea12a039db29d 100644 (file)
@@ -28,6 +28,7 @@
 #include "sparse-index.h"
 #include "csum-file.h"
 #include "promisor-remote.h"
+#include "hook.h"
 
 /* Mask for the name length in ce_flags in the on-disk index */
 
@@ -1339,9 +1340,6 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
        int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
        int new_only = option & ADD_CACHE_NEW_ONLY;
 
-       if (!(option & ADD_CACHE_KEEP_CACHE_TREE))
-               cache_tree_invalidate_path(istate, ce->name);
-
        /*
         * If this entry's path sorts after the last entry in the index,
         * we can avoid searching for it.
@@ -1352,6 +1350,13 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
        else
                pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce), EXPAND_SPARSE);
 
+       /*
+        * Cache tree path should be invalidated only after index_name_stage_pos,
+        * in case it expands a sparse index.
+        */
+       if (!(option & ADD_CACHE_KEEP_CACHE_TREE))
+               cache_tree_invalidate_path(istate, ce->name);
+
        /* existing match? Just replace it. */
        if (pos >= 0) {
                if (!new_only)
@@ -2775,7 +2780,7 @@ static int repo_verify_index(struct repository *repo)
        return verify_index_from(repo->index, repo->index_file);
 }
 
-static int has_racy_timestamp(struct index_state *istate)
+int has_racy_timestamp(struct index_state *istate)
 {
        int entries = istate->cache_nr;
        int i;
@@ -3009,6 +3014,9 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
            !is_null_oid(&istate->split_index->base_oid)) {
                struct strbuf sb = STRBUF_INIT;
 
+               if (istate->sparse_index)
+                       die(_("cannot write split index for a sparse index"));
+
                err = write_link_extension(&sb, istate) < 0 ||
                        write_index_ext_header(f, eoie_c, CACHE_EXT_LINK,
                                               sb.len) < 0;
@@ -3150,7 +3158,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
        else
                ret = close_lock_file_gently(lock);
 
-       run_hook_le(NULL, "post-index-change",
+       run_hooks_l("post-index-change",
                        istate->updated_workdir ? "1" : "0",
                        istate->updated_skipworktree ? "1" : "0", NULL);
        istate->updated_workdir = 0;
diff --git a/refs.c b/refs.c
index addb26293b4ffc1b0c997379acce5d13d70991c8..7017ae598041bb1eda4ef5f95a6880be7c54ff71 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -269,10 +269,9 @@ char *refs_resolve_refdup(struct ref_store *refs,
                          struct object_id *oid, int *flags)
 {
        const char *result;
-       int ignore_errno;
 
        result = refs_resolve_ref_unsafe(refs, refname, resolve_flags,
-                                        oid, flags, &ignore_errno);
+                                        oid, flags);
        return xstrdup_or_null(result);
 }
 
@@ -294,11 +293,10 @@ struct ref_filter {
 
 int read_ref_full(const char *refname, int resolve_flags, struct object_id *oid, int *flags)
 {
-       int ignore_errno;
        struct ref_store *refs = get_main_ref_store(the_repository);
 
        if (refs_resolve_ref_unsafe(refs, refname, resolve_flags,
-                                   oid, flags, &ignore_errno))
+                                   oid, flags))
                return 0;
        return -1;
 }
@@ -310,9 +308,8 @@ int read_ref(const char *refname, struct object_id *oid)
 
 int refs_ref_exists(struct ref_store *refs, const char *refname)
 {
-       int ignore_errno;
        return !!refs_resolve_ref_unsafe(refs, refname, RESOLVE_REF_READING,
-                                        NULL, NULL, &ignore_errno);
+                                        NULL, NULL);
 }
 
 int ref_exists(const char *refname)
@@ -656,15 +653,13 @@ int expand_ref(struct repository *repo, const char *str, int len,
                struct object_id *this_result;
                int flag;
                struct ref_store *refs = get_main_ref_store(repo);
-               int ignore_errno;
 
                this_result = refs_found ? &oid_from_ref : oid;
                strbuf_reset(&fullref);
                strbuf_addf(&fullref, *p, len, str);
                r = refs_resolve_ref_unsafe(refs, fullref.buf,
                                            RESOLVE_REF_READING,
-                                           this_result, &flag,
-                                           &ignore_errno);
+                                           this_result, &flag);
                if (r) {
                        if (!refs_found++)
                                *ref = xstrdup(r);
@@ -693,14 +688,12 @@ int repo_dwim_log(struct repository *r, const char *str, int len,
        for (p = ref_rev_parse_rules; *p; p++) {
                struct object_id hash;
                const char *ref, *it;
-               int ignore_errno;
 
                strbuf_reset(&path);
                strbuf_addf(&path, *p, len, str);
                ref = refs_resolve_ref_unsafe(refs, path.buf,
                                              RESOLVE_REF_READING,
-                                             oid ? &hash : NULL, NULL,
-                                             &ignore_errno);
+                                             oid ? &hash : NULL, NULL);
                if (!ref)
                        continue;
                if (refs_reflog_exists(refs, path.buf))
@@ -1390,10 +1383,9 @@ int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 {
        struct object_id oid;
        int flag;
-       int ignore_errno;
 
        if (refs_resolve_ref_unsafe(refs, "HEAD", RESOLVE_REF_READING,
-                                   &oid, &flag, &ignore_errno))
+                                   &oid, &flag))
                return fn("HEAD", &oid, flag, cb_data);
 
        return 0;
@@ -1682,15 +1674,13 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
                                    const char *refname,
                                    int resolve_flags,
                                    struct object_id *oid,
-                                   int *flags, int *failure_errno)
+                                   int *flags)
 {
        static struct strbuf sb_refname = STRBUF_INIT;
        struct object_id unused_oid;
        int unused_flags;
        int symref_count;
 
-       assert(failure_errno);
-
        if (!oid)
                oid = &unused_oid;
        if (!flags)
@@ -1700,10 +1690,8 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
 
        if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
                if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
-                   !refname_is_safe(refname)) {
-                       *failure_errno = EINVAL;
+                   !refname_is_safe(refname))
                        return NULL;
-               }
 
                /*
                 * dwim_ref() uses REF_ISBROKEN to distinguish between
@@ -1718,9 +1706,10 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
 
        for (symref_count = 0; symref_count < SYMREF_MAXDEPTH; symref_count++) {
                unsigned int read_flags = 0;
+               int failure_errno;
 
                if (refs_read_raw_ref(refs, refname, oid, &sb_refname,
-                                     &read_flags, failure_errno)) {
+                                     &read_flags, &failure_errno)) {
                        *flags |= read_flags;
 
                        /* In reading mode, refs must eventually resolve */
@@ -1732,9 +1721,9 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
                         * may show errors besides ENOENT if there are
                         * similarly-named refs.
                         */
-                       if (*failure_errno != ENOENT &&
-                           *failure_errno != EISDIR &&
-                           *failure_errno != ENOTDIR)
+                       if (failure_errno != ENOENT &&
+                           failure_errno != EISDIR &&
+                           failure_errno != ENOTDIR)
                                return NULL;
 
                        oidclr(oid);
@@ -1760,16 +1749,13 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
                }
                if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
                        if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
-                           !refname_is_safe(refname)) {
-                               *failure_errno = EINVAL;
+                           !refname_is_safe(refname))
                                return NULL;
-                       }
 
                        *flags |= REF_ISBROKEN | REF_BAD_NAME;
                }
        }
 
-       *failure_errno = ELOOP;
        return NULL;
 }
 
@@ -1784,10 +1770,8 @@ int refs_init_db(struct strbuf *err)
 const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
                               struct object_id *oid, int *flags)
 {
-       int ignore_errno;
-
        return refs_resolve_ref_unsafe(get_main_ref_store(the_repository), refname,
-                                      resolve_flags, oid, flags, &ignore_errno);
+                                      resolve_flags, oid, flags);
 }
 
 int resolve_gitlink_ref(const char *submodule, const char *refname,
@@ -1795,15 +1779,14 @@ int resolve_gitlink_ref(const char *submodule, const char *refname,
 {
        struct ref_store *refs;
        int flags;
-       int ignore_errno;
 
        refs = get_submodule_ref_store(submodule);
 
        if (!refs)
                return -1;
 
-       if (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags,
-                                    &ignore_errno) || is_null_oid(oid))
+       if (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags) ||
+           is_null_oid(oid))
                return -1;
        return 0;
 }
diff --git a/refs.h b/refs.h
index 8f91a7f9ff27ec0909c32d14122bf0f1a5c49573..cd2d0c1ac09001b904070d3938d317c157c4d4b6 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -58,11 +58,6 @@ struct worktree;
  * resolved. The function returns NULL for such ref names.
  * Caps and underscores refers to the special refs, such as HEAD,
  * FETCH_HEAD and friends, that all live outside of the refs/ directory.
- *
- * Callers should not inspect "errno" on failure, but rather pass in a
- * "failure_errno" parameter, on failure the "errno" will indicate the
- * type of failure encountered, but not necessarily one that came from
- * a syscall. We might have faked it up.
  */
 #define RESOLVE_REF_READING 0x01
 #define RESOLVE_REF_NO_RECURSE 0x02
@@ -72,7 +67,7 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
                                    const char *refname,
                                    int resolve_flags,
                                    struct object_id *oid,
-                                   int *flags, int *failure_errno);
+                                   int *flags);
 
 const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
                               struct object_id *oid, int *flags);
index 43a3b882d7c50c231ae714f79b4a4fedfa7e1c9d..a40267b3ae9c358ba89a19c4e28e8f7fd7eb05a7 100644 (file)
@@ -277,11 +277,10 @@ static void loose_fill_ref_dir(struct ref_store *ref_store,
                                         create_dir_entry(dir->cache, refname.buf,
                                                          refname.len));
                } else {
-                       int ignore_errno;
                        if (!refs_resolve_ref_unsafe(&refs->base,
                                                     refname.buf,
                                                     RESOLVE_REF_READING,
-                                                    &oid, &flag, &ignore_errno)) {
+                                                    &oid, &flag)) {
                                oidclr(&oid);
                                flag |= REF_ISBROKEN;
                        } else if (is_null_oid(&oid)) {
@@ -1006,7 +1005,6 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
 {
        struct strbuf ref_file = STRBUF_INIT;
        struct ref_lock *lock;
-       int ignore_errno;
 
        files_assert_main_repository(refs, "lock_ref_oid_basic");
        assert(err);
@@ -1034,7 +1032,7 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
        }
 
        if (!refs_resolve_ref_unsafe(&refs->base, lock->ref_name, 0,
-                                    &lock->old_oid, NULL, &ignore_errno))
+                                    &lock->old_oid, NULL))
                oidclr(&lock->old_oid);
        goto out;
 
@@ -1399,7 +1397,6 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
        struct strbuf tmp_renamed_log = STRBUF_INIT;
        int log, ret;
        struct strbuf err = STRBUF_INIT;
-       int ignore_errno;
 
        files_reflog_path(refs, &sb_oldref, oldrefname);
        files_reflog_path(refs, &sb_newref, newrefname);
@@ -1413,7 +1410,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
 
        if (!refs_resolve_ref_unsafe(&refs->base, oldrefname,
                                     RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
-                                    &orig_oid, &flag, &ignore_errno)) {
+                                    &orig_oid, &flag)) {
                ret = error("refname %s not found", oldrefname);
                goto out;
        }
@@ -1459,7 +1456,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
         */
        if (!copy && refs_resolve_ref_unsafe(&refs->base, newrefname,
                                             RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
-                                            NULL, NULL, &ignore_errno) &&
+                                            NULL, NULL) &&
            refs_delete_ref(&refs->base, NULL, newrefname,
                            NULL, REF_NO_DEREF)) {
                if (errno == EISDIR) {
@@ -1828,12 +1825,10 @@ static int commit_ref_update(struct files_ref_store *refs,
                 */
                int head_flag;
                const char *head_ref;
-               int ignore_errno;
 
                head_ref = refs_resolve_ref_unsafe(&refs->base, "HEAD",
                                                   RESOLVE_REF_READING,
-                                                  NULL, &head_flag,
-                                                  &ignore_errno);
+                                                  NULL, &head_flag);
                if (head_ref && (head_flag & REF_ISSYMREF) &&
                    !strcmp(head_ref, lock->ref_name)) {
                        struct strbuf log_err = STRBUF_INIT;
@@ -1877,12 +1872,10 @@ static void update_symref_reflog(struct files_ref_store *refs,
 {
        struct strbuf err = STRBUF_INIT;
        struct object_id new_oid;
-       int ignore_errno;
 
        if (logmsg &&
            refs_resolve_ref_unsafe(&refs->base, target,
-                                   RESOLVE_REF_READING, &new_oid, NULL,
-                                   &ignore_errno) &&
+                                   RESOLVE_REF_READING, &new_oid, NULL) &&
            files_log_ref_write(refs, refname, &lock->old_oid,
                                &new_oid, logmsg, 0, &err)) {
                error("%s", err.buf);
@@ -2156,7 +2149,6 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator)
                (struct files_reflog_iterator *)ref_iterator;
        struct dir_iterator *diter = iter->dir_iterator;
        int ok;
-       int ignore_errno;
 
        while ((ok = dir_iterator_advance(diter)) == ITER_OK) {
                int flags;
@@ -2170,8 +2162,7 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator)
 
                if (!refs_resolve_ref_unsafe(iter->ref_store,
                                             diter->relative_path, 0,
-                                            &iter->oid, &flags,
-                                            &ignore_errno)) {
+                                            &iter->oid, &flags)) {
                        error("bad ref for %s", diter->path.buf);
                        continue;
                }
@@ -2515,11 +2506,9 @@ static int lock_ref_for_update(struct files_ref_store *refs,
                         * the transaction, so we have to read it here
                         * to record and possibly check old_oid:
                         */
-                       int ignore_errno;
                        if (!refs_resolve_ref_unsafe(&refs->base,
                                                     referent.buf, 0,
-                                                    &lock->old_oid, NULL,
-                                                    &ignore_errno)) {
+                                                    &lock->old_oid, NULL)) {
                                if (update->flags & REF_HAVE_OLD) {
                                        strbuf_addf(err, "cannot lock ref '%s': "
                                                    "error reading reference",
@@ -3208,14 +3197,12 @@ static int files_reflog_expire(struct ref_store *ref_store,
 
                if ((expire_flags & EXPIRE_REFLOGS_UPDATE_REF) &&
                    !is_null_oid(&cb.last_kept_oid)) {
-                       int ignore_errno;
                        int type;
                        const char *ref;
 
                        ref = refs_resolve_ref_unsafe(&refs->base, refname,
                                                      RESOLVE_REF_NO_RECURSE,
-                                                     NULL, &type,
-                                                     &ignore_errno);
+                                                     NULL, &type);
                        update = !!(ref && !(type & REF_ISSYMREF));
                }
 
index 855e3f5c9472d04e1a9ce00da72c2316523f7ff1..2170748c5e9bb413f6a8ed2cef130686f47ecb58 100644 (file)
@@ -188,13 +188,16 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
        uint32_t full_block_size = table_block_size;
        uint8_t typ = block->data[header_off];
        uint32_t sz = get_be24(block->data + header_off + 1);
-
+       int err = 0;
        uint16_t restart_count = 0;
        uint32_t restart_start = 0;
        uint8_t *restart_bytes = NULL;
+       uint8_t *uncompressed = NULL;
 
-       if (!reftable_is_block_type(typ))
-               return REFTABLE_FORMAT_ERROR;
+       if (!reftable_is_block_type(typ)) {
+               err =  REFTABLE_FORMAT_ERROR;
+               goto done;
+       }
 
        if (typ == BLOCK_TYPE_LOG) {
                int block_header_skip = 4 + header_off;
@@ -203,7 +206,7 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
                uLongf src_len = block->len - block_header_skip;
                /* Log blocks specify the *uncompressed* size in their header.
                 */
-               uint8_t *uncompressed = reftable_malloc(sz);
+               uncompressed = reftable_malloc(sz);
 
                /* Copy over the block header verbatim. It's not compressed. */
                memcpy(uncompressed, block->data, block_header_skip);
@@ -212,16 +215,19 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
                if (Z_OK !=
                    uncompress2(uncompressed + block_header_skip, &dst_len,
                                block->data + block_header_skip, &src_len)) {
-                       reftable_free(uncompressed);
-                       return REFTABLE_ZLIB_ERROR;
+                       err = REFTABLE_ZLIB_ERROR;
+                       goto done;
                }
 
-               if (dst_len + block_header_skip != sz)
-                       return REFTABLE_FORMAT_ERROR;
+               if (dst_len + block_header_skip != sz) {
+                       err = REFTABLE_FORMAT_ERROR;
+                       goto done;
+               }
 
                /* We're done with the input data. */
                reftable_block_done(block);
                block->data = uncompressed;
+               uncompressed = NULL;
                block->len = sz;
                block->source = malloc_block_source();
                full_block_size = src_len + block_header_skip;
@@ -251,7 +257,9 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
        br->restart_count = restart_count;
        br->restart_bytes = restart_bytes;
 
-       return 0;
+done:
+       reftable_free(uncompressed);
+       return err;
 }
 
 static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
@@ -413,7 +421,7 @@ int block_reader_seek(struct block_reader *br, struct block_iter *it,
 done:
        strbuf_release(&key);
        strbuf_release(&next.last_key);
-       reftable_record_destroy(&rec);
+       reftable_record_release(&rec);
 
        return err;
 }
index 4b3ea262dcbf2d2513a894ba9ec388c0d050bdd0..fa2ee092ec0bf52546cfbf5f4d3158656e553629 100644 (file)
@@ -26,8 +26,9 @@ static void test_block_read_write(void)
        struct block_writer bw = {
                .last_key = STRBUF_INIT,
        };
-       struct reftable_ref_record ref = { NULL };
-       struct reftable_record rec = { NULL };
+       struct reftable_record rec = {
+               .type = BLOCK_TYPE_REF,
+       };
        int i = 0;
        int n;
        struct block_reader br = { 0 };
@@ -40,7 +41,6 @@ static void test_block_read_write(void)
        block.source = malloc_block_source();
        block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
                          header_off, hash_size(GIT_SHA1_FORMAT_ID));
-       reftable_record_from_ref(&rec, &ref);
 
        for (i = 0; i < N; i++) {
                char name[100];
@@ -48,14 +48,14 @@ static void test_block_read_write(void)
                snprintf(name, sizeof(name), "branch%02d", i);
                memset(hash, i, sizeof(hash));
 
-               ref.refname = name;
-               ref.value_type = REFTABLE_REF_VAL1;
-               ref.value.val1 = hash;
+               rec.u.ref.refname = name;
+               rec.u.ref.value_type = REFTABLE_REF_VAL1;
+               rec.u.ref.value.val1 = hash;
 
                names[i] = xstrdup(name);
                n = block_writer_add(&bw, &rec);
-               ref.refname = NULL;
-               ref.value_type = REFTABLE_REF_DELETION;
+               rec.u.ref.refname = NULL;
+               rec.u.ref.value_type = REFTABLE_REF_DELETION;
                EXPECT(n == 0);
        }
 
@@ -74,7 +74,7 @@ static void test_block_read_write(void)
                if (r > 0) {
                        break;
                }
-               EXPECT_STREQ(names[j], ref.refname);
+               EXPECT_STREQ(names[j], rec.u.ref.refname);
                j++;
        }
 
@@ -92,7 +92,7 @@ static void test_block_read_write(void)
                n = block_iter_next(&it, &rec);
                EXPECT(n == 0);
 
-               EXPECT_STREQ(names[i], ref.refname);
+               EXPECT_STREQ(names[i], rec.u.ref.refname);
 
                want.len--;
                n = block_reader_seek(&br, &it, &want);
@@ -100,7 +100,7 @@ static void test_block_read_write(void)
 
                n = block_iter_next(&it, &rec);
                EXPECT(n == 0);
-               EXPECT_STREQ(names[10 * (i / 10)], ref.refname);
+               EXPECT_STREQ(names[10 * (i / 10)], rec.u.ref.refname);
 
                block_iter_close(&it);
        }
index 0044eecd9aa39afa476dcfb8e6eeaaa89fb08b86..2605371c28d86f638dcd5079466e038e3eb7a38a 100644 (file)
@@ -134,8 +134,10 @@ int reftable_block_source_from_file(struct reftable_block_source *bs,
        }
 
        err = fstat(fd, &st);
-       if (err < 0)
-               return -1;
+       if (err < 0) {
+               close(fd);
+               return REFTABLE_IO_ERROR;
+       }
 
        p = reftable_calloc(sizeof(struct file_block_source));
        p->size = st.st_size;
index 7a8a738d860982c57398dad589d11bd233b9266c..b27d152e89ab65d368fc0a0a189862ed176318d3 100644 (file)
@@ -7,6 +7,7 @@ https://developers.google.com/open-source/licenses/bsd
 */
 
 #include "basics.h"
+#include "constants.h"
 #include "record.h"
 #include "generic.h"
 #include "reftable-iterator.h"
@@ -15,23 +16,21 @@ https://developers.google.com/open-source/licenses/bsd
 int reftable_table_seek_ref(struct reftable_table *tab,
                            struct reftable_iterator *it, const char *name)
 {
-       struct reftable_ref_record ref = {
-               .refname = (char *)name,
-       };
-       struct reftable_record rec = { NULL };
-       reftable_record_from_ref(&rec, &ref);
+       struct reftable_record rec = { .type = BLOCK_TYPE_REF,
+                                      .u.ref = {
+                                              .refname = (char *)name,
+                                      } };
        return tab->ops->seek_record(tab->table_arg, it, &rec);
 }
 
 int reftable_table_seek_log(struct reftable_table *tab,
                            struct reftable_iterator *it, const char *name)
 {
-       struct reftable_log_record log = {
-               .refname = (char *)name,
-               .update_index = ~((uint64_t)0),
-       };
-       struct reftable_record rec = { NULL };
-       reftable_record_from_log(&rec, &log);
+       struct reftable_record rec = { .type = BLOCK_TYPE_LOG,
+                                      .u.log = {
+                                              .refname = (char *)name,
+                                              .update_index = ~((uint64_t)0),
+                                      } };
        return tab->ops->seek_record(tab->table_arg, it, &rec);
 }
 
@@ -129,17 +128,25 @@ void reftable_iterator_destroy(struct reftable_iterator *it)
 int reftable_iterator_next_ref(struct reftable_iterator *it,
                               struct reftable_ref_record *ref)
 {
-       struct reftable_record rec = { NULL };
-       reftable_record_from_ref(&rec, ref);
-       return iterator_next(it, &rec);
+       struct reftable_record rec = {
+               .type = BLOCK_TYPE_REF,
+               .u.ref = *ref,
+       };
+       int err = iterator_next(it, &rec);
+       *ref = rec.u.ref;
+       return err;
 }
 
 int reftable_iterator_next_log(struct reftable_iterator *it,
                               struct reftable_log_record *log)
 {
-       struct reftable_record rec = { NULL };
-       reftable_record_from_log(&rec, log);
-       return iterator_next(it, &rec);
+       struct reftable_record rec = {
+               .type = BLOCK_TYPE_LOG,
+               .u.log = *log,
+       };
+       int err = iterator_next(it, &rec);
+       *log = rec.u.log;
+       return err;
 }
 
 int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)
index 93d04f735b852c8d1444623c24048017a956847a..a8d174c040658edda3d2c45a9df2f692aef41c9d 100644 (file)
@@ -32,7 +32,7 @@ static int filtering_ref_iterator_next(void *iter_arg,
                                       struct reftable_record *rec)
 {
        struct filtering_ref_iterator *fri = iter_arg;
-       struct reftable_ref_record *ref = rec->data;
+       struct reftable_ref_record *ref = &rec->u.ref;
        int err = 0;
        while (1) {
                err = reftable_iterator_next_ref(&fri->it, ref);
@@ -127,7 +127,7 @@ static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)
 static int indexed_table_ref_iter_next(void *p, struct reftable_record *rec)
 {
        struct indexed_table_ref_iter *it = p;
-       struct reftable_ref_record *ref = rec->data;
+       struct reftable_ref_record *ref = &rec->u.ref;
 
        while (1) {
                int err = block_iter_next(&it->cur, rec);
index e5b53da6db3f060e67f7e712b6098b5f42d3e5a5..2a6efa110d5dfaf0f4971a69f7b9e81a9fe04d68 100644 (file)
@@ -30,7 +30,7 @@ static int merged_iter_init(struct merged_iter *mi)
 
                if (err > 0) {
                        reftable_iterator_destroy(&mi->stack[i]);
-                       reftable_record_destroy(&rec);
+                       reftable_record_release(&rec);
                } else {
                        struct pq_entry e = {
                                .rec = rec,
@@ -57,18 +57,17 @@ static void merged_iter_close(void *p)
 static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi,
                                               size_t idx)
 {
-       struct reftable_record rec = reftable_new_record(mi->typ);
        struct pq_entry e = {
-               .rec = rec,
+               .rec = reftable_new_record(mi->typ),
                .index = idx,
        };
-       int err = iterator_next(&mi->stack[idx], &rec);
+       int err = iterator_next(&mi->stack[idx], &e.rec);
        if (err < 0)
                return err;
 
        if (err > 0) {
                reftable_iterator_destroy(&mi->stack[idx]);
-               reftable_record_destroy(&rec);
+               reftable_record_release(&e.rec);
                return 0;
        }
 
@@ -126,11 +125,11 @@ static int merged_iter_next_entry(struct merged_iter *mi,
                if (err < 0) {
                        return err;
                }
-               reftable_record_destroy(&top.rec);
+               reftable_record_release(&top.rec);
        }
 
        reftable_record_copy_from(rec, &entry.rec, hash_size(mi->hash_id));
-       reftable_record_destroy(&entry.rec);
+       reftable_record_release(&entry.rec);
        strbuf_release(&entry_key);
        return 0;
 }
@@ -290,11 +289,12 @@ int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,
                                   struct reftable_iterator *it,
                                   const char *name)
 {
-       struct reftable_ref_record ref = {
-               .refname = (char *)name,
+       struct reftable_record rec = {
+               .type = BLOCK_TYPE_REF,
+               .u.ref = {
+                       .refname = (char *)name,
+               },
        };
-       struct reftable_record rec = { NULL };
-       reftable_record_from_ref(&rec, &ref);
        return merged_table_seek_record(mt, it, &rec);
 }
 
@@ -302,12 +302,11 @@ int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,
                                      struct reftable_iterator *it,
                                      const char *name, uint64_t update_index)
 {
-       struct reftable_log_record log = {
-               .refname = (char *)name,
-               .update_index = update_index,
-       };
-       struct reftable_record rec = { NULL };
-       reftable_record_from_log(&rec, &log);
+       struct reftable_record rec = { .type = BLOCK_TYPE_LOG,
+                                      .u.log = {
+                                              .refname = (char *)name,
+                                              .update_index = update_index,
+                                      } };
        return merged_table_seek_record(mt, it, &rec);
 }
 
index efc474017a22d7e65e0aaa9fc78ded3ffc5075de..96ca6dd37b3ac0b881781403806d30e52edff46a 100644 (file)
@@ -74,6 +74,7 @@ struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)
 void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)
 {
        int i = 0;
+
        if (pq->len == pq->cap) {
                pq->cap = 2 * pq->cap + 1;
                pq->heap = reftable_realloc(pq->heap,
@@ -98,7 +99,7 @@ void merged_iter_pqueue_release(struct merged_iter_pqueue *pq)
 {
        int i = 0;
        for (i = 0; i < pq->len; i++) {
-               reftable_record_destroy(&pq->heap[i].rec);
+               reftable_record_release(&pq->heap[i].rec);
        }
        FREE_AND_NULL(pq->heap);
        pq->len = pq->cap = 0;
index c9bb05e37b717eab1527f5a1ac6b39fbec687207..7de5e886f35236eff6b7e033813ea2f7b403b9c8 100644 (file)
@@ -31,7 +31,7 @@ static void test_pq(void)
        int N = ARRAY_SIZE(names) - 1;
 
        struct merged_iter_pqueue pq = { NULL };
-       const char *last = NULL;
+       char *last = NULL;
 
        int i = 0;
        for (i = 0; i < N; i++) {
@@ -42,12 +42,10 @@ static void test_pq(void)
 
        i = 1;
        do {
-               struct reftable_record rec =
-                       reftable_new_record(BLOCK_TYPE_REF);
-               struct pq_entry e = { 0 };
-
-               reftable_record_as_ref(&rec)->refname = names[i];
-               e.rec = rec;
+               struct pq_entry e = { .rec = { .type = BLOCK_TYPE_REF,
+                                              .u.ref = {
+                                                      .refname = names[i],
+                                              } } };
                merged_iter_pqueue_add(&pq, e);
                merged_iter_pqueue_check(pq);
                i = (i * 7) % N;
@@ -55,19 +53,18 @@ static void test_pq(void)
 
        while (!merged_iter_pqueue_is_empty(pq)) {
                struct pq_entry e = merged_iter_pqueue_remove(&pq);
-               struct reftable_ref_record *ref =
-                       reftable_record_as_ref(&e.rec);
-
+               struct reftable_record *rec = &e.rec;
                merged_iter_pqueue_check(pq);
 
+               EXPECT(reftable_record_type(rec) == BLOCK_TYPE_REF);
                if (last) {
-                       EXPECT(strcmp(last, ref->refname) < 0);
+                       EXPECT(strcmp(last, rec->u.ref.refname) < 0);
                }
-               last = ref->refname;
-               ref->refname = NULL;
-               reftable_free(ref);
+               // this is names[i], so don't dealloc.
+               last = rec->u.ref.refname;
+               rec->u.ref.refname = NULL;
+               reftable_record_release(rec);
        }
-
        for (i = 0; i < N; i++) {
                reftable_free(names[i]);
        }
index 006709a645aea2ddbc914673710e66fa2343d569..00906e7a2de218085eaec3c8488365cb2da0a0a8 100644 (file)
@@ -239,8 +239,7 @@ static int table_iter_next_in_block(struct table_iter *ti,
 {
        int res = block_iter_next(&ti->bi, rec);
        if (res == 0 && reftable_record_type(rec) == BLOCK_TYPE_REF) {
-               ((struct reftable_ref_record *)rec->data)->update_index +=
-                       ti->r->min_update_index;
+               rec->u.ref.update_index += ti->r->min_update_index;
        }
 
        return res;
@@ -290,28 +289,33 @@ int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br,
 
        err = reader_get_block(r, &block, next_off, guess_block_size);
        if (err < 0)
-               return err;
+               goto done;
 
        block_size = extract_block_size(block.data, &block_typ, next_off,
                                        r->version);
-       if (block_size < 0)
-               return block_size;
-
+       if (block_size < 0) {
+               err = block_size;
+               goto done;
+       }
        if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {
-               reftable_block_done(&block);
-               return 1;
+               err = 1;
+               goto done;
        }
 
        if (block_size > guess_block_size) {
                reftable_block_done(&block);
                err = reader_get_block(r, &block, next_off, block_size);
                if (err < 0) {
-                       return err;
+                       goto done;
                }
        }
 
-       return block_reader_init(br, &block, header_off, r->block_size,
-                                hash_size(r->hash_id));
+       err = block_reader_init(br, &block, header_off, r->block_size,
+                               hash_size(r->hash_id));
+done:
+       reftable_block_done(&block);
+
+       return err;
 }
 
 static int table_iter_next_block(struct table_iter *dest,
@@ -475,7 +479,7 @@ static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,
 
 done:
        block_iter_close(&next.bi);
-       reftable_record_destroy(&rec);
+       reftable_record_release(&rec);
        strbuf_release(&want_key);
        strbuf_release(&got_key);
        return err;
@@ -485,34 +489,35 @@ static int reader_seek_indexed(struct reftable_reader *r,
                               struct reftable_iterator *it,
                               struct reftable_record *rec)
 {
-       struct reftable_index_record want_index = { .last_key = STRBUF_INIT };
-       struct reftable_record want_index_rec = { NULL };
-       struct reftable_index_record index_result = { .last_key = STRBUF_INIT };
-       struct reftable_record index_result_rec = { NULL };
+       struct reftable_record want_index = {
+               .type = BLOCK_TYPE_INDEX, .u.idx = { .last_key = STRBUF_INIT }
+       };
+       struct reftable_record index_result = {
+               .type = BLOCK_TYPE_INDEX,
+               .u.idx = { .last_key = STRBUF_INIT },
+       };
        struct table_iter index_iter = TABLE_ITER_INIT;
        struct table_iter next = TABLE_ITER_INIT;
        int err = 0;
 
-       reftable_record_key(rec, &want_index.last_key);
-       reftable_record_from_index(&want_index_rec, &want_index);
-       reftable_record_from_index(&index_result_rec, &index_result);
-
+       reftable_record_key(rec, &want_index.u.idx.last_key);
        err = reader_start(r, &index_iter, reftable_record_type(rec), 1);
        if (err < 0)
                goto done;
 
-       err = reader_seek_linear(r, &index_iter, &want_index_rec);
+       err = reader_seek_linear(r, &index_iter, &want_index);
        while (1) {
-               err = table_iter_next(&index_iter, &index_result_rec);
+               err = table_iter_next(&index_iter, &index_result);
                table_iter_block_done(&index_iter);
                if (err != 0)
                        goto done;
 
-               err = reader_table_iter_at(r, &next, index_result.offset, 0);
+               err = reader_table_iter_at(r, &next, index_result.u.idx.offset,
+                                          0);
                if (err != 0)
                        goto done;
 
-               err = block_iter_seek(&next.bi, &want_index.last_key);
+               err = block_iter_seek(&next.bi, &want_index.u.idx.last_key);
                if (err < 0)
                        goto done;
 
@@ -540,8 +545,8 @@ static int reader_seek_indexed(struct reftable_reader *r,
 done:
        block_iter_close(&next.bi);
        table_iter_close(&index_iter);
-       reftable_record_release(&want_index_rec);
-       reftable_record_release(&index_result_rec);
+       reftable_record_release(&want_index);
+       reftable_record_release(&index_result);
        return err;
 }
 
@@ -590,11 +595,12 @@ static int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,
 int reftable_reader_seek_ref(struct reftable_reader *r,
                             struct reftable_iterator *it, const char *name)
 {
-       struct reftable_ref_record ref = {
-               .refname = (char *)name,
+       struct reftable_record rec = {
+               .type = BLOCK_TYPE_REF,
+               .u.ref = {
+                       .refname = (char *)name,
+               },
        };
-       struct reftable_record rec = { NULL };
-       reftable_record_from_ref(&rec, &ref);
        return reader_seek(r, it, &rec);
 }
 
@@ -602,12 +608,11 @@ int reftable_reader_seek_log_at(struct reftable_reader *r,
                                struct reftable_iterator *it, const char *name,
                                uint64_t update_index)
 {
-       struct reftable_log_record log = {
-               .refname = (char *)name,
-               .update_index = update_index,
-       };
-       struct reftable_record rec = { NULL };
-       reftable_record_from_log(&rec, &log);
+       struct reftable_record rec = { .type = BLOCK_TYPE_LOG,
+                                      .u.log = {
+                                              .refname = (char *)name,
+                                              .update_index = update_index,
+                                      } };
        return reader_seek(r, it, &rec);
 }
 
@@ -641,6 +646,8 @@ int reftable_new_reader(struct reftable_reader **p,
 
 void reftable_reader_free(struct reftable_reader *r)
 {
+       if (!r)
+               return;
        reader_close(r);
        reftable_free(r);
 }
@@ -649,31 +656,33 @@ static int reftable_reader_refs_for_indexed(struct reftable_reader *r,
                                            struct reftable_iterator *it,
                                            uint8_t *oid)
 {
-       struct reftable_obj_record want = {
-               .hash_prefix = oid,
-               .hash_prefix_len = r->object_id_len,
+       struct reftable_record want = {
+               .type = BLOCK_TYPE_OBJ,
+               .u.obj = {
+                       .hash_prefix = oid,
+                       .hash_prefix_len = r->object_id_len,
+               },
        };
-       struct reftable_record want_rec = { NULL };
        struct reftable_iterator oit = { NULL };
-       struct reftable_obj_record got = { NULL };
-       struct reftable_record got_rec = { NULL };
+       struct reftable_record got = {
+               .type = BLOCK_TYPE_OBJ,
+               .u.obj = { 0 },
+       };
        int err = 0;
        struct indexed_table_ref_iter *itr = NULL;
 
        /* Look through the reverse index. */
-       reftable_record_from_obj(&want_rec, &want);
-       err = reader_seek(r, &oit, &want_rec);
+       err = reader_seek(r, &oit, &want);
        if (err != 0)
                goto done;
 
        /* read out the reftable_obj_record */
-       reftable_record_from_obj(&got_rec, &got);
-       err = iterator_next(&oit, &got_rec);
+       err = iterator_next(&oit, &got);
        if (err < 0)
                goto done;
 
-       if (err > 0 ||
-           memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
+       if (err > 0 || memcmp(want.u.obj.hash_prefix, got.u.obj.hash_prefix,
+                             r->object_id_len)) {
                /* didn't find it; return empty iterator */
                iterator_set_empty(it);
                err = 0;
@@ -681,15 +690,16 @@ static int reftable_reader_refs_for_indexed(struct reftable_reader *r,
        }
 
        err = new_indexed_table_ref_iter(&itr, r, oid, hash_size(r->hash_id),
-                                        got.offsets, got.offset_len);
+                                        got.u.obj.offsets,
+                                        got.u.obj.offset_len);
        if (err < 0)
                goto done;
-       got.offsets = NULL;
+       got.u.obj.offsets = NULL;
        iterator_from_indexed_table_ref_iter(it, itr);
 
 done:
        reftable_iterator_destroy(&oit);
-       reftable_record_release(&got_rec);
+       reftable_record_release(&got);
        return err;
 }
 
index 70c7aedba2ccb0ad73731abc2742ec2947200499..605ba0f9fd4f57d0e801d3fc69503ccdc32b63ed 100644 (file)
@@ -288,6 +288,71 @@ static void test_log_write_read(void)
        reader_close(&rd);
 }
 
+static void test_log_zlib_corruption(void)
+{
+       struct reftable_write_options opts = {
+               .block_size = 256,
+       };
+       struct reftable_iterator it = { 0 };
+       struct reftable_reader rd = { 0 };
+       struct reftable_block_source source = { 0 };
+       struct strbuf buf = STRBUF_INIT;
+       struct reftable_writer *w =
+               reftable_new_writer(&strbuf_add_void, &buf, &opts);
+       const struct reftable_stats *stats = NULL;
+       uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };
+       uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };
+       char message[100] = { 0 };
+       int err, i, n;
+
+       struct reftable_log_record log = {
+               .refname = "refname",
+               .value_type = REFTABLE_LOG_UPDATE,
+               .value = {
+                       .update = {
+                               .new_hash = hash1,
+                               .old_hash = hash2,
+                               .name = "My Name",
+                               .email = "myname@invalid",
+                               .message = message,
+                       },
+               },
+       };
+
+       for (i = 0; i < sizeof(message) - 1; i++)
+               message[i] = (uint8_t)(rand() % 64 + ' ');
+
+       reftable_writer_set_limits(w, 1, 1);
+
+       err = reftable_writer_add_log(w, &log);
+       EXPECT_ERR(err);
+
+       n = reftable_writer_close(w);
+       EXPECT(n == 0);
+
+       stats = writer_stats(w);
+       EXPECT(stats->log_stats.blocks > 0);
+       reftable_writer_free(w);
+       w = NULL;
+
+       /* corrupt the data. */
+       buf.buf[50] ^= 0x99;
+
+       block_source_from_strbuf(&source, &buf);
+
+       err = init_reader(&rd, &source, "file.log");
+       EXPECT_ERR(err);
+
+       err = reftable_reader_seek_log(&rd, &it, "refname");
+       EXPECT(err == REFTABLE_ZLIB_ERROR);
+
+       reftable_iterator_destroy(&it);
+
+       /* cleanup. */
+       strbuf_release(&buf);
+       reader_close(&rd);
+}
+
 static void test_table_read_write_sequential(void)
 {
        char **names;
@@ -631,7 +696,6 @@ static void test_write_key_order(void)
        err = reftable_writer_add_ref(w, &refs[0]);
        EXPECT_ERR(err);
        err = reftable_writer_add_ref(w, &refs[1]);
-       printf("%d\n", err);
        EXPECT(err == REFTABLE_API_ERROR);
        reftable_writer_close(w);
        reftable_writer_free(w);
@@ -667,6 +731,7 @@ static void test_corrupt_table(void)
 
 int readwrite_test_main(int argc, const char *argv[])
 {
+       RUN_TEST(test_log_zlib_corruption);
        RUN_TEST(test_corrupt_table);
        RUN_TEST(test_corrupt_table_empty);
        RUN_TEST(test_log_write_read);
index 6a5dac32dc69b7187d8c4e9481e6ddbe058ccae2..fbaa1fbef56c52fed8391c7bcef2efd035eb588b 100644 (file)
@@ -15,6 +15,10 @@ https://developers.google.com/open-source/licenses/bsd
 #include "reftable-error.h"
 #include "basics.h"
 
+static struct reftable_record_vtable *
+reftable_record_vtable(struct reftable_record *rec);
+static void *reftable_record_data(struct reftable_record *rec);
+
 int get_var_int(uint64_t *dest, struct string_view *in)
 {
        int ptr = 0;
@@ -72,7 +76,7 @@ int reftable_is_block_type(uint8_t typ)
        return 0;
 }
 
-uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec)
+uint8_t *reftable_ref_record_val1(const struct reftable_ref_record *rec)
 {
        switch (rec->value_type) {
        case REFTABLE_REF_VAL1:
@@ -84,7 +88,7 @@ uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec)
        }
 }
 
-uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec)
+uint8_t *reftable_ref_record_val2(const struct reftable_ref_record *rec)
 {
        switch (rec->value_type) {
        case REFTABLE_REF_VAL2:
@@ -251,24 +255,24 @@ static void hex_format(char *dest, uint8_t *src, int hash_size)
        }
 }
 
-void reftable_ref_record_print(struct reftable_ref_record *ref,
-                              uint32_t hash_id)
+static void reftable_ref_record_print_sz(const struct reftable_ref_record *ref,
+                                        int hash_size)
 {
-       char hex[2 * GIT_SHA256_RAWSZ + 1] = { 0 }; /* BUG */
+       char hex[GIT_MAX_HEXSZ + 1] = { 0 }; /* BUG */
        printf("ref{%s(%" PRIu64 ") ", ref->refname, ref->update_index);
        switch (ref->value_type) {
        case REFTABLE_REF_SYMREF:
                printf("=> %s", ref->value.symref);
                break;
        case REFTABLE_REF_VAL2:
-               hex_format(hex, ref->value.val2.value, hash_size(hash_id));
+               hex_format(hex, ref->value.val2.value, hash_size);
                printf("val 2 %s", hex);
                hex_format(hex, ref->value.val2.target_value,
-                          hash_size(hash_id));
+                          hash_size);
                printf("(T %s)", hex);
                break;
        case REFTABLE_REF_VAL1:
-               hex_format(hex, ref->value.val1, hash_size(hash_id));
+               hex_format(hex, ref->value.val1, hash_size);
                printf("val 1 %s", hex);
                break;
        case REFTABLE_REF_DELETION:
@@ -278,6 +282,11 @@ void reftable_ref_record_print(struct reftable_ref_record *ref,
        printf("}\n");
 }
 
+void reftable_ref_record_print(const struct reftable_ref_record *ref,
+                              uint32_t hash_id) {
+       reftable_ref_record_print_sz(ref, hash_size(hash_id));
+}
+
 static void reftable_ref_record_release_void(void *rec)
 {
        reftable_ref_record_release(rec);
@@ -430,6 +439,21 @@ static int reftable_ref_record_is_deletion_void(const void *p)
                (const struct reftable_ref_record *)p);
 }
 
+
+static int reftable_ref_record_equal_void(const void *a,
+                                         const void *b, int hash_size)
+{
+       struct reftable_ref_record *ra = (struct reftable_ref_record *) a;
+       struct reftable_ref_record *rb = (struct reftable_ref_record *) b;
+       return reftable_ref_record_equal(ra, rb, hash_size);
+}
+
+static void reftable_ref_record_print_void(const void *rec,
+                                          int hash_size)
+{
+       reftable_ref_record_print_sz((struct reftable_ref_record *) rec, hash_size);
+}
+
 static struct reftable_record_vtable reftable_ref_record_vtable = {
        .key = &reftable_ref_record_key,
        .type = BLOCK_TYPE_REF,
@@ -439,6 +463,8 @@ static struct reftable_record_vtable reftable_ref_record_vtable = {
        .decode = &reftable_ref_record_decode,
        .release = &reftable_ref_record_release_void,
        .is_deletion = &reftable_ref_record_is_deletion_void,
+       .equal = &reftable_ref_record_equal_void,
+       .print = &reftable_ref_record_print_void,
 };
 
 static void reftable_obj_record_key(const void *r, struct strbuf *dest)
@@ -457,6 +483,21 @@ static void reftable_obj_record_release(void *rec)
        memset(obj, 0, sizeof(struct reftable_obj_record));
 }
 
+static void reftable_obj_record_print(const void *rec, int hash_size)
+{
+       const struct reftable_obj_record *obj = rec;
+       char hex[GIT_MAX_HEXSZ + 1] = { 0 };
+       struct strbuf offset_str = STRBUF_INIT;
+       int i;
+
+       for (i = 0; i < obj->offset_len; i++)
+               strbuf_addf(&offset_str, "%" PRIu64 " ", obj->offsets[i]);
+       hex_format(hex, obj->hash_prefix, obj->hash_prefix_len);
+       printf("prefix %s (len %d), offsets [%s]\n",
+              hex, obj->hash_prefix_len, offset_str.buf);
+       strbuf_release(&offset_str);
+}
+
 static void reftable_obj_record_copy_from(void *rec, const void *src_rec,
                                          int hash_size)
 {
@@ -465,12 +506,14 @@ static void reftable_obj_record_copy_from(void *rec, const void *src_rec,
                (const struct reftable_obj_record *)src_rec;
 
        reftable_obj_record_release(obj);
-       *obj = *src;
-       obj->hash_prefix = reftable_malloc(obj->hash_prefix_len);
-       memcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len);
+       obj->hash_prefix = reftable_malloc(src->hash_prefix_len);
+       obj->hash_prefix_len = src->hash_prefix_len;
+       if (src->hash_prefix_len)
+               memcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len);
 
-       obj->offsets = reftable_malloc(obj->offset_len * sizeof(uint64_t));
-       COPY_ARRAY(obj->offsets, src->offsets, obj->offset_len);
+       obj->offsets = reftable_malloc(src->offset_len * sizeof(uint64_t));
+       obj->offset_len = src->offset_len;
+       COPY_ARRAY(obj->offsets, src->offsets, src->offset_len);
 }
 
 static uint8_t reftable_obj_record_val_type(const void *rec)
@@ -572,6 +615,25 @@ static int not_a_deletion(const void *p)
        return 0;
 }
 
+static int reftable_obj_record_equal_void(const void *a, const void *b, int hash_size)
+{
+       struct reftable_obj_record *ra = (struct reftable_obj_record *) a;
+       struct reftable_obj_record *rb = (struct reftable_obj_record *) b;
+
+       if (ra->hash_prefix_len != rb->hash_prefix_len
+           || ra->offset_len != rb->offset_len)
+               return 0;
+
+       if (ra->hash_prefix_len &&
+           memcmp(ra->hash_prefix, rb->hash_prefix, ra->hash_prefix_len))
+               return 0;
+       if (ra->offset_len &&
+           memcmp(ra->offsets, rb->offsets, ra->offset_len * sizeof(uint64_t)))
+               return 0;
+
+       return 1;
+}
+
 static struct reftable_record_vtable reftable_obj_record_vtable = {
        .key = &reftable_obj_record_key,
        .type = BLOCK_TYPE_OBJ,
@@ -580,32 +642,43 @@ static struct reftable_record_vtable reftable_obj_record_vtable = {
        .encode = &reftable_obj_record_encode,
        .decode = &reftable_obj_record_decode,
        .release = &reftable_obj_record_release,
-       .is_deletion = not_a_deletion,
+       .is_deletion = &not_a_deletion,
+       .equal = &reftable_obj_record_equal_void,
+       .print = &reftable_obj_record_print,
 };
 
-void reftable_log_record_print(struct reftable_log_record *log,
-                              uint32_t hash_id)
+static void reftable_log_record_print_sz(struct reftable_log_record *log,
+                                        int hash_size)
 {
-       char hex[GIT_SHA256_RAWSZ + 1] = { 0 };
+       char hex[GIT_MAX_HEXSZ + 1] = { 0 };
 
        switch (log->value_type) {
        case REFTABLE_LOG_DELETION:
-               printf("log{%s(%" PRIu64 ") delete", log->refname,
+               printf("log{%s(%" PRIu64 ") delete\n", log->refname,
                       log->update_index);
                break;
        case REFTABLE_LOG_UPDATE:
                printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %04d\n",
-                      log->refname, log->update_index, log->value.update.name,
-                      log->value.update.email, log->value.update.time,
+                      log->refname, log->update_index,
+                      log->value.update.name ? log->value.update.name : "",
+                      log->value.update.email ? log->value.update.email : "",
+                      log->value.update.time,
                       log->value.update.tz_offset);
-               hex_format(hex, log->value.update.old_hash, hash_size(hash_id));
+               hex_format(hex, log->value.update.old_hash, hash_size);
                printf("%s => ", hex);
-               hex_format(hex, log->value.update.new_hash, hash_size(hash_id));
-               printf("%s\n\n%s\n}\n", hex, log->value.update.message);
+               hex_format(hex, log->value.update.new_hash, hash_size);
+               printf("%s\n\n%s\n}\n", hex,
+                      log->value.update.message ? log->value.update.message : "");
                break;
        }
 }
 
+void reftable_log_record_print(struct reftable_log_record *log,
+                                     uint32_t hash_id)
+{
+       reftable_log_record_print_sz(log, hash_size(hash_id));
+}
+
 static void reftable_log_record_key(const void *r, struct strbuf *dest)
 {
        const struct reftable_log_record *rec =
@@ -881,8 +954,16 @@ static int zero_hash_eq(uint8_t *a, uint8_t *b, int sz)
        return !memcmp(a, b, sz);
 }
 
-int reftable_log_record_equal(struct reftable_log_record *a,
-                             struct reftable_log_record *b, int hash_size)
+static int reftable_log_record_equal_void(const void *a,
+                                         const void *b, int hash_size)
+{
+       return reftable_log_record_equal((struct reftable_log_record *) a,
+                                        (struct reftable_log_record *) b,
+                                        hash_size);
+}
+
+int reftable_log_record_equal(const struct reftable_log_record *a,
+                             const struct reftable_log_record *b, int hash_size)
 {
        if (!(null_streq(a->refname, b->refname) &&
              a->update_index == b->update_index &&
@@ -915,6 +996,11 @@ static int reftable_log_record_is_deletion_void(const void *p)
                (const struct reftable_log_record *)p);
 }
 
+static void reftable_log_record_print_void(const void *rec, int hash_size)
+{
+       reftable_log_record_print_sz((struct reftable_log_record*)rec, hash_size);
+}
+
 static struct reftable_record_vtable reftable_log_record_vtable = {
        .key = &reftable_log_record_key,
        .type = BLOCK_TYPE_LOG,
@@ -924,60 +1010,10 @@ static struct reftable_record_vtable reftable_log_record_vtable = {
        .decode = &reftable_log_record_decode,
        .release = &reftable_log_record_release_void,
        .is_deletion = &reftable_log_record_is_deletion_void,
+       .equal = &reftable_log_record_equal_void,
+       .print = &reftable_log_record_print_void,
 };
 
-struct reftable_record reftable_new_record(uint8_t typ)
-{
-       struct reftable_record rec = { NULL };
-       switch (typ) {
-       case BLOCK_TYPE_REF: {
-               struct reftable_ref_record *r =
-                       reftable_calloc(sizeof(struct reftable_ref_record));
-               reftable_record_from_ref(&rec, r);
-               return rec;
-       }
-
-       case BLOCK_TYPE_OBJ: {
-               struct reftable_obj_record *r =
-                       reftable_calloc(sizeof(struct reftable_obj_record));
-               reftable_record_from_obj(&rec, r);
-               return rec;
-       }
-       case BLOCK_TYPE_LOG: {
-               struct reftable_log_record *r =
-                       reftable_calloc(sizeof(struct reftable_log_record));
-               reftable_record_from_log(&rec, r);
-               return rec;
-       }
-       case BLOCK_TYPE_INDEX: {
-               struct reftable_index_record empty = { .last_key =
-                                                              STRBUF_INIT };
-               struct reftable_index_record *r =
-                       reftable_calloc(sizeof(struct reftable_index_record));
-               *r = empty;
-               reftable_record_from_index(&rec, r);
-               return rec;
-       }
-       }
-       abort();
-       return rec;
-}
-
-/* clear out the record, yielding the reftable_record data that was
- * encapsulated. */
-static void *reftable_record_yield(struct reftable_record *rec)
-{
-       void *p = rec->data;
-       rec->data = NULL;
-       return p;
-}
-
-void reftable_record_destroy(struct reftable_record *rec)
-{
-       reftable_record_release(rec);
-       reftable_free(reftable_record_yield(rec));
-}
-
 static void reftable_index_record_key(const void *r, struct strbuf *dest)
 {
        const struct reftable_index_record *rec = r;
@@ -1042,6 +1078,21 @@ static int reftable_index_record_decode(void *rec, struct strbuf key,
        return start.len - in.len;
 }
 
+static int reftable_index_record_equal(const void *a, const void *b, int hash_size)
+{
+       struct reftable_index_record *ia = (struct reftable_index_record *) a;
+       struct reftable_index_record *ib = (struct reftable_index_record *) b;
+
+       return ia->offset == ib->offset && !strbuf_cmp(&ia->last_key, &ib->last_key);
+}
+
+static void reftable_index_record_print(const void *rec, int hash_size)
+{
+       const struct reftable_index_record *idx = rec;
+       /* TODO: escape null chars? */
+       printf("\"%s\" %" PRIu64 "\n", idx->last_key.buf, idx->offset);
+}
+
 static struct reftable_record_vtable reftable_index_record_vtable = {
        .key = &reftable_index_record_key,
        .type = BLOCK_TYPE_INDEX,
@@ -1051,95 +1102,66 @@ static struct reftable_record_vtable reftable_index_record_vtable = {
        .decode = &reftable_index_record_decode,
        .release = &reftable_index_record_release,
        .is_deletion = &not_a_deletion,
+       .equal = &reftable_index_record_equal,
+       .print = &reftable_index_record_print,
 };
 
 void reftable_record_key(struct reftable_record *rec, struct strbuf *dest)
 {
-       rec->ops->key(rec->data, dest);
+       reftable_record_vtable(rec)->key(reftable_record_data(rec), dest);
 }
 
 uint8_t reftable_record_type(struct reftable_record *rec)
 {
-       return rec->ops->type;
+       return rec->type;
 }
 
 int reftable_record_encode(struct reftable_record *rec, struct string_view dest,
                           int hash_size)
 {
-       return rec->ops->encode(rec->data, dest, hash_size);
+       return reftable_record_vtable(rec)->encode(reftable_record_data(rec),
+                                                  dest, hash_size);
 }
 
 void reftable_record_copy_from(struct reftable_record *rec,
                               struct reftable_record *src, int hash_size)
 {
-       assert(src->ops->type == rec->ops->type);
+       assert(src->type == rec->type);
 
-       rec->ops->copy_from(rec->data, src->data, hash_size);
+       reftable_record_vtable(rec)->copy_from(reftable_record_data(rec),
+                                              reftable_record_data(src),
+                                              hash_size);
 }
 
 uint8_t reftable_record_val_type(struct reftable_record *rec)
 {
-       return rec->ops->val_type(rec->data);
+       return reftable_record_vtable(rec)->val_type(reftable_record_data(rec));
 }
 
 int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
                           uint8_t extra, struct string_view src, int hash_size)
 {
-       return rec->ops->decode(rec->data, key, extra, src, hash_size);
+       return reftable_record_vtable(rec)->decode(reftable_record_data(rec),
+                                                  key, extra, src, hash_size);
 }
 
 void reftable_record_release(struct reftable_record *rec)
 {
-       rec->ops->release(rec->data);
+       reftable_record_vtable(rec)->release(reftable_record_data(rec));
 }
 
 int reftable_record_is_deletion(struct reftable_record *rec)
 {
-       return rec->ops->is_deletion(rec->data);
+       return reftable_record_vtable(rec)->is_deletion(
+               reftable_record_data(rec));
 }
 
-void reftable_record_from_ref(struct reftable_record *rec,
-                             struct reftable_ref_record *ref_rec)
+int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, int hash_size)
 {
-       assert(!rec->ops);
-       rec->data = ref_rec;
-       rec->ops = &reftable_ref_record_vtable;
-}
-
-void reftable_record_from_obj(struct reftable_record *rec,
-                             struct reftable_obj_record *obj_rec)
-{
-       assert(!rec->ops);
-       rec->data = obj_rec;
-       rec->ops = &reftable_obj_record_vtable;
-}
-
-void reftable_record_from_index(struct reftable_record *rec,
-                               struct reftable_index_record *index_rec)
-{
-       assert(!rec->ops);
-       rec->data = index_rec;
-       rec->ops = &reftable_index_record_vtable;
-}
-
-void reftable_record_from_log(struct reftable_record *rec,
-                             struct reftable_log_record *log_rec)
-{
-       assert(!rec->ops);
-       rec->data = log_rec;
-       rec->ops = &reftable_log_record_vtable;
-}
-
-struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *rec)
-{
-       assert(reftable_record_type(rec) == BLOCK_TYPE_REF);
-       return rec->data;
-}
-
-struct reftable_log_record *reftable_record_as_log(struct reftable_record *rec)
-{
-       assert(reftable_record_type(rec) == BLOCK_TYPE_LOG);
-       return rec->data;
+       if (a->type != b->type)
+               return 0;
+       return reftable_record_vtable(a)->equal(
+               reftable_record_data(a), reftable_record_data(b), hash_size);
 }
 
 static int hash_equal(uint8_t *a, uint8_t *b, int hash_size)
@@ -1150,13 +1172,15 @@ static int hash_equal(uint8_t *a, uint8_t *b, int hash_size)
        return a == b;
 }
 
-int reftable_ref_record_equal(struct reftable_ref_record *a,
-                             struct reftable_ref_record *b, int hash_size)
+int reftable_ref_record_equal(const struct reftable_ref_record *a,
+                             const struct reftable_ref_record *b, int hash_size)
 {
        assert(hash_size > 0);
-       if (!(0 == strcmp(a->refname, b->refname) &&
-             a->update_index == b->update_index &&
-             a->value_type == b->value_type))
+       if (!null_streq(a->refname, b->refname))
+               return 0;
+
+       if (a->update_index != b->update_index ||
+           a->value_type != b->value_type)
                return 0;
 
        switch (a->value_type) {
@@ -1210,3 +1234,81 @@ void string_view_consume(struct string_view *s, int n)
        s->buf += n;
        s->len -= n;
 }
+
+static void *reftable_record_data(struct reftable_record *rec)
+{
+       switch (rec->type) {
+       case BLOCK_TYPE_REF:
+               return &rec->u.ref;
+       case BLOCK_TYPE_LOG:
+               return &rec->u.log;
+       case BLOCK_TYPE_INDEX:
+               return &rec->u.idx;
+       case BLOCK_TYPE_OBJ:
+               return &rec->u.obj;
+       }
+       abort();
+}
+
+static struct reftable_record_vtable *
+reftable_record_vtable(struct reftable_record *rec)
+{
+       switch (rec->type) {
+       case BLOCK_TYPE_REF:
+               return &reftable_ref_record_vtable;
+       case BLOCK_TYPE_LOG:
+               return &reftable_log_record_vtable;
+       case BLOCK_TYPE_INDEX:
+               return &reftable_index_record_vtable;
+       case BLOCK_TYPE_OBJ:
+               return &reftable_obj_record_vtable;
+       }
+       abort();
+}
+
+struct reftable_record reftable_new_record(uint8_t typ)
+{
+       struct reftable_record clean = {
+               .type = typ,
+       };
+
+       /* the following is involved, but the naive solution (just return
+        * `clean` as is, except for BLOCK_TYPE_INDEX), returns a garbage
+        * clean.u.obj.offsets pointer on Windows VS CI.  Go figure.
+        */
+       switch (typ) {
+       case BLOCK_TYPE_OBJ:
+       {
+               struct reftable_obj_record obj = { 0 };
+               clean.u.obj = obj;
+               break;
+       }
+       case BLOCK_TYPE_INDEX:
+       {
+               struct reftable_index_record idx = {
+                       .last_key = STRBUF_INIT,
+               };
+               clean.u.idx = idx;
+               break;
+       }
+       case BLOCK_TYPE_REF:
+       {
+               struct reftable_ref_record ref = { 0 };
+               clean.u.ref = ref;
+               break;
+       }
+       case BLOCK_TYPE_LOG:
+       {
+               struct reftable_log_record log = { 0 };
+               clean.u.log = log;
+               break;
+       }
+       }
+       return clean;
+}
+
+void reftable_record_print(struct reftable_record *rec, int hash_size)
+{
+       printf("'%c': ", rec->type);
+       reftable_record_vtable(rec)->print(reftable_record_data(rec), hash_size);
+}
index 498e8c50bf4f02eb13bc653fe11006a861f6ab9e..fd80cd451d5d4c3ffea93d5bb1c7a0014531fae9 100644 (file)
@@ -58,18 +58,18 @@ struct reftable_record_vtable {
 
        /* is this a tombstone? */
        int (*is_deletion)(const void *rec);
-};
 
-/* record is a generic wrapper for different types of records. */
-struct reftable_record {
-       void *data;
-       struct reftable_record_vtable *ops;
+       /* Are two records equal? This assumes they have the same type. Returns 0 for non-equal. */
+       int (*equal)(const void *a, const void *b, int hash_size);
+
+       /* Print on stdout, for debugging. */
+       void (*print)(const void *rec, int hash_size);
 };
 
 /* returns true for recognized block types. Block start with the block type. */
 int reftable_is_block_type(uint8_t typ);
 
-/* creates a malloced record of the given type. Dispose with record_destroy */
+/* return an initialized record for the given type */
 struct reftable_record reftable_new_record(uint8_t typ);
 
 /* Encode `key` into `dest`. Sets `is_restart` to indicate a restart. Returns
@@ -97,8 +97,25 @@ struct reftable_obj_record {
        int offset_len;
 };
 
-/* see struct record_vtable */
+/* record is a generic wrapper for different types of records. It is normally
+ * created on the stack, or embedded within another struct. If the type is
+ * known, a fresh instance can be initialized explicitly. Otherwise, use
+ * reftable_new_record() to initialize generically (as the index_record is not
+ * valid as 0-initialized structure)
+ */
+struct reftable_record {
+       uint8_t type;
+       union {
+               struct reftable_ref_record ref;
+               struct reftable_log_record log;
+               struct reftable_obj_record obj;
+               struct reftable_index_record idx;
+       } u;
+};
 
+/* see struct record_vtable */
+int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, int hash_size);
+void reftable_record_print(struct reftable_record *rec, int hash_size);
 void reftable_record_key(struct reftable_record *rec, struct strbuf *dest);
 uint8_t reftable_record_type(struct reftable_record *rec);
 void reftable_record_copy_from(struct reftable_record *rec,
@@ -111,25 +128,9 @@ int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
                           int hash_size);
 int reftable_record_is_deletion(struct reftable_record *rec);
 
-/* zeroes out the embedded record */
+/* frees and zeroes out the embedded record */
 void reftable_record_release(struct reftable_record *rec);
 
-/* clear and deallocate embedded record, and zero `rec`. */
-void reftable_record_destroy(struct reftable_record *rec);
-
-/* initialize generic records from concrete records. The generic record should
- * be zeroed out. */
-void reftable_record_from_obj(struct reftable_record *rec,
-                             struct reftable_obj_record *objrec);
-void reftable_record_from_index(struct reftable_record *rec,
-                               struct reftable_index_record *idxrec);
-void reftable_record_from_ref(struct reftable_record *rec,
-                             struct reftable_ref_record *refrec);
-void reftable_record_from_log(struct reftable_record *rec,
-                             struct reftable_log_record *logrec);
-struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *ref);
-struct reftable_log_record *reftable_record_as_log(struct reftable_record *ref);
-
 /* for qsort. */
 int reftable_ref_record_compare_name(const void *a, const void *b);
 
index f4ad7cace41bb227564e4d6a17078fdefd36c555..f91ea5e88305d466188c8f48ce6fbaf4ef7d8ebc 100644 (file)
 
 static void test_copy(struct reftable_record *rec)
 {
-       struct reftable_record copy =
-               reftable_new_record(reftable_record_type(rec));
+       struct reftable_record copy = { 0 };
+       uint8_t typ;
+
+       typ = reftable_record_type(rec);
+       copy = reftable_new_record(typ);
        reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
        /* do it twice to catch memory leaks */
        reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
-       switch (reftable_record_type(&copy)) {
-       case BLOCK_TYPE_REF:
-               EXPECT(reftable_ref_record_equal(reftable_record_as_ref(&copy),
-                                                reftable_record_as_ref(rec),
-                                                GIT_SHA1_RAWSZ));
-               break;
-       case BLOCK_TYPE_LOG:
-               EXPECT(reftable_log_record_equal(reftable_record_as_log(&copy),
-                                                reftable_record_as_log(rec),
-                                                GIT_SHA1_RAWSZ));
-               break;
-       }
-       reftable_record_destroy(&copy);
+       EXPECT(reftable_record_equal(rec, &copy, GIT_SHA1_RAWSZ));
+
+       puts("testing print coverage:\n");
+       reftable_record_print(&copy, GIT_SHA1_RAWSZ);
+
+       reftable_record_release(&copy);
 }
 
 static void test_varint_roundtrip(void)
@@ -106,61 +102,58 @@ static void test_reftable_ref_record_roundtrip(void)
        int i = 0;
 
        for (i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {
-               struct reftable_ref_record in = { NULL };
-               struct reftable_ref_record out = { NULL };
-               struct reftable_record rec_out = { NULL };
+               struct reftable_record in = {
+                       .type = BLOCK_TYPE_REF,
+               };
+               struct reftable_record out = { .type = BLOCK_TYPE_REF };
                struct strbuf key = STRBUF_INIT;
-               struct reftable_record rec = { NULL };
                uint8_t buffer[1024] = { 0 };
                struct string_view dest = {
                        .buf = buffer,
                        .len = sizeof(buffer),
                };
-
                int n, m;
 
-               in.value_type = i;
+               in.u.ref.value_type = i;
                switch (i) {
                case REFTABLE_REF_DELETION:
                        break;
                case REFTABLE_REF_VAL1:
-                       in.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
-                       set_hash(in.value.val1, 1);
+                       in.u.ref.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
+                       set_hash(in.u.ref.value.val1, 1);
                        break;
                case REFTABLE_REF_VAL2:
-                       in.value.val2.value = reftable_malloc(GIT_SHA1_RAWSZ);
-                       set_hash(in.value.val2.value, 1);
-                       in.value.val2.target_value =
+                       in.u.ref.value.val2.value =
                                reftable_malloc(GIT_SHA1_RAWSZ);
-                       set_hash(in.value.val2.target_value, 2);
+                       set_hash(in.u.ref.value.val2.value, 1);
+                       in.u.ref.value.val2.target_value =
+                               reftable_malloc(GIT_SHA1_RAWSZ);
+                       set_hash(in.u.ref.value.val2.target_value, 2);
                        break;
                case REFTABLE_REF_SYMREF:
-                       in.value.symref = xstrdup("target");
+                       in.u.ref.value.symref = xstrdup("target");
                        break;
                }
-               in.refname = xstrdup("refs/heads/master");
+               in.u.ref.refname = xstrdup("refs/heads/master");
 
-               reftable_record_from_ref(&rec, &in);
-               test_copy(&rec);
+               test_copy(&in);
 
-               EXPECT(reftable_record_val_type(&rec) == i);
+               EXPECT(reftable_record_val_type(&in) == i);
 
-               reftable_record_key(&rec, &key);
-               n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+               reftable_record_key(&in, &key);
+               n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
                EXPECT(n > 0);
 
                /* decode into a non-zero reftable_record to test for leaks. */
-
-               reftable_record_from_ref(&rec_out, &out);
-               m = reftable_record_decode(&rec_out, key, i, dest,
-                                          GIT_SHA1_RAWSZ);
+               m = reftable_record_decode(&out, key, i, dest, GIT_SHA1_RAWSZ);
                EXPECT(n == m);
 
-               EXPECT(reftable_ref_record_equal(&in, &out, GIT_SHA1_RAWSZ));
-               reftable_record_release(&rec_out);
+               EXPECT(reftable_ref_record_equal(&in.u.ref, &out.u.ref,
+                                                GIT_SHA1_RAWSZ));
+               reftable_record_release(&in);
 
                strbuf_release(&key);
-               reftable_ref_record_release(&in);
+               reftable_record_release(&out);
        }
 }
 
@@ -187,7 +180,8 @@ static void test_reftable_log_record_equal(void)
 static void test_reftable_log_record_roundtrip(void)
 {
        int i;
-       struct reftable_log_record in[2] = {
+
+       struct reftable_log_record in[] = {
                {
                        .refname = xstrdup("refs/heads/master"),
                        .update_index = 42,
@@ -208,12 +202,26 @@ static void test_reftable_log_record_roundtrip(void)
                        .refname = xstrdup("refs/heads/master"),
                        .update_index = 22,
                        .value_type = REFTABLE_LOG_DELETION,
+               },
+               {
+                       .refname = xstrdup("branch"),
+                       .update_index = 33,
+                       .value_type = REFTABLE_LOG_UPDATE,
+                       .value = {
+                               .update = {
+                                       .old_hash = reftable_malloc(GIT_SHA1_RAWSZ),
+                                       .new_hash = reftable_malloc(GIT_SHA1_RAWSZ),
+                                       /* rest of fields left empty. */
+                               },
+                       },
                }
        };
        set_test_hash(in[0].value.update.new_hash, 1);
        set_test_hash(in[0].value.update.old_hash, 2);
+       set_test_hash(in[2].value.update.new_hash, 3);
+       set_test_hash(in[2].value.update.old_hash, 4);
        for (i = 0; i < ARRAY_SIZE(in); i++) {
-               struct reftable_record rec = { NULL };
+               struct reftable_record rec = { .type = BLOCK_TYPE_LOG };
                struct strbuf key = STRBUF_INIT;
                uint8_t buffer[1024] = { 0 };
                struct string_view dest = {
@@ -221,23 +229,25 @@ static void test_reftable_log_record_roundtrip(void)
                        .len = sizeof(buffer),
                };
                /* populate out, to check for leaks. */
-               struct reftable_log_record out = {
-                       .refname = xstrdup("old name"),
-                       .value_type = REFTABLE_LOG_UPDATE,
-                       .value = {
-                               .update = {
-                                       .new_hash = reftable_calloc(GIT_SHA1_RAWSZ),
-                                       .old_hash = reftable_calloc(GIT_SHA1_RAWSZ),
-                                       .name = xstrdup("old name"),
-                                       .email = xstrdup("old@email"),
-                                       .message = xstrdup("old message"),
+               struct reftable_record out = {
+                       .type = BLOCK_TYPE_LOG,
+                       .u.log = {
+                               .refname = xstrdup("old name"),
+                               .value_type = REFTABLE_LOG_UPDATE,
+                               .value = {
+                                       .update = {
+                                               .new_hash = reftable_calloc(GIT_SHA1_RAWSZ),
+                                               .old_hash = reftable_calloc(GIT_SHA1_RAWSZ),
+                                               .name = xstrdup("old name"),
+                                               .email = xstrdup("old@email"),
+                                               .message = xstrdup("old message"),
+                                       },
                                },
                        },
                };
-               struct reftable_record rec_out = { NULL };
                int n, m, valtype;
 
-               reftable_record_from_log(&rec, &in[i]);
+               rec.u.log = in[i];
 
                test_copy(&rec);
 
@@ -245,16 +255,16 @@ static void test_reftable_log_record_roundtrip(void)
 
                n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
                EXPECT(n >= 0);
-               reftable_record_from_log(&rec_out, &out);
                valtype = reftable_record_val_type(&rec);
-               m = reftable_record_decode(&rec_out, key, valtype, dest,
+               m = reftable_record_decode(&out, key, valtype, dest,
                                           GIT_SHA1_RAWSZ);
                EXPECT(n == m);
 
-               EXPECT(reftable_log_record_equal(&in[i], &out, GIT_SHA1_RAWSZ));
+               EXPECT(reftable_log_record_equal(&in[i], &out.u.log,
+                                                GIT_SHA1_RAWSZ));
                reftable_log_record_release(&in[i]);
                strbuf_release(&key);
-               reftable_record_release(&rec_out);
+               reftable_record_release(&out);
        }
 }
 
@@ -322,47 +332,43 @@ static void test_reftable_obj_record_roundtrip(void)
                                               } };
        int i = 0;
        for (i = 0; i < ARRAY_SIZE(recs); i++) {
-               struct reftable_obj_record in = recs[i];
                uint8_t buffer[1024] = { 0 };
                struct string_view dest = {
                        .buf = buffer,
                        .len = sizeof(buffer),
                };
-               struct reftable_record rec = { NULL };
+               struct reftable_record in = {
+                       .type = BLOCK_TYPE_OBJ,
+                       .u.obj = recs[i],
+               };
                struct strbuf key = STRBUF_INIT;
-               struct reftable_obj_record out = { NULL };
-               struct reftable_record rec_out = { NULL };
+               struct reftable_record out = { .type = BLOCK_TYPE_OBJ };
                int n, m;
                uint8_t extra;
 
-               reftable_record_from_obj(&rec, &in);
-               test_copy(&rec);
-               reftable_record_key(&rec, &key);
-               n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+               test_copy(&in);
+               reftable_record_key(&in, &key);
+               n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
                EXPECT(n > 0);
-               extra = reftable_record_val_type(&rec);
-               reftable_record_from_obj(&rec_out, &out);
-               m = reftable_record_decode(&rec_out, key, extra, dest,
+               extra = reftable_record_val_type(&in);
+               m = reftable_record_decode(&out, key, extra, dest,
                                           GIT_SHA1_RAWSZ);
                EXPECT(n == m);
 
-               EXPECT(in.hash_prefix_len == out.hash_prefix_len);
-               EXPECT(in.offset_len == out.offset_len);
-
-               EXPECT(!memcmp(in.hash_prefix, out.hash_prefix,
-                              in.hash_prefix_len));
-               EXPECT(0 == memcmp(in.offsets, out.offsets,
-                                  sizeof(uint64_t) * in.offset_len));
+               EXPECT(reftable_record_equal(&in, &out, GIT_SHA1_RAWSZ));
                strbuf_release(&key);
-               reftable_record_release(&rec_out);
+               reftable_record_release(&out);
        }
 }
 
 static void test_reftable_index_record_roundtrip(void)
 {
-       struct reftable_index_record in = {
-               .offset = 42,
-               .last_key = STRBUF_INIT,
+       struct reftable_record in = {
+               .type = BLOCK_TYPE_INDEX,
+               .u.idx = {
+                       .offset = 42,
+                       .last_key = STRBUF_INIT,
+               },
        };
        uint8_t buffer[1024] = { 0 };
        struct string_view dest = {
@@ -370,31 +376,30 @@ static void test_reftable_index_record_roundtrip(void)
                .len = sizeof(buffer),
        };
        struct strbuf key = STRBUF_INIT;
-       struct reftable_record rec = { NULL };
-       struct reftable_index_record out = { .last_key = STRBUF_INIT };
-       struct reftable_record out_rec = { NULL };
+       struct reftable_record out = {
+               .type = BLOCK_TYPE_INDEX,
+               .u.idx = { .last_key = STRBUF_INIT },
+       };
        int n, m;
        uint8_t extra;
 
-       strbuf_addstr(&in.last_key, "refs/heads/master");
-       reftable_record_from_index(&rec, &in);
-       reftable_record_key(&rec, &key);
-       test_copy(&rec);
+       strbuf_addstr(&in.u.idx.last_key, "refs/heads/master");
+       reftable_record_key(&in, &key);
+       test_copy(&in);
 
-       EXPECT(0 == strbuf_cmp(&key, &in.last_key));
-       n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+       EXPECT(0 == strbuf_cmp(&key, &in.u.idx.last_key));
+       n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
        EXPECT(n > 0);
 
-       extra = reftable_record_val_type(&rec);
-       reftable_record_from_index(&out_rec, &out);
-       m = reftable_record_decode(&out_rec, key, extra, dest, GIT_SHA1_RAWSZ);
+       extra = reftable_record_val_type(&in);
+       m = reftable_record_decode(&out, key, extra, dest, GIT_SHA1_RAWSZ);
        EXPECT(m == n);
 
-       EXPECT(in.offset == out.offset);
+       EXPECT(reftable_record_equal(&in, &out, GIT_SHA1_RAWSZ));
 
-       reftable_record_release(&out_rec);
+       reftable_record_release(&out);
        strbuf_release(&key);
-       strbuf_release(&in.last_key);
+       strbuf_release(&in.u.idx.last_key);
 }
 
 int record_test_main(int argc, const char *argv[])
index 5370d2288c735fbbec3c0b22e19ace82d9dfc3d6..67104f8fbfecd7984127d29dd1ae3fa9bb263394 100644 (file)
@@ -49,25 +49,25 @@ struct reftable_ref_record {
 
 /* Returns the first hash, or NULL if `rec` is not of type
  * REFTABLE_REF_VAL1 or REFTABLE_REF_VAL2. */
-uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec);
+uint8_t *reftable_ref_record_val1(const struct reftable_ref_record *rec);
 
 /* Returns the second hash, or NULL if `rec` is not of type
  * REFTABLE_REF_VAL2. */
-uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec);
+uint8_t *reftable_ref_record_val2(const struct reftable_ref_record *rec);
 
 /* returns whether 'ref' represents a deletion */
 int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref);
 
 /* prints a reftable_ref_record onto stdout. Useful for debugging. */
-void reftable_ref_record_print(struct reftable_ref_record *ref,
+void reftable_ref_record_print(const struct reftable_ref_record *ref,
                               uint32_t hash_id);
 
 /* frees and nulls all pointer values inside `ref`. */
 void reftable_ref_record_release(struct reftable_ref_record *ref);
 
 /* returns whether two reftable_ref_records are the same. Useful for testing. */
-int reftable_ref_record_equal(struct reftable_ref_record *a,
-                             struct reftable_ref_record *b, int hash_size);
+int reftable_ref_record_equal(const struct reftable_ref_record *a,
+                             const struct reftable_ref_record *b, int hash_size);
 
 /* reftable_log_record holds a reflog entry */
 struct reftable_log_record {
@@ -104,8 +104,8 @@ int reftable_log_record_is_deletion(const struct reftable_log_record *log);
 void reftable_log_record_release(struct reftable_log_record *log);
 
 /* returns whether two records are equal. Useful for testing. */
-int reftable_log_record_equal(struct reftable_log_record *a,
-                             struct reftable_log_record *b, int hash_size);
+int reftable_log_record_equal(const struct reftable_log_record *a,
+                             const struct reftable_log_record *b, int hash_size);
 
 /* dumps a reftable_log_record on stdout, for debugging/testing. */
 void reftable_log_record_print(struct reftable_log_record *log,
diff --git a/reftable/reftable.c b/reftable/reftable.c
deleted file mode 100644 (file)
index 0e4607a..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
-Copyright 2020 Google LLC
-
-Use of this source code is governed by a BSD-style
-license that can be found in the LICENSE file or at
-https://developers.google.com/open-source/licenses/bsd
-*/
-
-#include "basics.h"
-#include "record.h"
-#include "generic.h"
-#include "reftable-iterator.h"
-#include "reftable-generic.h"
-
-int reftable_table_seek_ref(struct reftable_table *tab,
-                           struct reftable_iterator *it, const char *name)
-{
-       struct reftable_ref_record ref = {
-               .refname = (char *)name,
-       };
-       struct reftable_record rec = { NULL };
-       reftable_record_from_ref(&rec, &ref);
-       return tab->ops->seek_record(tab->table_arg, it, &rec);
-}
-
-int reftable_table_read_ref(struct reftable_table *tab, const char *name,
-                           struct reftable_ref_record *ref)
-{
-       struct reftable_iterator it = { NULL };
-       int err = reftable_table_seek_ref(tab, &it, name);
-       if (err)
-               goto done;
-
-       err = reftable_iterator_next_ref(&it, ref);
-       if (err)
-               goto done;
-
-       if (strcmp(ref->refname, name) ||
-           reftable_ref_record_is_deletion(ref)) {
-               reftable_ref_record_release(ref);
-               err = 1;
-               goto done;
-       }
-
-done:
-       reftable_iterator_destroy(&it);
-       return err;
-}
-
-uint64_t reftable_table_max_update_index(struct reftable_table *tab)
-{
-       return tab->ops->max_update_index(tab->table_arg);
-}
-
-uint64_t reftable_table_min_update_index(struct reftable_table *tab)
-{
-       return tab->ops->min_update_index(tab->table_arg);
-}
-
-uint32_t reftable_table_hash_id(struct reftable_table *tab)
-{
-       return tab->ops->hash_id(tab->table_arg);
-}
-
-void reftable_iterator_destroy(struct reftable_iterator *it)
-{
-       if (!it->ops) {
-               return;
-       }
-       it->ops->close(it->iter_arg);
-       it->ops = NULL;
-       FREE_AND_NULL(it->iter_arg);
-}
-
-int reftable_iterator_next_ref(struct reftable_iterator *it,
-                              struct reftable_ref_record *ref)
-{
-       struct reftable_record rec = { NULL };
-       reftable_record_from_ref(&rec, ref);
-       return iterator_next(it, &rec);
-}
-
-int reftable_iterator_next_log(struct reftable_iterator *it,
-                              struct reftable_log_record *log)
-{
-       struct reftable_record rec = { NULL };
-       reftable_record_from_log(&rec, log);
-       return iterator_next(it, &rec);
-}
-
-int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)
-{
-       return it->ops->next(it->iter_arg, rec);
-}
-
-static int empty_iterator_next(void *arg, struct reftable_record *rec)
-{
-       return 1;
-}
-
-static void empty_iterator_close(void *arg)
-{
-}
-
-static struct reftable_iterator_vtable empty_vtable = {
-       .next = &empty_iterator_next,
-       .close = &empty_iterator_close,
-};
-
-void iterator_set_empty(struct reftable_iterator *it)
-{
-       assert(!it->ops);
-       it->iter_arg = NULL;
-       it->ops = &empty_vtable;
-}
index 56bf5f2d84ae5f9bad040e68768f99274c11e48c..ddbdf1b9c8bf4668dfe4f9b7c03cabd43effb660 100644 (file)
@@ -889,7 +889,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
        struct strbuf new_table_path = STRBUF_INIT;
        int err = 0;
        int have_lock = 0;
-       int lock_file_fd = 0;
+       int lock_file_fd = -1;
        int compact_count = last - first + 1;
        char **listp = NULL;
        char **delete_on_success =
@@ -923,7 +923,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
        }
        /* Don't want to write to the lock for now.  */
        close(lock_file_fd);
-       lock_file_fd = 0;
+       lock_file_fd = -1;
 
        have_lock = 1;
        err = stack_uptodate(st);
@@ -1031,7 +1031,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
                goto done;
        }
        err = close(lock_file_fd);
-       lock_file_fd = 0;
+       lock_file_fd = -1;
        if (err < 0) {
                err = REFTABLE_IO_ERROR;
                unlink(new_table_path.buf);
@@ -1068,9 +1068,9 @@ done:
                listp++;
        }
        free_names(subtable_locks);
-       if (lock_file_fd > 0) {
+       if (lock_file_fd >= 0) {
                close(lock_file_fd);
-               lock_file_fd = 0;
+               lock_file_fd = -1;
        }
        if (have_lock) {
                unlink(lock_file_name.buf);
index f4c743db80c391c1b6c91d3d5f178c76b4393964..19fe4e200859683b13b1f0b0a21c8b1685ad4a08 100644 (file)
@@ -90,7 +90,7 @@ static void test_read_file(void)
                EXPECT(0 == strcmp(want[i], names[i]));
        }
        free_names(names);
-       remove(fn);
+       (void) remove(fn);
 }
 
 static void test_parse_names(void)
@@ -839,6 +839,7 @@ static void test_reftable_stack_auto_compaction(void)
                EXPECT_ERR(err);
 
                err = reftable_stack_auto_compact(st);
+               EXPECT_ERR(err);
                EXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));
        }
 
index 4907306c0c5d4e13ef13ea9f89c9529a6b7a3cda..18f9207dfee16accdaac46a77d13b9960aad3ca2 100644 (file)
@@ -16,17 +16,6 @@ https://developers.google.com/open-source/licenses/bsd
 #include "hash.h" /* hash ID, sizes.*/
 #include "dir.h" /* remove_dir_recursively, for tests.*/
 
-#include <zlib.h>
-
-#ifdef NO_UNCOMPRESS2
-/*
- * This is uncompress2, which is only available in zlib >= 1.2.9
- * (released as of early 2017)
- */
-int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source,
-               uLong *sourceLen);
-#endif
-
 int hash_size(uint32_t id);
 
 #endif
index 35c8649c9b73cdd06d279a6364440d017c66a9fc..944c2329ab568aa17cb1243507cc7a352faaf8ad 100644 (file)
@@ -150,6 +150,8 @@ void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
 
 void reftable_writer_free(struct reftable_writer *w)
 {
+       if (!w)
+               return;
        reftable_free(w->block);
        reftable_free(w);
 }
@@ -254,8 +256,10 @@ done:
 int reftable_writer_add_ref(struct reftable_writer *w,
                            struct reftable_ref_record *ref)
 {
-       struct reftable_record rec = { NULL };
-       struct reftable_ref_record copy = *ref;
+       struct reftable_record rec = {
+               .type = BLOCK_TYPE_REF,
+               .u.ref = *ref,
+       };
        int err = 0;
 
        if (ref->refname == NULL)
@@ -264,8 +268,7 @@ int reftable_writer_add_ref(struct reftable_writer *w,
            ref->update_index > w->max_update_index)
                return REFTABLE_API_ERROR;
 
-       reftable_record_from_ref(&rec, &copy);
-       copy.update_index -= w->min_update_index;
+       rec.u.ref.update_index -= w->min_update_index;
 
        err = writer_add_record(w, &rec);
        if (err < 0)
@@ -304,7 +307,10 @@ int reftable_writer_add_refs(struct reftable_writer *w,
 static int reftable_writer_add_log_verbatim(struct reftable_writer *w,
                                            struct reftable_log_record *log)
 {
-       struct reftable_record rec = { NULL };
+       struct reftable_record rec = {
+               .type = BLOCK_TYPE_LOG,
+               .u.log = *log,
+       };
        if (w->block_writer &&
            block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
                int err = writer_finish_public_section(w);
@@ -314,8 +320,6 @@ static int reftable_writer_add_log_verbatim(struct reftable_writer *w,
 
        w->next -= w->pending_padding;
        w->pending_padding = 0;
-
-       reftable_record_from_log(&rec, log);
        return writer_add_record(w, &rec);
 }
 
@@ -396,8 +400,10 @@ static int writer_finish_section(struct reftable_writer *w)
                w->index_len = 0;
                w->index_cap = 0;
                for (i = 0; i < idx_len; i++) {
-                       struct reftable_record rec = { NULL };
-                       reftable_record_from_index(&rec, idx + i);
+                       struct reftable_record rec = {
+                               .type = BLOCK_TYPE_INDEX,
+                               .u.idx = idx[i],
+                       };
                        if (block_writer_add(w->block_writer, &rec) == 0) {
                                continue;
                        }
@@ -465,17 +471,17 @@ static void write_object_record(void *void_arg, void *key)
 {
        struct write_record_arg *arg = void_arg;
        struct obj_index_tree_node *entry = key;
-       struct reftable_obj_record obj_rec = {
-               .hash_prefix = (uint8_t *)entry->hash.buf,
-               .hash_prefix_len = arg->w->stats.object_id_len,
-               .offsets = entry->offsets,
-               .offset_len = entry->offset_len,
-       };
-       struct reftable_record rec = { NULL };
+       struct reftable_record
+               rec = { .type = BLOCK_TYPE_OBJ,
+                       .u.obj = {
+                               .hash_prefix = (uint8_t *)entry->hash.buf,
+                               .hash_prefix_len = arg->w->stats.object_id_len,
+                               .offsets = entry->offsets,
+                               .offset_len = entry->offset_len,
+                       } };
        if (arg->err < 0)
                goto done;
 
-       reftable_record_from_obj(&rec, &obj_rec);
        arg->err = block_writer_add(arg->w->block_writer, &rec);
        if (arg->err == 0)
                goto done;
@@ -488,7 +494,8 @@ static void write_object_record(void *void_arg, void *key)
        arg->err = block_writer_add(arg->w->block_writer, &rec);
        if (arg->err == 0)
                goto done;
-       obj_rec.offset_len = 0;
+
+       rec.u.obj.offset_len = 0;
        arg->err = block_writer_add(arg->w->block_writer, &rec);
 
        /* Should be able to write into a fresh block. */
index a6d8ec6c1ac72f8b3b95978d11c87dd0b4918c01..c97c626eaa83908408848733d2db32cbbe097931 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -508,9 +508,8 @@ static void read_config(struct repository *repo)
 
        repo->remote_state->current_branch = NULL;
        if (startup_info->have_repository) {
-               int ignore_errno;
                const char *head_ref = refs_resolve_ref_unsafe(
-                       get_main_ref_store(repo), "HEAD", 0, NULL, &flag, &ignore_errno);
+                       get_main_ref_store(repo), "HEAD", 0, NULL, &flag);
                if (head_ref && (flag & REF_ISSYMREF) &&
                    skip_prefix(head_ref, "refs/heads/", &head_ref)) {
                        repo->remote_state->current_branch = make_branch(
index 00ca5571a1ab77e8a1b2d505d59e6ff59d629dd9..b4fbd16cdcc251386a1b77a6920fdbad923cc1ef 100644 (file)
@@ -26,7 +26,7 @@ void prepare_repo_settings(struct repository *r)
        /* Defaults */
        r->settings.index_version = -1;
        r->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;
-       r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT;
+       r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_CONSECUTIVE;
 
        /* Booleans config or default, cascades to other settings */
        repo_cfg_bool(r, "feature.manyfiles", &manyfiles, 0);
@@ -81,10 +81,17 @@ void prepare_repo_settings(struct repository *r)
        }
 
        if (!repo_config_get_string(r, "fetch.negotiationalgorithm", &strval)) {
+               int fetch_default = r->settings.fetch_negotiation_algorithm;
                if (!strcasecmp(strval, "skipping"))
                        r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;
                else if (!strcasecmp(strval, "noop"))
                        r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_NOOP;
+               else if (!strcasecmp(strval, "consecutive"))
+                       r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_CONSECUTIVE;
+               else if (!strcasecmp(strval, "default"))
+                       r->settings.fetch_negotiation_algorithm = fetch_default;
+               else
+                       die("unknown fetch negotiation algorithm '%s'", strval);
        }
 
        /*
index 2b5cf97f31e88934d9b23d96a97cffc60a9606fd..ca837cb9e914aaaf85c39f323ae671ab4eb8c8cb 100644 (file)
@@ -20,7 +20,7 @@ enum untracked_cache_setting {
 };
 
 enum fetch_negotiation_setting {
-       FETCH_NEGOTIATION_DEFAULT,
+       FETCH_NEGOTIATION_CONSECUTIVE,
        FETCH_NEGOTIATION_SKIPPING,
        FETCH_NEGOTIATION_NOOP,
 };
index d83d58df4fbc930b756395a8a99a77be4c1e598f..d26627c59329151ef196e26444a64cb44ad699d4 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -609,19 +609,20 @@ static int try_merge(struct index_state *istate,
                     const struct rerere_id *id, const char *path,
                     mmfile_t *cur, mmbuffer_t *result)
 {
-       int ret;
+       enum ll_merge_result ret;
        mmfile_t base = {NULL, 0}, other = {NULL, 0};
 
        if (read_mmfile(&base, rerere_path(id, "preimage")) ||
-           read_mmfile(&other, rerere_path(id, "postimage")))
-               ret = 1;
-       else
+           read_mmfile(&other, rerere_path(id, "postimage"))) {
+               ret = LL_MERGE_CONFLICT;
+       } else {
                /*
                 * A three-way merge. Note that this honors user-customizable
                 * low-level merge driver settings.
                 */
                ret = ll_merge(result, path, &base, NULL, cur, "", &other, "",
                               istate, NULL);
+       }
 
        free(base.ptr);
        free(other.ptr);
diff --git a/reset.c b/reset.c
index f214df3d96ca218a25c780b2dc79ca4ca76999de..0881e63691508c5c965620eda574d18fdd295789 100644 (file)
--- a/reset.c
+++ b/reset.c
@@ -7,6 +7,7 @@
 #include "tree-walk.h"
 #include "tree.h"
 #include "unpack-trees.h"
+#include "hook.h"
 
 int reset_head(struct repository *r, struct object_id *oid, const char *action,
               const char *switch_to_branch, unsigned flags,
@@ -127,7 +128,7 @@ reset_head_refs:
                                            reflog_head);
        }
        if (run_hook)
-               run_hook_le(NULL, "post-checkout",
+               run_hooks_l("post-checkout",
                            oid_to_hex(orig ? orig : null_oid()),
                            oid_to_hex(oid), "1", NULL);
 
index ad4286fbdde521c3eebc048577bc61a341c9e6ee..d8d326d6b024fb241d161cc725f303927fb53eff 100644 (file)
@@ -273,7 +273,7 @@ static void commit_stack_clear(struct commit_stack *stack)
        stack->nr = stack->alloc = 0;
 }
 
-static void mark_one_parent_uninteresting(struct commit *commit,
+static void mark_one_parent_uninteresting(struct rev_info *revs, struct commit *commit,
                                          struct commit_stack *pending)
 {
        struct commit_list *l;
@@ -290,20 +290,26 @@ static void mark_one_parent_uninteresting(struct commit *commit,
         * wasn't uninteresting), in which case we need
         * to mark its parents recursively too..
         */
-       for (l = commit->parents; l; l = l->next)
+       for (l = commit->parents; l; l = l->next) {
                commit_stack_push(pending, l->item);
+               if (revs && revs->exclude_first_parent_only)
+                       break;
+       }
 }
 
-void mark_parents_uninteresting(struct commit *commit)
+void mark_parents_uninteresting(struct rev_info *revs, struct commit *commit)
 {
        struct commit_stack pending = COMMIT_STACK_INIT;
        struct commit_list *l;
 
-       for (l = commit->parents; l; l = l->next)
-               mark_one_parent_uninteresting(l->item, &pending);
+       for (l = commit->parents; l; l = l->next) {
+               mark_one_parent_uninteresting(revs, l->item, &pending);
+               if (revs && revs->exclude_first_parent_only)
+                       break;
+       }
 
        while (pending.nr > 0)
-               mark_one_parent_uninteresting(commit_stack_pop(&pending),
+               mark_one_parent_uninteresting(revs, commit_stack_pop(&pending),
                                              &pending);
 
        commit_stack_clear(&pending);
@@ -441,7 +447,7 @@ static struct commit *handle_commit(struct rev_info *revs,
                if (repo_parse_commit(revs->repo, commit) < 0)
                        die("unable to parse commit %s", name);
                if (flags & UNINTERESTING) {
-                       mark_parents_uninteresting(commit);
+                       mark_parents_uninteresting(revs, commit);
 
                        if (!revs->topo_order || !generation_numbers_enabled(the_repository))
                                revs->limited = 1;
@@ -1124,7 +1130,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
                        if (repo_parse_commit_gently(revs->repo, p, 1) < 0)
                                continue;
                        if (p->parents)
-                               mark_parents_uninteresting(p);
+                               mark_parents_uninteresting(revs, p);
                        if (p->object.flags & SEEN)
                                continue;
                        p->object.flags |= (SEEN | NOT_USER_GIVEN);
@@ -1132,6 +1138,8 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
                                commit_list_insert_by_date(p, list);
                        if (queue)
                                prio_queue_put(queue, p);
+                       if (revs->exclude_first_parent_only)
+                               break;
                }
                return 0;
        }
@@ -1422,7 +1430,7 @@ static int limit_list(struct rev_info *revs)
                if (process_parents(revs, commit, &original_list, NULL) < 0)
                        return -1;
                if (obj->flags & UNINTERESTING) {
-                       mark_parents_uninteresting(commit);
+                       mark_parents_uninteresting(revs, commit);
                        slop = still_interesting(original_list, date, slop, &interesting_cache);
                        if (slop)
                                continue;
@@ -2223,6 +2231,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                return argcount;
        } else if (!strcmp(arg, "--first-parent")) {
                revs->first_parent_only = 1;
+       } else if (!strcmp(arg, "--exclude-first-parent-only")) {
+               revs->exclude_first_parent_only = 1;
        } else if (!strcmp(arg, "--ancestry-path")) {
                revs->ancestry_path = 1;
                revs->simplify_history = 0;
@@ -3345,7 +3355,7 @@ static void explore_walk_step(struct rev_info *revs)
                return;
 
        if (c->object.flags & UNINTERESTING)
-               mark_parents_uninteresting(c);
+               mark_parents_uninteresting(revs, c);
 
        for (p = c->parents; p; p = p->next)
                test_flag_and_insert(&info->explore_queue, p->item, TOPO_WALK_EXPLORED);
index 3f66147bfd390abdd98de4f366014bdce88179c2..e16d06f29d09af578b7837ef4430e3f7395b6e1c 100644 (file)
@@ -158,6 +158,7 @@ struct rev_info {
                        bisect:1,
                        ancestry_path:1,
                        first_parent_only:1,
+                       exclude_first_parent_only:1,
                        line_level_traverse:1,
                        tree_blobs_in_commit_order:1,
 
@@ -195,7 +196,8 @@ struct rev_info {
                        combine_merges:1,
                        combined_all_paths:1,
                        dense_combined_merges:1,
-                       first_parent_merges:1;
+                       first_parent_merges:1,
+                       remerge_diff:1;
 
        /* Format info */
        int             show_notes;
@@ -315,6 +317,9 @@ struct rev_info {
 
        /* misc. flags related to '--no-kept-objects' */
        unsigned keep_pack_cache_flags;
+
+       /* Location where temporary objects for remerge-diff are written. */
+       struct tmp_objdir *remerge_objdir;
 };
 
 int ref_excluded(struct string_list *, const char *path);
@@ -398,7 +403,7 @@ const char *get_revision_mark(const struct rev_info *revs,
 void put_revision_mark(const struct rev_info *revs,
                       const struct commit *commit);
 
-void mark_parents_uninteresting(struct commit *commit);
+void mark_parents_uninteresting(struct rev_info *revs, struct commit *commit);
 void mark_tree_uninteresting(struct repository *r, struct tree *tree);
 void mark_trees_uninteresting_sparse(struct repository *r, struct oidset *trees);
 
index 69dde42f1e7f5e5886f2ea2d9caba02dd9ab8eff..a8501e38cebe50f6a1fefb6d31d92ce049b96ac3 100644 (file)
@@ -1307,39 +1307,6 @@ int async_with_fork(void)
 #endif
 }
 
-int run_hook_ve(const char *const *env, const char *name, va_list args)
-{
-       struct child_process hook = CHILD_PROCESS_INIT;
-       const char *p;
-
-       p = find_hook(name);
-       if (!p)
-               return 0;
-
-       strvec_push(&hook.args, p);
-       while ((p = va_arg(args, const char *)))
-               strvec_push(&hook.args, p);
-       if (env)
-               strvec_pushv(&hook.env_array, (const char **)env);
-       hook.no_stdin = 1;
-       hook.stdout_to_stderr = 1;
-       hook.trace2_hook_name = name;
-
-       return run_command(&hook);
-}
-
-int run_hook_le(const char *const *env, const char *name, ...)
-{
-       va_list args;
-       int ret;
-
-       va_start(args, name);
-       ret = run_hook_ve(env, name, args);
-       va_end(args);
-
-       return ret;
-}
-
 struct io_pump {
        /* initialized by caller */
        int fd;
index 2be5f5d6422e54b84df300abd41974df3cd169da..07bed6c31b4e9f065b9ae0a4490cfd56adada0c7 100644 (file)
@@ -220,23 +220,6 @@ int finish_command_in_signal(struct child_process *);
  */
 int run_command(struct child_process *);
 
-/**
- * Run a hook.
- * The first argument is a pathname to an index file, or NULL
- * if the hook uses the default index file or no index is needed.
- * The second argument is the name of the hook.
- * The further arguments correspond to the hook arguments.
- * The last argument has to be NULL to terminate the arguments list.
- * If the hook does not exist or is not executable, the return
- * value will be zero.
- * If it is executable, the hook will be executed and the exit
- * status of the hook is returned.
- * On execution, .stdout_to_stderr and .no_stdin will be set.
- */
-LAST_ARG_MUST_BE_NULL
-int run_hook_le(const char *const *env, const char *name, ...);
-int run_hook_ve(const char *const *env, const char *name, va_list args);
-
 /*
  * Trigger an auto-gc
  */
index 5213d16e97174adbf10fc487edb0f3d3a726f7d7..fb978a53a0fa8c28fdcf1b826be2f0f3a4929134 100644 (file)
@@ -1281,7 +1281,6 @@ void print_commit_summary(struct repository *r,
        struct strbuf author_ident = STRBUF_INIT;
        struct strbuf committer_ident = STRBUF_INIT;
        struct ref_store *refs;
-       int resolve_errno;
 
        commit = lookup_commit(r, oid);
        if (!commit)
@@ -1332,12 +1331,9 @@ void print_commit_summary(struct repository *r,
        diff_setup_done(&rev.diffopt);
 
        refs = get_main_ref_store(the_repository);
-       head = refs_resolve_ref_unsafe(refs, "HEAD", 0, NULL, NULL,
-                                      &resolve_errno);
-       if (!head) {
-               errno = resolve_errno;
-               die_errno(_("unable to resolve HEAD after creating commit"));
-       }
+       head = refs_resolve_ref_unsafe(refs, "HEAD", 0, NULL, NULL);
+       if (!head)
+               die(_("unable to resolve HEAD after creating commit"));
        if (!strcmp(head, "HEAD"))
                head = _("detached HEAD");
        else
index 9ed18eb8849b27856b8cc409921757f7f069b28b..71e5876f3776e924150cf308928262e2ff0cc16e 100644 (file)
--- a/shallow.c
+++ b/shallow.c
@@ -603,7 +603,7 @@ static int mark_uninteresting(const char *refname, const struct object_id *oid,
        if (!commit)
                return 0;
        commit->object.flags |= UNINTERESTING;
-       mark_parents_uninteresting(commit);
+       mark_parents_uninteresting(NULL, commit);
        return 0;
 }
 
index a1d505d50e98cfcc7bbe8482d74769c10e56aa04..08f54747bb49689df7396b75a57b80de37e35f51 100644 (file)
@@ -136,7 +136,7 @@ static int is_sparse_index_allowed(struct index_state *istate, int flags)
                /*
                 * The sparse index is not (yet) integrated with a split index.
                 */
-               if (istate->split_index)
+               if (istate->split_index || git_env_bool("GIT_TEST_SPLIT_INDEX", 0))
                        return 0;
                /*
                 * The GIT_TEST_SPARSE_INDEX environment variable triggers the
index 8e52e891c3bc34d35eab1124302d6f0294d9cedb..9d0ccc30d00e35965f47d55253166f3d8ce142e2 100644 (file)
@@ -5,6 +5,9 @@
 struct split_index *init_split_index(struct index_state *istate)
 {
        if (!istate->split_index) {
+               if (istate->sparse_index)
+                       die(_("cannot use split index with a sparse index"));
+
                CALLOC_ARRAY(istate->split_index, 1);
                istate->split_index->refcount = 1;
        }
index 6cbaf39f7b6ccbb265d77dd17d8c3146d629ca90..7ff12467cdbed54326e2209a39fda4ae68b02f36 100644 (file)
@@ -48,15 +48,9 @@ void git_stable_qsort(void *b, size_t n, size_t s,
                      int (*cmp)(const void *, const void *))
 {
        const size_t size = st_mult(n, s);
-       char buf[1024];
-
-       if (size < sizeof(buf)) {
-               /* The temporary array fits on the small on-stack buffer. */
-               msort_with_tmp(b, n, s, cmp, buf);
-       } else {
-               /* It's somewhat large, so malloc it.  */
-               char *tmp = xmalloc(size);
-               msort_with_tmp(b, n, s, cmp, tmp);
-               free(tmp);
-       }
+       char *tmp;
+
+       tmp = xmalloc(size);
+       msort_with_tmp(b, n, s, cmp, tmp);
+       free(tmp);
 }
diff --git a/t/helper/test-csprng.c b/t/helper/test-csprng.c
new file mode 100644 (file)
index 0000000..65d1497
--- /dev/null
@@ -0,0 +1,29 @@
+#include "test-tool.h"
+#include "git-compat-util.h"
+
+
+int cmd__csprng(int argc, const char **argv)
+{
+       unsigned long count;
+       unsigned char buf[1024];
+
+       if (argc > 2) {
+               fprintf(stderr, "usage: %s [<size>]\n", argv[0]);
+               return 2;
+       }
+
+       count = (argc == 2) ? strtoul(argv[1], NULL, 0) : -1L;
+
+       while (count) {
+               unsigned long chunk = count < sizeof(buf) ? count : sizeof(buf);
+               if (csprng_bytes(buf, chunk) < 0) {
+                       perror("failed to read");
+                       return 5;
+               }
+               if (fwrite(buf, chunk, 1, stdout) != chunk)
+                       return 1;
+               count -= chunk;
+       }
+
+       return 0;
+}
index 3e4ddaee70557690e14c09ad5da34ddbab4a781a..9646d85fc84a9e52ff4c05e7614a9582c0fc3b7e 100644 (file)
@@ -180,10 +180,9 @@ static int cmd_resolve_ref(struct ref_store *refs, const char **argv)
        int resolve_flags = arg_flags(*argv++, "resolve-flags", empty_flags);
        int flags;
        const char *ref;
-       int ignore_errno;
 
        ref = refs_resolve_ref_unsafe(refs, refname, resolve_flags,
-                                     &oid, &flags, &ignore_errno);
+                                     &oid, &flags);
        printf("%s %s 0x%x\n", oid_to_hex(&oid), ref ? ref : "(null)", flags);
        return ref ? 0 : 1;
 }
index 26b03d7b7892ee68f7498a1f5cff4ae364190c16..1f0a28cbb64de427ff8d8c542e0b1166250093e2 100644 (file)
@@ -3,15 +3,16 @@
 
 int cmd__reftable(int argc, const char **argv)
 {
+       /* test from simple to complex. */
        basics_test_main(argc, argv);
+       record_test_main(argc, argv);
        block_test_main(argc, argv);
-       merged_test_main(argc, argv);
+       tree_test_main(argc, argv);
        pq_test_main(argc, argv);
-       record_test_main(argc, argv);
-       refname_test_main(argc, argv);
        readwrite_test_main(argc, argv);
+       merged_test_main(argc, argv);
        stack_test_main(argc, argv);
-       tree_test_main(argc, argv);
+       refname_test_main(argc, argv);
        return 0;
 }
 
index 338a57b104d689a843df92b8adc0d6d0381252be..e6ec69cf326a3babac7bac7df9a03026b0c16d5a 100644 (file)
@@ -20,6 +20,7 @@ static struct test_cmd cmds[] = {
        { "chmtime", cmd__chmtime },
        { "config", cmd__config },
        { "crontab", cmd__crontab },
+       { "csprng", cmd__csprng },
        { "ctype", cmd__ctype },
        { "date", cmd__date },
        { "delta", cmd__delta },
index 48cee1f4a2d9855e10897289b08191905f208730..20756eefddac8394b9e9f2625909f1833b2fec02 100644 (file)
@@ -10,6 +10,7 @@ int cmd__bloom(int argc, const char **argv);
 int cmd__chmtime(int argc, const char **argv);
 int cmd__config(int argc, const char **argv);
 int cmd__crontab(int argc, const char **argv);
+int cmd__csprng(int argc, const char **argv);
 int cmd__ctype(int argc, const char **argv);
 int cmd__date(int argc, const char **argv);
 int cmd__delta(int argc, const char **argv);
index 21d0392ddac5a2e21f8ce090d87efc6cda4e4d9a..a95537e759b0365db3f80ccf9135838da0cbde5e 100644 (file)
@@ -1,6 +1,9 @@
 # Helpers for scripts testing bitmap functionality; see t5310 for
 # example usage.
 
+objdir=.git/objects
+midx=$objdir/pack/multi-pack-index
+
 # Compare a file containing rev-list bitmap traversal output to its non-bitmap
 # counterpart. You can't just use test_cmp for this, because the two produce
 # subtly different output:
@@ -264,3 +267,185 @@ have_delta () {
 midx_checksum () {
        test-tool read-midx --checksum "$1"
 }
+
+# midx_pack_source <obj>
+midx_pack_source () {
+       test-tool read-midx --show-objects .git/objects | grep "^$1 " | cut -f2
+}
+
+test_rev_exists () {
+       commit="$1"
+       kind="$2"
+
+       test_expect_success "reverse index exists ($kind)" '
+               GIT_TRACE2_EVENT=$(pwd)/event.trace \
+                       git rev-list --test-bitmap "$commit" &&
+
+               if test "rev" = "$kind"
+               then
+                       test_path_is_file $midx-$(midx_checksum $objdir).rev
+               fi &&
+               grep "\"category\":\"load_midx_revindex\",\"key\":\"source\",\"value\":\"$kind\"" event.trace
+       '
+}
+
+midx_bitmap_core () {
+       rev_kind="${1:-midx}"
+
+       setup_bitmap_history
+
+       test_expect_success 'create single-pack midx with bitmaps' '
+               git repack -ad &&
+               git multi-pack-index write --bitmap &&
+               test_path_is_file $midx &&
+               test_path_is_file $midx-$(midx_checksum $objdir).bitmap
+       '
+
+       test_rev_exists HEAD "$rev_kind"
+
+       basic_bitmap_tests
+
+       test_expect_success 'create new additional packs' '
+               for i in $(test_seq 1 16)
+               do
+                       test_commit "$i" &&
+                       git repack -d || return 1
+               done &&
+
+               git checkout -b other2 HEAD~8 &&
+               for i in $(test_seq 1 8)
+               do
+                       test_commit "side-$i" &&
+                       git repack -d || return 1
+               done &&
+               git checkout second
+       '
+
+       test_expect_success 'create multi-pack midx with bitmaps' '
+               git multi-pack-index write --bitmap &&
+
+               ls $objdir/pack/pack-*.pack >packs &&
+               test_line_count = 25 packs &&
+
+               test_path_is_file $midx &&
+               test_path_is_file $midx-$(midx_checksum $objdir).bitmap
+       '
+
+       test_rev_exists HEAD "$rev_kind"
+
+       basic_bitmap_tests
+
+       test_expect_success '--no-bitmap is respected when bitmaps exist' '
+               git multi-pack-index write --bitmap &&
+
+               test_commit respect--no-bitmap &&
+               git repack -d &&
+
+               test_path_is_file $midx &&
+               test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+
+               git multi-pack-index write --no-bitmap &&
+
+               test_path_is_file $midx &&
+               test_path_is_missing $midx-$(midx_checksum $objdir).bitmap &&
+               test_path_is_missing $midx-$(midx_checksum $objdir).rev
+       '
+
+       test_expect_success 'setup midx with base from later pack' '
+               # Write a and b so that "a" is a delta on top of base "b", since Git
+               # prefers to delete contents out of a base rather than add to a shorter
+               # object.
+               test_seq 1 128 >a &&
+               test_seq 1 130 >b &&
+
+               git add a b &&
+               git commit -m "initial commit" &&
+
+               a=$(git rev-parse HEAD:a) &&
+               b=$(git rev-parse HEAD:b) &&
+
+               # In the first pack, "a" is stored as a delta to "b".
+               p1=$(git pack-objects .git/objects/pack/pack <<-EOF
+               $a
+               $b
+               EOF
+               ) &&
+
+               # In the second pack, "a" is missing, and "b" is not a delta nor base to
+               # any other object.
+               p2=$(git pack-objects .git/objects/pack/pack <<-EOF
+               $b
+               $(git rev-parse HEAD)
+               $(git rev-parse HEAD^{tree})
+               EOF
+               ) &&
+
+               git prune-packed &&
+               # Use the second pack as the preferred source, so that "b" occurs
+               # earlier in the MIDX object order, rendering "a" unusable for pack
+               # reuse.
+               git multi-pack-index write --bitmap --preferred-pack=pack-$p2.idx &&
+
+               have_delta $a $b &&
+               test $(midx_pack_source $a) != $(midx_pack_source $b)
+       '
+
+       rev_list_tests 'full bitmap with backwards delta'
+
+       test_expect_success 'clone with bitmaps enabled' '
+               git clone --no-local --bare . clone-reverse-delta.git &&
+               test_when_finished "rm -fr clone-reverse-delta.git" &&
+
+               git rev-parse HEAD >expect &&
+               git --git-dir=clone-reverse-delta.git rev-parse HEAD >actual &&
+               test_cmp expect actual
+       '
+
+       test_expect_success 'changing the preferred pack does not corrupt bitmaps' '
+               rm -fr repo &&
+               git init repo &&
+               test_when_finished "rm -fr repo" &&
+               (
+                       cd repo &&
+
+                       test_commit A &&
+                       test_commit B &&
+
+                       git rev-list --objects --no-object-names HEAD^ >A.objects &&
+                       git rev-list --objects --no-object-names HEAD^.. >B.objects &&
+
+                       A=$(git pack-objects $objdir/pack/pack <A.objects) &&
+                       B=$(git pack-objects $objdir/pack/pack <B.objects) &&
+
+                       cat >indexes <<-EOF &&
+                       pack-$A.idx
+                       pack-$B.idx
+                       EOF
+
+                       git multi-pack-index write --bitmap --stdin-packs \
+                               --preferred-pack=pack-$A.pack <indexes &&
+                       git rev-list --test-bitmap A &&
+
+                       git multi-pack-index write --bitmap --stdin-packs \
+                               --preferred-pack=pack-$B.pack <indexes &&
+                       git rev-list --test-bitmap A
+               )
+       '
+}
+
+midx_bitmap_partial_tests () {
+       rev_kind="${1:-midx}"
+
+       test_expect_success 'setup partial bitmaps' '
+               test_commit packed &&
+               git repack &&
+               test_commit loose &&
+               git multi-pack-index write --bitmap 2>err &&
+               test_path_is_file $midx &&
+               test_path_is_file $midx-$(midx_checksum $objdir).bitmap
+       '
+
+       test_rev_exists HEAD~ "$rev_kind"
+
+       basic_bitmap_tests HEAD~
+}
index 168329adbc4edeedc98501575ccc9b9c81f0c061..2da25b314499c33166fa65b0996d9ac5ee5aad35 100644 (file)
@@ -3,21 +3,21 @@
 mkdir Z
 for a in N D M
 do
-    for b in N D M
-    do
-        p=$a$b
+       for b in N D M
+       do
+               p=$a$b
        echo This is $p from the original tree. >$p
        echo This is Z/$p from the original tree. >Z/$p
-       test_expect_success \
-           "adding test file $p and Z/$p" \
-           'git update-index --add $p &&
-           git update-index --add Z/$p'
+       test_expect_success "adding test file $p and Z/$p" '
+           git update-index --add $p &&
+           git update-index --add Z/$p
+    '
     done
 done
 echo This is SS from the original tree. >SS
-test_expect_success \
-    'adding test file SS' \
-    'git update-index --add SS'
+test_expect_success 'adding test file SS' '
+       git update-index --add SS
+'
 cat >TT <<\EOF
 This is a trivial merge sample text.
 Branch A is expected to upcase this word, here.
@@ -30,12 +30,12 @@ At the very end, here comes another line, that is
 the word, expected to be upcased by Branch B.
 This concludes the trivial merge sample file.
 EOF
-test_expect_success \
-    'adding test file TT' \
-    'git update-index --add TT'
-test_expect_success \
-    'prepare initial tree' \
-    'tree_O=$(git write-tree)'
+test_expect_success 'adding test file TT' '
+       git update-index --add TT
+'
+test_expect_success 'prepare initial tree' '
+       tree_O=$(git write-tree)
+'
 
 ################################################################
 # Branch A and B makes the changes according to the above matrix.
@@ -45,48 +45,48 @@ test_expect_success \
 
 to_remove=$(echo D? Z/D?)
 rm -f $to_remove
-test_expect_success \
-    'change in branch A (removal)' \
-    'git update-index --remove $to_remove'
+test_expect_success 'change in branch A (removal)' '
+       git update-index --remove $to_remove
+'
 
 for p in M? Z/M?
 do
-    echo This is modified $p in the branch A. >$p
-    test_expect_success \
-       'change in branch A (modification)' \
-        "git update-index $p"
+       echo This is modified $p in the branch A. >$p
+       test_expect_success 'change in branch A (modification)' '
+               git update-index $p
+       '
 done
 
 for p in AN AA Z/AN Z/AA
 do
-    echo This is added $p in the branch A. >$p
-    test_expect_success \
-       'change in branch A (addition)' \
-       "git update-index --add $p"
+       echo This is added $p in the branch A. >$p
+       test_expect_success 'change in branch A (addition)' '
+               git update-index --add $p
+       '
 done
 
 echo This is SS from the modified tree. >SS
 echo This is LL from the modified tree. >LL
-test_expect_success \
-    'change in branch A (addition)' \
-    'git update-index --add LL &&
-     git update-index SS'
+test_expect_success 'change in branch A (addition)' '
+       git update-index --add LL &&
+       git update-index SS
+'
 mv TT TT-
 sed -e '/Branch A/s/word/WORD/g' <TT- >TT
 rm -f TT-
-test_expect_success \
-    'change in branch A (edit)' \
-    'git update-index TT'
+test_expect_success 'change in branch A (edit)' '
+       git update-index TT
+'
 
 mkdir DF
 echo Branch A makes a file at DF/DF, creating a directory DF. >DF/DF
-test_expect_success \
-    'change in branch A (change file to directory)' \
-    'git update-index --add DF/DF'
+test_expect_success 'change in branch A (change file to directory)' '
+       git update-index --add DF/DF
+'
 
-test_expect_success \
-    'recording branch A tree' \
-    'tree_A=$(git write-tree)'
+test_expect_success 'recording branch A tree' '
+       tree_A=$(git write-tree)
+'
 
 ################################################################
 # Branch B
@@ -94,65 +94,65 @@ test_expect_success \
 
 rm -rf [NDMASLT][NDMASLT] Z DF
 mkdir Z
-test_expect_success \
-    'reading original tree and checking out' \
-    'git read-tree $tree_O &&
-     git checkout-index -a'
+test_expect_success 'reading original tree and checking out' '
+       git read-tree $tree_O &&
+       git checkout-index -a
+'
 
 to_remove=$(echo ?D Z/?D)
 rm -f $to_remove
-test_expect_success \
-    'change in branch B (removal)' \
-    "git update-index --remove $to_remove"
+test_expect_success 'change in branch B (removal)' '
+       git update-index --remove $to_remove
+'
 
 for p in ?M Z/?M
 do
-    echo This is modified $p in the branch B. >$p
-    test_expect_success \
-       'change in branch B (modification)' \
-       "git update-index $p"
+       echo This is modified $p in the branch B. >$p
+       test_expect_success 'change in branch B (modification)' '
+               git update-index $p
+       '
 done
 
 for p in NA AA Z/NA Z/AA
 do
-    echo This is added $p in the branch B. >$p
-    test_expect_success \
-       'change in branch B (addition)' \
-       "git update-index --add $p"
+       echo This is added $p in the branch B. >$p
+       test_expect_success 'change in branch B (addition)' '
+               git update-index --add $p
+       '
 done
 echo This is SS from the modified tree. >SS
 echo This is LL from the modified tree. >LL
-test_expect_success \
-    'change in branch B (addition and modification)' \
-    'git update-index --add LL &&
-     git update-index SS'
+test_expect_success 'change in branch B (addition and modification)' '
+       git update-index --add LL &&
+       git update-index SS
+'
 mv TT TT-
 sed -e '/Branch B/s/word/WORD/g' <TT- >TT
 rm -f TT-
-test_expect_success \
-    'change in branch B (modification)' \
-    'git update-index TT'
+test_expect_success 'change in branch B (modification)' '
+       git update-index TT
+'
 
 echo Branch B makes a file at DF. >DF
-test_expect_success \
-    'change in branch B (addition of a file to conflict with directory)' \
-    'git update-index --add DF'
-
-test_expect_success \
-    'recording branch B tree' \
-    'tree_B=$(git write-tree)'
-
-test_expect_success \
-    'keep contents of 3 trees for easy access' \
-    'rm -f .git/index &&
-     git read-tree $tree_O &&
-     mkdir .orig-O &&
-     git checkout-index --prefix=.orig-O/ -f -q -a &&
-     rm -f .git/index &&
-     git read-tree $tree_A &&
-     mkdir .orig-A &&
-     git checkout-index --prefix=.orig-A/ -f -q -a &&
-     rm -f .git/index &&
-     git read-tree $tree_B &&
-     mkdir .orig-B &&
-     git checkout-index --prefix=.orig-B/ -f -q -a'
+test_expect_success 'change in branch B (addition of a file to conflict with directory)' '
+       git update-index --add DF
+'
+
+test_expect_success 'recording branch B tree' '
+       tree_B=$(git write-tree)
+'
+
+test_expect_success 'keep contents of 3 trees for easy access' '
+       rm -f .git/index &&
+       git read-tree $tree_O &&
+       mkdir .orig-O &&
+       git checkout-index --prefix=.orig-O/ -f -q -a &&
+       rm -f .git/index &&
+       git read-tree $tree_A &&
+       mkdir .orig-A &&
+       git checkout-index --prefix=.orig-A/ -f -q -a &&
+       rm -f .git/index &&
+       git read-tree $tree_B &&
+       mkdir .orig-B &&
+       git checkout-index --prefix=.orig-B/ -f -q -a
+'
index cb777c74a24f55624604f782675d3356365a37fb..2a7106b9495ce1190dec2ff1a42bd0cb77ff3f9b 100755 (executable)
@@ -117,5 +117,7 @@ test_perf_on_all git diff
 test_perf_on_all git diff --cached
 test_perf_on_all git blame $SPARSE_CONE/a
 test_perf_on_all git blame $SPARSE_CONE/f3/a
+test_perf_on_all git checkout-index -f --all
+test_perf_on_all git update-index --add --remove $SPARSE_CONE/a
 
 test_done
index 4a5c5c602cfa8f53ecb9e329a20e67a008fb0e00..c5f7ac63b0ab98d0ff3b4924e51f3c899b40eeb5 100755 (executable)
@@ -597,6 +597,12 @@ do
        # auto: core.autocrlf=false and core.eol unset(or native) uses native eol
        checkout_files     auto  "$id" ""     false   ""       $NL   CRLF  CRLF_mix_LF  LF_mix_CR    LF_nul
        checkout_files     auto  "$id" ""     false   native   $NL   CRLF  CRLF_mix_LF  LF_mix_CR    LF_nul
+       # core.autocrlf false, .gitattributes sets eol
+       checkout_files     ""    "$id" "lf"   false   ""       LF    CRLF  CRLF_mix_LF  LF_mix_CR    LF_nul
+       checkout_files     ""    "$id" "crlf" false   ""       CRLF  CRLF  CRLF         CRLF_mix_CR  CRLF_nul
+       # core.autocrlf true, .gitattributes sets eol
+       checkout_files     ""    "$id" "lf"   true    ""       LF    CRLF  CRLF_mix_LF  LF_mix_CR    LF_nul
+       checkout_files     ""    "$id" "crlf" true    ""       CRLF  CRLF  CRLF         CRLF_mix_CR  CRLF_nul
 done
 
 # The rest of the tests are unique; do the usual linting.
index 39382fa1958152ae0cb88edda55cd63c33a15b6e..145eee11df97c9cf63542e49e42f80ecabab1b73 100755 (executable)
@@ -4,6 +4,98 @@ test_description='git cat-file'
 
 . ./test-lib.sh
 
+test_cmdmode_usage () {
+       test_expect_code 129 "$@" 2>err &&
+       grep "^error:.*is incompatible with" err
+}
+
+for switches in \
+       '-e -p' \
+       '-p -t' \
+       '-t -s' \
+       '-s --textconv' \
+       '--textconv --filters' \
+       '--batch-all-objects -e'
+do
+       test_expect_success "usage: cmdmode $switches" '
+               test_cmdmode_usage git cat-file $switches
+       '
+done
+
+test_incompatible_usage () {
+       test_expect_code 129 "$@" 2>err &&
+       grep -E "^(fatal|error):.*(requires|incompatible with|needs)" err
+}
+
+for opt in --batch --batch-check
+do
+       test_expect_success "usage: incompatible options: --path with $opt" '
+               test_incompatible_usage git cat-file --path=foo $opt
+       '
+done
+
+test_missing_usage () {
+       test_expect_code 129 "$@" 2>err &&
+       grep -E "^fatal:.*required" err
+}
+
+short_modes="-e -p -t -s"
+cw_modes="--textconv --filters"
+
+for opt in $cw_modes
+do
+       test_expect_success "usage: $opt requires another option" '
+               test_missing_usage git cat-file $opt
+       '
+done
+
+for opt in $short_modes
+do
+       test_expect_success "usage: $opt requires another option" '
+               test_missing_usage git cat-file $opt
+       '
+
+       for opt2 in --batch \
+               --batch-check \
+               --follow-symlinks \
+               "--path=foo HEAD:some-path.txt"
+       do
+               test_expect_success "usage: incompatible options: $opt and $opt2" '
+                       test_incompatible_usage git cat-file $opt $opt2
+               '
+       done
+done
+
+test_too_many_arguments () {
+       test_expect_code 129 "$@" 2>err &&
+       grep -E "^fatal: too many arguments$" err
+}
+
+for opt in $short_modes $cw_modes
+do
+       args="one two three"
+       test_expect_success "usage: too many arguments: $opt $args" '
+               test_too_many_arguments git cat-file $opt $args
+       '
+
+       for opt2 in --buffer --follow-symlinks
+       do
+               test_expect_success "usage: incompatible arguments: $opt with batch option $opt2" '
+                       test_incompatible_usage git cat-file $opt $opt2
+               '
+       done
+done
+
+for opt in --buffer \
+       --follow-symlinks \
+       --batch-all-objects
+do
+       test_expect_success "usage: bad option combination: $opt without batch mode" '
+               test_incompatible_usage git cat-file $opt &&
+               test_incompatible_usage git cat-file $opt commit HEAD
+       '
+done
+
 echo_without_newline () {
     printf '%s' "$*"
 }
index 42776984fe77912c8bc6a8450935c08134858946..3592d1244243e060fbdeb1916108f1dca0bf1d12 100755 (executable)
@@ -5,6 +5,9 @@ test_description='sparse checkout builtin tests'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+GIT_TEST_SPLIT_INDEX=false
+export GIT_TEST_SPLIT_INDEX
+
 . ./test-lib.sh
 
 list_files() {
@@ -79,6 +82,12 @@ test_expect_success 'git sparse-checkout init' '
        check_files repo a
 '
 
+test_expect_success 'git sparse-checkout init in empty repo' '
+       test_when_finished rm -rf empty-repo blank-template &&
+       git init --template= empty-repo &&
+       git -C empty-repo sparse-checkout init
+'
+
 test_expect_success 'git sparse-checkout list after init' '
        git -C repo sparse-checkout list >actual &&
        cat >expect <<-\EOF &&
@@ -228,36 +237,31 @@ test_expect_success 'sparse-checkout disable' '
 '
 
 test_expect_success 'sparse-index enabled and disabled' '
-       (
-               sane_unset GIT_TEST_SPLIT_INDEX &&
-               git -C repo update-index --no-split-index &&
-
-               git -C repo sparse-checkout init --cone --sparse-index &&
-               test_cmp_config -C repo true index.sparse &&
-               git -C repo ls-files --sparse >sparse &&
-               git -C repo sparse-checkout disable &&
-               git -C repo ls-files --sparse >full &&
-
-               cat >expect <<-\EOF &&
-               @@ -1,4 +1,7 @@
-                a
-               -deep/
-               -folder1/
-               -folder2/
-               +deep/a
-               +deep/deeper1/a
-               +deep/deeper1/deepest/a
-               +deep/deeper2/a
-               +folder1/a
-               +folder2/a
-               EOF
+       git -C repo sparse-checkout init --cone --sparse-index &&
+       test_cmp_config -C repo true index.sparse &&
+       git -C repo ls-files --sparse >sparse &&
+       git -C repo sparse-checkout disable &&
+       git -C repo ls-files --sparse >full &&
 
-               diff -u sparse full | tail -n +3 >actual &&
-               test_cmp expect actual &&
+       cat >expect <<-\EOF &&
+       @@ -1,4 +1,7 @@
+        a
+       -deep/
+       -folder1/
+       -folder2/
+       +deep/a
+       +deep/deeper1/a
+       +deep/deeper1/deepest/a
+       +deep/deeper2/a
+       +folder1/a
+       +folder2/a
+       EOF
+
+       diff -u sparse full | tail -n +3 >actual &&
+       test_cmp expect actual &&
 
-               git -C repo config --list >config &&
-               ! grep index.sparse config
-       )
+       git -C repo config --list >config &&
+       ! grep index.sparse config
 '
 
 test_expect_success 'cone mode: init and set' '
index 4ba16177528c920e816bdd1cf8db117ca5f6519e..f3a059e5af557aacf113909aa06e089d9fd43e8c 100755 (executable)
@@ -593,13 +593,11 @@ test_expect_success 'reset with pathspecs outside sparse definition' '
 
        test_sparse_match git reset update-folder1 -- folder1 &&
        git -C full-checkout reset update-folder1 -- folder1 &&
-       test_sparse_match git status --porcelain=v2 &&
-       test_all_match git rev-parse HEAD:folder1 &&
+       test_all_match git ls-files -s -- folder1 &&
 
        test_sparse_match git reset update-folder2 -- folder2/a &&
        git -C full-checkout reset update-folder2 -- folder2/a &&
-       test_sparse_match git status --porcelain=v2 &&
-       test_all_match git rev-parse HEAD:folder2/a
+       test_all_match git ls-files -s -- folder2/a
 '
 
 test_expect_success 'reset with wildcard pathspec' '
@@ -629,6 +627,173 @@ test_expect_success 'reset with wildcard pathspec' '
        test_all_match git ls-files -s -- folder1
 '
 
+test_expect_success 'update-index modify outside sparse definition' '
+       init_repos &&
+
+       write_script edit-contents <<-\EOF &&
+       echo text >>$1
+       EOF
+
+       # Create & modify folder1/a
+       # Note that this setup is a manual way of reaching the erroneous
+       # condition in which a `skip-worktree` enabled, outside-of-cone file
+       # exists on disk. It is used here to ensure `update-index` is stable
+       # and behaves predictably if such a condition occurs.
+       run_on_sparse mkdir -p folder1 &&
+       run_on_sparse cp ../initial-repo/folder1/a folder1/a &&
+       run_on_all ../edit-contents folder1/a &&
+
+       # If file has skip-worktree enabled, update-index does not modify the
+       # index entry
+       test_sparse_match git update-index folder1/a &&
+       test_sparse_match git status --porcelain=v2 &&
+       test_must_be_empty sparse-checkout-out &&
+
+       # When skip-worktree is disabled (even on files outside sparse cone), file
+       # is updated in the index
+       test_sparse_match git update-index --no-skip-worktree folder1/a &&
+       test_all_match git status --porcelain=v2 &&
+       test_all_match git update-index folder1/a &&
+       test_all_match git status --porcelain=v2
+'
+
+test_expect_success 'update-index --add outside sparse definition' '
+       init_repos &&
+
+       write_script edit-contents <<-\EOF &&
+       echo text >>$1
+       EOF
+
+       # Create folder1, add new file
+       run_on_sparse mkdir -p folder1 &&
+       run_on_all ../edit-contents folder1/b &&
+
+       # The *untracked* out-of-cone file is added to the index because it does
+       # not have a `skip-worktree` bit to signal that it should be ignored
+       # (unlike in `git add`, which will fail due to the file being outside
+       # the sparse checkout definition).
+       test_all_match git update-index --add folder1/b &&
+       test_all_match git status --porcelain=v2
+'
+
+# NEEDSWORK: `--remove`, unlike the rest of `update-index`, does not ignore
+# `skip-worktree` entries by default and will remove them from the index.
+# The `--ignore-skip-worktree-entries` flag must be used in conjunction with
+# `--remove` to ignore the `skip-worktree` entries and prevent their removal
+# from the index.
+test_expect_success 'update-index --remove outside sparse definition' '
+       init_repos &&
+
+       # When --ignore-skip-worktree-entries is _not_ specified:
+       # out-of-cone, not-on-disk files are removed from the index
+       test_sparse_match git update-index --remove folder1/a &&
+       cat >expect <<-EOF &&
+       D       folder1/a
+       EOF
+       test_sparse_match git diff --cached --name-status &&
+       test_cmp expect sparse-checkout-out &&
+
+       # Reset the state
+       test_all_match git reset --hard &&
+
+       # When --ignore-skip-worktree-entries is specified, out-of-cone
+       # (skip-worktree) files are ignored
+       test_sparse_match git update-index --remove --ignore-skip-worktree-entries folder1/a &&
+       test_sparse_match git diff --cached --name-status &&
+       test_must_be_empty sparse-checkout-out &&
+
+       # Reset the state
+       test_all_match git reset --hard &&
+
+       # --force-remove supercedes --ignore-skip-worktree-entries, removing
+       # a skip-worktree file from the index (and disk) when both are specified
+       # with --remove
+       test_sparse_match git update-index --force-remove --ignore-skip-worktree-entries folder1/a &&
+       cat >expect <<-EOF &&
+       D       folder1/a
+       EOF
+       test_sparse_match git diff --cached --name-status &&
+       test_cmp expect sparse-checkout-out
+'
+
+test_expect_success 'update-index with directories' '
+       init_repos &&
+
+       # update-index will exit silently when provided with a directory name
+       # containing a trailing slash
+       test_all_match git update-index deep/ folder1/ &&
+       grep "Ignoring path deep/" sparse-checkout-err &&
+       grep "Ignoring path folder1/" sparse-checkout-err &&
+
+       # When update-index is given a directory name WITHOUT a trailing slash, it will
+       # behave in different ways depending on the status of the directory on disk:
+       # * if it exists, the command exits with an error ("add individual files instead")
+       # * if it does NOT exist (e.g., in a sparse-checkout), it is assumed to be a
+       #   file and either triggers an error ("does not exist  and --remove not passed")
+       #   or is ignored completely (when using --remove)
+       test_all_match test_must_fail git update-index deep &&
+       run_on_all test_must_fail git update-index folder1 &&
+       test_must_fail git -C full-checkout update-index --remove folder1 &&
+       test_sparse_match git update-index --remove folder1 &&
+       test_all_match git status --porcelain=v2
+'
+
+test_expect_success 'update-index --again file outside sparse definition' '
+       init_repos &&
+
+       test_all_match git checkout -b test-reupdate &&
+
+       # Update HEAD without modifying the index to introduce a difference in
+       # folder1/a
+       test_sparse_match git reset --soft update-folder1 &&
+
+       # Because folder1/a differs in the index vs HEAD,
+       # `git update-index --no-skip-worktree --again` will effectively perform
+       # `git update-index --no-skip-worktree folder1/a` and remove the skip-worktree
+       # flag from folder1/a
+       test_sparse_match git update-index --no-skip-worktree --again &&
+       test_sparse_match git status --porcelain=v2 &&
+
+       cat >expect <<-EOF &&
+       D       folder1/a
+       EOF
+       test_sparse_match git diff --name-status &&
+       test_cmp expect sparse-checkout-out
+'
+
+test_expect_success 'update-index --cacheinfo' '
+       init_repos &&
+
+       deep_a_oid=$(git -C full-checkout rev-parse update-deep:deep/a) &&
+       folder2_oid=$(git -C full-checkout rev-parse update-folder2:folder2) &&
+       folder1_a_oid=$(git -C full-checkout rev-parse update-folder1:folder1/a) &&
+
+       test_all_match git update-index --cacheinfo 100644 $deep_a_oid deep/a &&
+       test_all_match git status --porcelain=v2 &&
+
+       # Cannot add sparse directory, even in sparse index case
+       test_all_match test_must_fail git update-index --add --cacheinfo 040000 $folder2_oid folder2/ &&
+
+       # Sparse match only: the new outside-of-cone entry is added *without* skip-worktree,
+       # so `git status` reports it as "deleted" in the worktree
+       test_sparse_match git update-index --add --cacheinfo 100644 $folder1_a_oid folder1/a &&
+       test_sparse_match git status --porcelain=v2 &&
+       cat >expect <<-EOF &&
+       MD folder1/a
+       EOF
+       test_sparse_match git status --short -- folder1/a &&
+       test_cmp expect sparse-checkout-out &&
+
+       # To return folder1/a to "normal" for a sparse checkout (ignored &
+       # outside-of-cone), add the skip-worktree flag.
+       test_sparse_match git update-index --skip-worktree folder1/a &&
+       cat >expect <<-EOF &&
+       S folder1/a
+       EOF
+       test_sparse_match git ls-files -t -- folder1/a &&
+       test_cmp expect sparse-checkout-out
+'
+
 test_expect_success 'merge, cherry-pick, and rebase' '
        init_repos &&
 
@@ -754,6 +919,74 @@ test_expect_success 'cherry-pick with conflicts' '
        test_all_match test_must_fail git cherry-pick to-cherry-pick
 '
 
+test_expect_success 'checkout-index inside sparse definition' '
+       init_repos &&
+
+       run_on_all rm -f deep/a &&
+       test_all_match git checkout-index -- deep/a &&
+       test_all_match git status --porcelain=v2 &&
+
+       echo test >>new-a &&
+       run_on_all cp ../new-a a &&
+       test_all_match test_must_fail git checkout-index -- a &&
+       test_all_match git checkout-index -f -- a &&
+       test_all_match git status --porcelain=v2
+'
+
+test_expect_success 'checkout-index outside sparse definition' '
+       init_repos &&
+
+       # Without --ignore-skip-worktree-bits, outside-of-cone files will trigger
+       # an error
+       test_sparse_match test_must_fail git checkout-index -- folder1/a &&
+       test_i18ngrep "folder1/a has skip-worktree enabled" sparse-checkout-err &&
+       test_path_is_missing folder1/a &&
+
+       # With --ignore-skip-worktree-bits, outside-of-cone files are checked out
+       test_sparse_match git checkout-index --ignore-skip-worktree-bits -- folder1/a &&
+       test_cmp sparse-checkout/folder1/a sparse-index/folder1/a &&
+       test_cmp sparse-checkout/folder1/a full-checkout/folder1/a &&
+
+       run_on_sparse rm -rf folder1 &&
+       echo test >new-a &&
+       run_on_sparse mkdir -p folder1 &&
+       run_on_all cp ../new-a folder1/a &&
+
+       test_all_match test_must_fail git checkout-index --ignore-skip-worktree-bits -- folder1/a &&
+       test_all_match git checkout-index -f --ignore-skip-worktree-bits -- folder1/a &&
+       test_cmp sparse-checkout/folder1/a sparse-index/folder1/a &&
+       test_cmp sparse-checkout/folder1/a full-checkout/folder1/a
+'
+
+test_expect_success 'checkout-index with folders' '
+       init_repos &&
+
+       # Inside checkout definition
+       test_all_match test_must_fail git checkout-index -f -- deep/ &&
+
+       # Outside checkout definition
+       # Note: although all tests fail (as expected), the messaging differs. For
+       # non-sparse index checkouts, the error is that the "file" does not appear
+       # in the index; for sparse checkouts, the error is explicitly that the
+       # entry is a sparse directory.
+       run_on_all test_must_fail git checkout-index -f -- folder1/ &&
+       test_cmp full-checkout-err sparse-checkout-err &&
+       ! test_cmp full-checkout-err sparse-index-err &&
+       grep "is a sparse directory" sparse-index-err
+'
+
+test_expect_success 'checkout-index --all' '
+       init_repos &&
+
+       test_all_match git checkout-index --all &&
+       test_sparse_match test_path_is_missing folder1 &&
+
+       # --ignore-skip-worktree-bits will cause `skip-worktree` files to be
+       # checked out, causing the outside-of-cone `folder1` to exist on-disk
+       test_all_match git checkout-index --ignore-skip-worktree-bits --all &&
+       test_all_match test_path_exists folder1
+'
+
 test_expect_success 'clean' '
        init_repos &&
 
@@ -763,23 +996,42 @@ test_expect_success 'clean' '
        test_all_match git commit -m "ignore bogus files" &&
 
        run_on_sparse mkdir folder1 &&
+       run_on_all mkdir -p deep/untracked-deep &&
        run_on_all touch folder1/bogus &&
+       run_on_all touch folder1/untracked &&
+       run_on_all touch deep/untracked-deep/bogus &&
+       run_on_all touch deep/untracked-deep/untracked &&
 
        test_all_match git status --porcelain=v2 &&
        test_all_match git clean -f &&
        test_all_match git status --porcelain=v2 &&
        test_sparse_match ls &&
        test_sparse_match ls folder1 &&
+       run_on_all test_path_exists folder1/bogus &&
+       run_on_all test_path_is_missing folder1/untracked &&
+       run_on_all test_path_exists deep/untracked-deep/bogus &&
+       run_on_all test_path_exists deep/untracked-deep/untracked &&
+
+       test_all_match git clean -fd &&
+       test_all_match git status --porcelain=v2 &&
+       test_sparse_match ls &&
+       test_sparse_match ls folder1 &&
+       run_on_all test_path_exists folder1/bogus &&
+       run_on_all test_path_exists deep/untracked-deep/bogus &&
+       run_on_all test_path_is_missing deep/untracked-deep/untracked &&
 
        test_all_match git clean -xf &&
        test_all_match git status --porcelain=v2 &&
        test_sparse_match ls &&
        test_sparse_match ls folder1 &&
+       run_on_all test_path_is_missing folder1/bogus &&
+       run_on_all test_path_exists deep/untracked-deep/bogus &&
 
        test_all_match git clean -xdf &&
        test_all_match git status --porcelain=v2 &&
        test_sparse_match ls &&
        test_sparse_match ls folder1 &&
+       run_on_all test_path_is_missing deep/untracked-deep/bogus &&
 
        test_sparse_match test_path_is_dir folder1
 '
@@ -898,6 +1150,8 @@ test_expect_success 'sparse-index is not expanded' '
        echo >>sparse-index/untracked.txt &&
        ensure_not_expanded add . &&
 
+       ensure_not_expanded checkout-index -f a &&
+       ensure_not_expanded checkout-index -f --all &&
        for ref in update-deep update-folder1 update-folder2 update-deep
        do
                echo >>sparse-index/README.md &&
@@ -926,6 +1180,8 @@ test_expect_success 'sparse-index is not expanded' '
        # Wildcard identifies only full sparse directories, no index expansion
        ensure_not_expanded reset deepest -- folder\* &&
 
+       ensure_not_expanded clean -fd &&
+
        ensure_not_expanded checkout -f update-deep &&
        test_config -C sparse-index pull.twohead ort &&
        (
@@ -1001,6 +1257,24 @@ test_expect_success 'sparse index is not expanded: diff' '
        ensure_not_expanded diff --cached
 '
 
+test_expect_success 'sparse index is not expanded: update-index' '
+       init_repos &&
+
+       deep_a_oid=$(git -C full-checkout rev-parse update-deep:deep/a) &&
+       ensure_not_expanded update-index --cacheinfo 100644 $deep_a_oid deep/a &&
+
+       echo "test" >sparse-index/README.md &&
+       echo "test2" >sparse-index/a &&
+       rm -f sparse-index/deep/a &&
+
+       ensure_not_expanded update-index --add README.md &&
+       ensure_not_expanded update-index a &&
+       ensure_not_expanded update-index --remove deep/a &&
+
+       ensure_not_expanded reset --soft update-deep &&
+       ensure_not_expanded update-index --add --remove --again
+'
+
 test_expect_success 'sparse index is not expanded: blame' '
        init_repos &&
 
index 78359f1f4a2d736f44b84075013e647a7fab3660..7dd9b325d90fd1151f5a4202cad60d8a72df7780 100755 (executable)
@@ -2388,4 +2388,122 @@ test_expect_success '--get and --get-all with --fixed-value' '
        test_must_fail git config --file=config --get-regexp --fixed-value fixed+ non-existent
 '
 
+test_expect_success 'includeIf.hasconfig:remote.*.url' '
+       git init hasremoteurlTest &&
+       test_when_finished "rm -rf hasremoteurlTest" &&
+
+       cat >include-this <<-\EOF &&
+       [user]
+               this = this-is-included
+       EOF
+       cat >dont-include-that <<-\EOF &&
+       [user]
+               that = that-is-not-included
+       EOF
+       cat >>hasremoteurlTest/.git/config <<-EOF &&
+       [includeIf "hasconfig:remote.*.url:foourl"]
+               path = "$(pwd)/include-this"
+       [includeIf "hasconfig:remote.*.url:barurl"]
+               path = "$(pwd)/dont-include-that"
+       [remote "foo"]
+               url = foourl
+       EOF
+
+       echo this-is-included >expect-this &&
+       git -C hasremoteurlTest config --get user.this >actual-this &&
+       test_cmp expect-this actual-this &&
+
+       test_must_fail git -C hasremoteurlTest config --get user.that
+'
+
+test_expect_success 'includeIf.hasconfig:remote.*.url respects last-config-wins' '
+       git init hasremoteurlTest &&
+       test_when_finished "rm -rf hasremoteurlTest" &&
+
+       cat >include-two-three <<-\EOF &&
+       [user]
+               two = included-config
+               three = included-config
+       EOF
+       cat >>hasremoteurlTest/.git/config <<-EOF &&
+       [remote "foo"]
+               url = foourl
+       [user]
+               one = main-config
+               two = main-config
+       [includeIf "hasconfig:remote.*.url:foourl"]
+               path = "$(pwd)/include-two-three"
+       [user]
+               three = main-config
+       EOF
+
+       echo main-config >expect-main-config &&
+       echo included-config >expect-included-config &&
+
+       git -C hasremoteurlTest config --get user.one >actual &&
+       test_cmp expect-main-config actual &&
+
+       git -C hasremoteurlTest config --get user.two >actual &&
+       test_cmp expect-included-config actual &&
+
+       git -C hasremoteurlTest config --get user.three >actual &&
+       test_cmp expect-main-config actual
+'
+
+test_expect_success 'includeIf.hasconfig:remote.*.url globs' '
+       git init hasremoteurlTest &&
+       test_when_finished "rm -rf hasremoteurlTest" &&
+
+       printf "[user]\ndss = yes\n" >double-star-start &&
+       printf "[user]\ndse = yes\n" >double-star-end &&
+       printf "[user]\ndsm = yes\n" >double-star-middle &&
+       printf "[user]\nssm = yes\n" >single-star-middle &&
+       printf "[user]\nno = no\n" >no &&
+
+       cat >>hasremoteurlTest/.git/config <<-EOF &&
+       [remote "foo"]
+               url = https://foo/bar/baz
+       [includeIf "hasconfig:remote.*.url:**/baz"]
+               path = "$(pwd)/double-star-start"
+       [includeIf "hasconfig:remote.*.url:**/nomatch"]
+               path = "$(pwd)/no"
+       [includeIf "hasconfig:remote.*.url:https:/**"]
+               path = "$(pwd)/double-star-end"
+       [includeIf "hasconfig:remote.*.url:nomatch:/**"]
+               path = "$(pwd)/no"
+       [includeIf "hasconfig:remote.*.url:https:/**/baz"]
+               path = "$(pwd)/double-star-middle"
+       [includeIf "hasconfig:remote.*.url:https:/**/nomatch"]
+               path = "$(pwd)/no"
+       [includeIf "hasconfig:remote.*.url:https://*/bar/baz"]
+               path = "$(pwd)/single-star-middle"
+       [includeIf "hasconfig:remote.*.url:https://*/baz"]
+               path = "$(pwd)/no"
+       EOF
+
+       git -C hasremoteurlTest config --get user.dss &&
+       git -C hasremoteurlTest config --get user.dse &&
+       git -C hasremoteurlTest config --get user.dsm &&
+       git -C hasremoteurlTest config --get user.ssm &&
+       test_must_fail git -C hasremoteurlTest config --get user.no
+'
+
+test_expect_success 'includeIf.hasconfig:remote.*.url forbids remote url in such included files' '
+       git init hasremoteurlTest &&
+       test_when_finished "rm -rf hasremoteurlTest" &&
+
+       cat >include-with-url <<-\EOF &&
+       [remote "bar"]
+               url = barurl
+       EOF
+       cat >>hasremoteurlTest/.git/config <<-EOF &&
+       [includeIf "hasconfig:remote.*.url:foourl"]
+               path = "$(pwd)/include-with-url"
+       EOF
+
+       # test with any Git command
+       test_must_fail git -C hasremoteurlTest status 2>err &&
+       grep "fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url" err
+'
+
 test_done
index 1a3ee8845d67e92190261e33d93c98f540972f7f..51f829162819740fea8b080db3be6b422452b366 100755 (executable)
@@ -40,6 +40,12 @@ test_expect_success 'delete_refs(FOO, refs/tags/new-tag)' '
        test_must_fail git rev-parse refs/tags/new-tag --
 '
 
+# In reftable, we keep the reflogs around for deleted refs.
+test_expect_success !REFFILES 'delete-reflog(FOO, refs/tags/new-tag)' '
+       $RUN delete-reflog FOO &&
+       $RUN delete-reflog refs/tags/new-tag
+'
+
 test_expect_success 'rename_refs(main, new-main)' '
        git rev-parse main >expected &&
        $RUN rename-ref refs/heads/main refs/heads/new-main &&
@@ -105,7 +111,7 @@ test_expect_success 'delete_reflog(HEAD)' '
        test_must_fail git reflog exists HEAD
 '
 
-test_expect_success 'create-reflog(HEAD)' '
+test_expect_success REFFILES 'create-reflog(HEAD)' '
        $RUN create-reflog HEAD &&
        git reflog exists HEAD
 '
diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh
new file mode 100755 (executable)
index 0000000..29718aa
--- /dev/null
@@ -0,0 +1,134 @@
+#!/bin/sh
+
+test_description='git-hook command'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+test_expect_success 'git hook usage' '
+       test_expect_code 129 git hook &&
+       test_expect_code 129 git hook run &&
+       test_expect_code 129 git hook run -h &&
+       test_expect_code 129 git hook run --unknown 2>err &&
+       grep "unknown option" err
+'
+
+test_expect_success 'git hook run: nonexistent hook' '
+       cat >stderr.expect <<-\EOF &&
+       error: cannot find a hook named test-hook
+       EOF
+       test_expect_code 1 git hook run test-hook 2>stderr.actual &&
+       test_cmp stderr.expect stderr.actual
+'
+
+test_expect_success 'git hook run: nonexistent hook with --ignore-missing' '
+       git hook run --ignore-missing does-not-exist 2>stderr.actual &&
+       test_must_be_empty stderr.actual
+'
+
+test_expect_success 'git hook run: basic' '
+       write_script .git/hooks/test-hook <<-EOF &&
+       echo Test hook
+       EOF
+
+       cat >expect <<-\EOF &&
+       Test hook
+       EOF
+       git hook run test-hook 2>actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git hook run: stdout and stderr both write to our stderr' '
+       write_script .git/hooks/test-hook <<-EOF &&
+       echo >&1 Will end up on stderr
+       echo >&2 Will end up on stderr
+       EOF
+
+       cat >stderr.expect <<-\EOF &&
+       Will end up on stderr
+       Will end up on stderr
+       EOF
+       git hook run test-hook >stdout.actual 2>stderr.actual &&
+       test_cmp stderr.expect stderr.actual &&
+       test_must_be_empty stdout.actual
+'
+
+test_expect_success 'git hook run: exit codes are passed along' '
+       write_script .git/hooks/test-hook <<-EOF &&
+       exit 1
+       EOF
+
+       test_expect_code 1 git hook run test-hook &&
+
+       write_script .git/hooks/test-hook <<-EOF &&
+       exit 2
+       EOF
+
+       test_expect_code 2 git hook run test-hook &&
+
+       write_script .git/hooks/test-hook <<-EOF &&
+       exit 128
+       EOF
+
+       test_expect_code 128 git hook run test-hook &&
+
+       write_script .git/hooks/test-hook <<-EOF &&
+       exit 129
+       EOF
+
+       test_expect_code 129 git hook run test-hook
+'
+
+test_expect_success 'git hook run arg u ments without -- is not allowed' '
+       test_expect_code 129 git hook run test-hook arg u ments
+'
+
+test_expect_success 'git hook run -- pass arguments' '
+       write_script .git/hooks/test-hook <<-\EOF &&
+       echo $1
+       echo $2
+       EOF
+
+       cat >expect <<-EOF &&
+       arg
+       u ments
+       EOF
+
+       git hook run test-hook -- arg "u ments" 2>actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git hook run -- out-of-repo runs excluded' '
+       write_script .git/hooks/test-hook <<-EOF &&
+       echo Test hook
+       EOF
+
+       nongit test_must_fail git hook run test-hook
+'
+
+test_expect_success 'git -c core.hooksPath=<PATH> hook run' '
+       mkdir my-hooks &&
+       write_script my-hooks/test-hook <<-\EOF &&
+       echo Hook ran $1 >>actual
+       EOF
+
+       cat >expect <<-\EOF &&
+       Test hook
+       Hook ran one
+       Hook ran two
+       Hook ran three
+       Hook ran four
+       EOF
+
+       # Test various ways of specifying the path. See also
+       # t1350-config-hooks-path.sh
+       >actual &&
+       git hook run test-hook -- ignored 2>>actual &&
+       git -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual &&
+       git -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual &&
+       git -c core.hooksPath="$PWD/my-hooks" hook run test-hook -- three 2>>actual &&
+       git -c core.hooksPath="$PWD/my-hooks/" hook run test-hook -- four 2>>actual &&
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t2108-update-index-refresh-racy.sh b/t/t2108-update-index-refresh-racy.sh
new file mode 100755 (executable)
index 0000000..bc5f288
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+test_description='update-index refresh tests related to racy timestamps'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+reset_files () {
+       echo content >file &&
+       echo content >other &&
+       test_set_magic_mtime file &&
+       test_set_magic_mtime other
+}
+
+update_assert_changed () {
+       test_set_magic_mtime .git/index &&
+       test_might_fail git update-index "$1" &&
+       ! test_is_magic_mtime .git/index
+}
+
+test_expect_success 'setup' '
+       reset_files &&
+       # we are calling reset_files() a couple of times during tests;
+       # test-tool chmtime does not change the ctime; to not weaken
+       # or even break our tests, disable ctime-checks entirely
+       git config core.trustctime false &&
+       git add file other &&
+       git commit -m "initial import"
+'
+
+test_expect_success '--refresh has no racy timestamps to fix' '
+       reset_files &&
+       # set the index time far enough to the future;
+       # it must be at least 3 seconds for VFAT
+       test_set_magic_mtime .git/index +60 &&
+       git update-index --refresh &&
+       test_is_magic_mtime .git/index +60
+'
+
+test_expect_success '--refresh should fix racy timestamp' '
+       reset_files &&
+       update_assert_changed --refresh
+'
+
+test_expect_success '--really-refresh should fix racy timestamp' '
+       reset_files &&
+       update_assert_changed --really-refresh
+'
+
+test_expect_success '--refresh should fix racy timestamp if other file needs update' '
+       reset_files &&
+       echo content2 >other &&
+       test_set_magic_mtime other &&
+       update_assert_changed --refresh
+'
+
+test_expect_success '--refresh should fix racy timestamp if racy file needs update' '
+       reset_files &&
+       echo content2 >file &&
+       test_set_magic_mtime file &&
+       update_assert_changed --refresh
+'
+
+test_done
index 19c6f4acbf6c8a2872da776e586b327639258901..1e9f7833dd691a8b6ee769158c4684745dd4a8d4 100755 (executable)
@@ -11,7 +11,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
 log_with_names () {
        git rev-list --topo-order --parents --pretty="tformat:%s" HEAD |
-       git name-rev --stdin --name-only --refs=refs/heads/$1
+       git name-rev --annotate-stdin --name-only --refs=refs/heads/$1
 }
 
 
index 207714655f283971f791b4ab2426aa0d7b910080..94537a6b40a136f71d52a3e34552c1f1f7a298ac 100755 (executable)
@@ -326,7 +326,9 @@ test_expect_success 'correct message when there is nothing to do' '
 test_expect_success 'setup again' '
        git reset --hard &&
        test_chmod +x file &&
-       echo content >>file
+       echo content >>file &&
+       test_write_lines A B C D>file2 &&
+       git add file2
 '
 
 # Write the patch file with a new line at the top and bottom
@@ -341,13 +343,27 @@ test_expect_success 'setup patch' '
         content
        +lastline
        \ No newline at end of file
+       diff --git a/file2 b/file2
+       index 8422d40..35b930a 100644
+       --- a/file2
+       +++ b/file2
+       @@ -1,4 +1,5 @@
+       -A
+       +Z
+        B
+       +Y
+        C
+       -D
+       +X
        EOF
 '
 
 # Expected output, diff is similar to the patch but w/ diff at the top
 test_expect_success 'setup expected' '
        echo diff --git a/file b/file >expected &&
-       cat patch |sed "/^index/s/ 100644/ 100755/" >>expected &&
+       sed -e "/^index 180b47c/s/ 100644/ 100755/" \
+           -e /1,5/s//1,4/ \
+           -e /Y/d patch >>expected &&
        cat >expected-output <<-\EOF
        --- a/file
        +++ b/file
@@ -366,6 +382,28 @@ test_expect_success 'setup expected' '
         content
        +lastline
        \ No newline at end of file
+       --- a/file2
+       +++ b/file2
+       @@ -1,4 +1,5 @@
+       -A
+       +Z
+        B
+       +Y
+        C
+       -D
+       +X
+       @@ -1,2 +1,2 @@
+       -A
+       +Z
+        B
+       @@ -2,2 +2,3 @@
+        B
+       +Y
+        C
+       @@ -3,2 +4,2 @@
+        C
+       -D
+       +X
        EOF
 '
 
@@ -373,9 +411,9 @@ test_expect_success 'setup expected' '
 test_expect_success 'add first line works' '
        git commit -am "clear local changes" &&
        git apply patch &&
-       printf "%s\n" s y y | git add -p file 2>error |
-               sed -n -e "s/^([1-2]\/[1-2]) Stage this hunk[^@]*\(@@ .*\)/\1/" \
-                      -e "/^[-+@ \\\\]"/p  >output &&
+       test_write_lines s y y s y n y | git add -p 2>error >raw-output &&
+       sed -n -e "s/^([1-9]\/[1-9]) Stage this hunk[^@]*\(@@ .*\)/\1/" \
+              -e "/^[-+@ \\\\]"/p raw-output >output &&
        test_must_be_empty error &&
        git diff --cached >diff &&
        diff_cmp expected diff &&
index 686747e55a301a661d75c63223a7d682c30dbd4b..b149e2af44185d5a78ff7eff1e2c0efb67f35f4d 100755 (executable)
@@ -1272,7 +1272,6 @@ test_expect_success 'stash works when user.name and user.email are not set' '
        >2 &&
        git add 2 &&
        test_config user.useconfigonly true &&
-       test_config stash.usebuiltin true &&
        (
                sane_unset GIT_AUTHOR_NAME &&
                sane_unset GIT_AUTHOR_EMAIL &&
@@ -1323,20 +1322,6 @@ test_expect_success 'stash handles skip-worktree entries nicely' '
        git rev-parse --verify refs/stash:A.t
 '
 
-test_expect_success 'stash -c stash.useBuiltin=false warning ' '
-       expected="stash.useBuiltin support has been removed" &&
-
-       git -c stash.useBuiltin=false stash 2>err &&
-       test_i18ngrep "$expected" err &&
-       env GIT_TEST_STASH_USE_BUILTIN=false git stash 2>err &&
-       test_i18ngrep "$expected" err &&
-
-       git -c stash.useBuiltin=true stash 2>err &&
-       test_must_be_empty err &&
-       env GIT_TEST_STASH_USE_BUILTIN=true git stash 2>err &&
-       test_must_be_empty err
-'
-
 test_expect_success 'git stash succeeds despite directory/file change' '
        test_create_repo directory_file_switch_v1 &&
        (
diff --git a/t/t4069-remerge-diff.sh b/t/t4069-remerge-diff.sh
new file mode 100755 (executable)
index 0000000..35f9495
--- /dev/null
@@ -0,0 +1,291 @@
+#!/bin/sh
+
+test_description='remerge-diff handling'
+
+. ./test-lib.sh
+
+# This test is ort-specific
+if test "${GIT_TEST_MERGE_ALGORITHM}" != ort
+then
+       skip_all="GIT_TEST_MERGE_ALGORITHM != ort"
+       test_done
+fi
+
+test_expect_success 'setup basic merges' '
+       test_write_lines 1 2 3 4 5 6 7 8 9 >numbers &&
+       git add numbers &&
+       git commit -m base &&
+
+       git branch feature_a &&
+       git branch feature_b &&
+       git branch feature_c &&
+
+       git branch ab_resolution &&
+       git branch bc_resolution &&
+
+       git checkout feature_a &&
+       test_write_lines 1 2 three 4 5 6 7 eight 9 >numbers &&
+       git commit -a -m change_a &&
+
+       git checkout feature_b &&
+       test_write_lines 1 2 tres 4 5 6 7 8 9 >numbers &&
+       git commit -a -m change_b &&
+
+       git checkout feature_c &&
+       test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&
+       git commit -a -m change_c &&
+
+       git checkout bc_resolution &&
+       git merge --ff-only feature_b &&
+       # no conflict
+       git merge feature_c &&
+
+       git checkout ab_resolution &&
+       git merge --ff-only feature_a &&
+       # conflicts!
+       test_must_fail git merge feature_b &&
+       # Resolve conflict...and make another change elsewhere
+       test_write_lines 1 2 drei 4 5 6 7 acht 9 >numbers &&
+       git add numbers &&
+       git merge --continue
+'
+
+test_expect_success 'remerge-diff on a clean merge' '
+       git log -1 --oneline bc_resolution >expect &&
+       git show --oneline --remerge-diff bc_resolution >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'remerge-diff with both a resolved conflict and an unrelated change' '
+       git log -1 --oneline ab_resolution >tmp &&
+       cat <<-EOF >>tmp &&
+       diff --git a/numbers b/numbers
+       remerge CONFLICT (content): Merge conflict in numbers
+       index a1fb731..6875544 100644
+       --- a/numbers
+       +++ b/numbers
+       @@ -1,13 +1,9 @@
+        1
+        2
+       -<<<<<<< b0ed5cb (change_a)
+       -three
+       -=======
+       -tres
+       ->>>>>>> 6cd3f82 (change_b)
+       +drei
+        4
+        5
+        6
+        7
+       -eight
+       +acht
+        9
+       EOF
+       # Hashes above are sha1; rip them out so test works with sha256
+       sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+       git show --oneline --remerge-diff ab_resolution >tmp &&
+       sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'setup non-content conflicts' '
+       git switch --orphan base &&
+
+       test_write_lines 1 2 3 4 5 6 7 8 9 >numbers &&
+       test_write_lines a b c d e f g h i >letters &&
+       test_write_lines in the way >content &&
+       git add numbers letters content &&
+       git commit -m base &&
+
+       git branch side1 &&
+       git branch side2 &&
+
+       git checkout side1 &&
+       test_write_lines 1 2 three 4 5 6 7 8 9 >numbers &&
+       git mv letters letters_side1 &&
+       git mv content file_or_directory &&
+       git add numbers &&
+       git commit -m side1 &&
+
+       git checkout side2 &&
+       git rm numbers &&
+       git mv letters letters_side2 &&
+       mkdir file_or_directory &&
+       echo hello >file_or_directory/world &&
+       git add file_or_directory/world &&
+       git commit -m side2 &&
+
+       git checkout -b resolution side1 &&
+       test_must_fail git merge side2 &&
+       test_write_lines 1 2 three 4 5 6 7 8 9 >numbers &&
+       git add numbers &&
+       git add letters_side1 &&
+       git rm letters &&
+       git rm letters_side2 &&
+       git add file_or_directory~HEAD &&
+       git mv file_or_directory~HEAD wanted_content &&
+       git commit -m resolved
+'
+
+test_expect_success 'remerge-diff with non-content conflicts' '
+       git log -1 --oneline resolution >tmp &&
+       cat <<-EOF >>tmp &&
+       diff --git a/file_or_directory~HASH (side1) b/wanted_content
+       similarity index 100%
+       rename from file_or_directory~HASH (side1)
+       rename to wanted_content
+       remerge CONFLICT (file/directory): directory in the way of file_or_directory from HASH (side1); moving it to file_or_directory~HASH (side1) instead.
+       diff --git a/letters b/letters
+       remerge CONFLICT (rename/rename): letters renamed to letters_side1 in HASH (side1) and to letters_side2 in HASH (side2).
+       diff --git a/letters_side2 b/letters_side2
+       deleted file mode 100644
+       index b236ae5..0000000
+       --- a/letters_side2
+       +++ /dev/null
+       @@ -1,9 +0,0 @@
+       -a
+       -b
+       -c
+       -d
+       -e
+       -f
+       -g
+       -h
+       -i
+       diff --git a/numbers b/numbers
+       remerge CONFLICT (modify/delete): numbers deleted in HASH (side2) and modified in HASH (side1).  Version HASH (side1) of numbers left in tree.
+       EOF
+       # We still have some sha1 hashes above; rip them out so test works
+       # with sha256
+       sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+       git show --oneline --remerge-diff resolution >tmp &&
+       sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'remerge-diff w/ diff-filter=U: all conflict headers, no diff content' '
+       git log -1 --oneline resolution >tmp &&
+       cat <<-EOF >>tmp &&
+       diff --git a/file_or_directory~HASH (side1) b/file_or_directory~HASH (side1)
+       remerge CONFLICT (file/directory): directory in the way of file_or_directory from HASH (side1); moving it to file_or_directory~HASH (side1) instead.
+       diff --git a/letters b/letters
+       remerge CONFLICT (rename/rename): letters renamed to letters_side1 in HASH (side1) and to letters_side2 in HASH (side2).
+       diff --git a/numbers b/numbers
+       remerge CONFLICT (modify/delete): numbers deleted in HASH (side2) and modified in HASH (side1).  Version HASH (side1) of numbers left in tree.
+       EOF
+       # We still have some sha1 hashes above; rip them out so test works
+       # with sha256
+       sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+       git show --oneline --remerge-diff --diff-filter=U resolution >tmp &&
+       sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'remerge-diff w/ diff-filter=R: relevant file + conflict header' '
+       git log -1 --oneline resolution >tmp &&
+       cat <<-EOF >>tmp &&
+       diff --git a/file_or_directory~HASH (side1) b/wanted_content
+       similarity index 100%
+       rename from file_or_directory~HASH (side1)
+       rename to wanted_content
+       remerge CONFLICT (file/directory): directory in the way of file_or_directory from HASH (side1); moving it to file_or_directory~HASH (side1) instead.
+       EOF
+       # We still have some sha1 hashes above; rip them out so test works
+       # with sha256
+       sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+       git show --oneline --remerge-diff --diff-filter=R resolution >tmp &&
+       sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'remerge-diff w/ pathspec: limits to relevant file including conflict header' '
+       git log -1 --oneline resolution >tmp &&
+       cat <<-EOF >>tmp &&
+       diff --git a/letters b/letters
+       remerge CONFLICT (rename/rename): letters renamed to letters_side1 in HASH (side1) and to letters_side2 in HASH (side2).
+       diff --git a/letters_side2 b/letters_side2
+       deleted file mode 100644
+       index b236ae5..0000000
+       --- a/letters_side2
+       +++ /dev/null
+       @@ -1,9 +0,0 @@
+       -a
+       -b
+       -c
+       -d
+       -e
+       -f
+       -g
+       -h
+       -i
+       EOF
+       # We still have some sha1 hashes above; rip them out so test works
+       # with sha256
+       sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+       git show --oneline --remerge-diff resolution -- "letters*" >tmp &&
+       sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'setup non-content conflicts' '
+       git switch --orphan newbase &&
+
+       test_write_lines 1 2 3 4 5 6 7 8 9 >numbers &&
+       git add numbers &&
+       git commit -m base &&
+
+       git branch newside1 &&
+       git branch newside2 &&
+
+       git checkout newside1 &&
+       test_write_lines 1 2 three 4 5 6 7 8 9 >numbers &&
+       git add numbers &&
+       git commit -m side1 &&
+
+       git checkout newside2 &&
+       test_write_lines 1 2 drei 4 5 6 7 8 9 >numbers &&
+       git add numbers &&
+       git commit -m side2 &&
+
+       git checkout -b newresolution newside1 &&
+       test_must_fail git merge newside2 &&
+       git checkout --theirs numbers &&
+       git add -u numbers &&
+       git commit -m resolved
+'
+
+test_expect_success 'remerge-diff turns off history simplification' '
+       git log -1 --oneline newresolution >tmp &&
+       cat <<-EOF >>tmp &&
+       diff --git a/numbers b/numbers
+       remerge CONFLICT (content): Merge conflict in numbers
+       index 070e9e7..5335e78 100644
+       --- a/numbers
+       +++ b/numbers
+       @@ -1,10 +1,6 @@
+        1
+        2
+       -<<<<<<< 96f1e45 (side1)
+       -three
+       -=======
+        drei
+       ->>>>>>> 4fd522f (side2)
+        4
+        5
+        6
+       EOF
+       # We still have some sha1 hashes above; rip them out so test works
+       # with sha256
+       sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+       git show --oneline --remerge-diff newresolution -- numbers >tmp &&
+       sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+       test_cmp expect actual
+'
+
+test_done
index 504955986197224ecc41c15e52fea98a021b9f00..a20d57834931276c0961896bf4724f30b6dc3d09 100755 (executable)
@@ -142,6 +142,19 @@ test_expect_success 'diff-filter=R' '
 
 '
 
+test_expect_success 'multiple --diff-filter bits' '
+
+       git log -M --pretty="format:%s" --diff-filter=R HEAD >expect &&
+       git log -M --pretty="format:%s" --diff-filter=Ra HEAD >actual &&
+       test_cmp expect actual &&
+       git log -M --pretty="format:%s" --diff-filter=aR HEAD >actual &&
+       test_cmp expect actual &&
+       git log -M --pretty="format:%s" \
+               --diff-filter=a --diff-filter=R HEAD >actual &&
+       test_cmp expect actual
+
+'
+
 test_expect_success 'diff-filter=C' '
 
        git log -C -C --pretty="format:%s" --diff-filter=C HEAD >actual &&
@@ -659,7 +672,7 @@ EOF
 
 test_expect_success 'log --graph with full output' '
        git log --graph --date-order --pretty=short |
-               git name-rev --name-only --stdin |
+               git name-rev --name-only --annotate-stdin |
                sed "s/Merge:.*/Merge: A B/;s/ *\$//" >actual &&
        test_cmp expect actual
 '
index 80f4a65b285c55cfc7dce38fb115fb0190de97a2..a730c0db9856c1d92ba840ca07f4c67064d437a1 100755 (executable)
@@ -38,7 +38,7 @@ calc_patch_id () {
        shift
        git patch-id "$@" >patch-id.output &&
        sed "s/ .*//" patch-id.output >patch-id_"$patch_name" &&
-       test_line_count -gt 0 patch-id_"$patch_name"
+       test_line_count -eq 1 patch-id_"$patch_name"
 }
 
 get_top_diff () {
@@ -166,40 +166,67 @@ test_expect_success 'patch-id respects config from subdir' '
        )
 '
 
-cat >nonl <<\EOF
-diff --git i/a w/a
-index e69de29..2e65efe 100644
---- i/a
-+++ w/a
-@@ -0,0 +1 @@
-+a
-\ No newline at end of file
-diff --git i/b w/b
-index e69de29..6178079 100644
---- i/b
-+++ w/b
-@@ -0,0 +1 @@
-+b
-EOF
-
-cat >withnl <<\EOF
-diff --git i/a w/a
-index e69de29..7898192 100644
---- i/a
-+++ w/a
-@@ -0,0 +1 @@
-+a
-diff --git i/b w/b
-index e69de29..6178079 100644
---- i/b
-+++ w/b
-@@ -0,0 +1 @@
-+b
-EOF
-
 test_expect_success 'patch-id handles no-nl-at-eof markers' '
-       cat nonl | calc_patch_id nonl &&
-       cat withnl | calc_patch_id withnl &&
+       cat >nonl <<-\EOF &&
+       diff --git i/a w/a
+       index e69de29..2e65efe 100644
+       --- i/a
+       +++ w/a
+       @@ -0,0 +1 @@
+       +a
+       \ No newline at end of file
+       diff --git i/b w/b
+       index e69de29..6178079 100644
+       --- i/b
+       +++ w/b
+       @@ -0,0 +1 @@
+       +b
+       EOF
+       cat >withnl <<-\EOF &&
+       diff --git i/a w/a
+       index e69de29..7898192 100644
+       --- i/a
+       +++ w/a
+       @@ -0,0 +1 @@
+       +a
+       diff --git i/b w/b
+       index e69de29..6178079 100644
+       --- i/b
+       +++ w/b
+       @@ -0,0 +1 @@
+       +b
+       EOF
+       calc_patch_id nonl <nonl &&
+       calc_patch_id withnl <withnl &&
        test_cmp patch-id_nonl patch-id_withnl
 '
+
+test_expect_success 'patch-id handles diffs with one line of before/after' '
+       cat >diffu1 <<-\EOF &&
+       diff --git a/bar b/bar
+       index bdaf90f..31051f6 100644
+       --- a/bar
+       +++ b/bar
+       @@ -2 +2,2 @@
+        b
+       +c
+       diff --git a/car b/car
+       index 00750ed..2ae5e34 100644
+       --- a/car
+       +++ b/car
+       @@ -1 +1,2 @@
+        3
+       +d
+       diff --git a/foo b/foo
+       index e439850..7146eb8 100644
+       --- a/foo
+       +++ b/foo
+       @@ -2 +2,2 @@
+        a
+       +e
+       EOF
+       calc_patch_id diffu1 <diffu1 &&
+       test_config patchid.stable true &&
+       calc_patch_id diffu1stable <diffu1
+'
 test_done
index d05ab716f6aa1fd6f641f14e308e2e5b80e70af3..f775fc1ce691d5337b31d067cd93944b7bae3bc5 100755 (executable)
@@ -397,4 +397,32 @@ test_expect_success 'pack.preferBitmapTips' '
        )
 '
 
+test_expect_success 'complains about multiple pack bitmaps' '
+       rm -fr repo &&
+       git init repo &&
+       test_when_finished "rm -fr repo" &&
+       (
+               cd repo &&
+
+               test_commit base &&
+
+               git repack -adb &&
+               bitmap="$(ls .git/objects/pack/pack-*.bitmap)" &&
+               mv "$bitmap" "$bitmap.bak" &&
+
+               test_commit other &&
+               git repack -ab &&
+
+               mv "$bitmap.bak" "$bitmap" &&
+
+               find .git/objects/pack -type f -name "*.pack" >packs &&
+               find .git/objects/pack -type f -name "*.bitmap" >bitmaps &&
+               test_line_count = 2 packs &&
+               test_line_count = 2 bitmaps &&
+
+               git rev-list --use-bitmap-index HEAD 2>err &&
+               grep "ignoring extra bitmap file" err
+       )
+'
+
 test_done
index ea889c088a51f635e1e111f1e55d02cd21b53d0f..9d8e249ae8b7f38c4c941bd76ef904cdfe1b3d27 100755 (executable)
@@ -22,8 +22,8 @@ test_expect_success 'disable reflogs' '
 '
 
 create_bogus_ref () {
-       test_when_finished 'rm -f .git/refs/heads/bogus..name' &&
-       echo $bogus >.git/refs/heads/bogus..name
+       test-tool ref-store main update-ref msg "refs/heads/bogus..name" $bogus $ZERO_OID REF_SKIP_REFNAME_VERIFICATION &&
+       test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/bogus..name"
 }
 
 test_expect_success 'create history reachable only from a bogus-named ref' '
@@ -113,7 +113,7 @@ test_expect_success 'pack-refs does not silently delete broken loose ref' '
 # we do not want to count on running pack-refs to
 # actually pack it, as it is perfectly reasonable to
 # skip processing a broken ref
-test_expect_success 'create packed-refs file with broken ref' '
+test_expect_success REFFILES 'create packed-refs file with broken ref' '
        rm -f .git/refs/heads/main &&
        cat >.git/packed-refs <<-EOF &&
        $missing refs/heads/main
@@ -124,13 +124,13 @@ test_expect_success 'create packed-refs file with broken ref' '
        test_cmp expect actual
 '
 
-test_expect_success 'pack-refs does not silently delete broken packed ref' '
+test_expect_success REFFILES 'pack-refs does not silently delete broken packed ref' '
        git pack-refs --all --prune &&
        git rev-parse refs/heads/main >actual &&
        test_cmp expect actual
 '
 
-test_expect_success 'pack-refs does not drop broken refs during deletion' '
+test_expect_success REFFILES  'pack-refs does not drop broken refs during deletion' '
        git update-ref -d refs/heads/other &&
        git rev-parse refs/heads/main >actual &&
        test_cmp expect actual
index e187f90f29e2b592b4074add76d76212b9d0d729..3c1ecc7e25017399381d581258d3d0817a5f4b1b 100755 (executable)
@@ -9,125 +9,13 @@ test_description='exercise basic multi-pack bitmap functionality'
 GIT_TEST_MULTI_PACK_INDEX=0
 GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
 
-objdir=.git/objects
-midx=$objdir/pack/multi-pack-index
+# This test exercise multi-pack bitmap functionality where the object order is
+# stored and read from a special chunk within the MIDX, so use the default
+# behavior here.
+sane_unset GIT_TEST_MIDX_WRITE_REV
+sane_unset GIT_TEST_MIDX_READ_RIDX
 
-# midx_pack_source <obj>
-midx_pack_source () {
-       test-tool read-midx --show-objects .git/objects | grep "^$1 " | cut -f2
-}
-
-setup_bitmap_history
-
-test_expect_success 'enable core.multiPackIndex' '
-       git config core.multiPackIndex true
-'
-
-test_expect_success 'create single-pack midx with bitmaps' '
-       git repack -ad &&
-       git multi-pack-index write --bitmap &&
-       test_path_is_file $midx &&
-       test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
-       test_path_is_file $midx-$(midx_checksum $objdir).rev
-'
-
-basic_bitmap_tests
-
-test_expect_success 'create new additional packs' '
-       for i in $(test_seq 1 16)
-       do
-               test_commit "$i" &&
-               git repack -d || return 1
-       done &&
-
-       git checkout -b other2 HEAD~8 &&
-       for i in $(test_seq 1 8)
-       do
-               test_commit "side-$i" &&
-               git repack -d || return 1
-       done &&
-       git checkout second
-'
-
-test_expect_success 'create multi-pack midx with bitmaps' '
-       git multi-pack-index write --bitmap &&
-
-       ls $objdir/pack/pack-*.pack >packs &&
-       test_line_count = 25 packs &&
-
-       test_path_is_file $midx &&
-       test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
-       test_path_is_file $midx-$(midx_checksum $objdir).rev
-'
-
-basic_bitmap_tests
-
-test_expect_success '--no-bitmap is respected when bitmaps exist' '
-       git multi-pack-index write --bitmap &&
-
-       test_commit respect--no-bitmap &&
-       git repack -d &&
-
-       test_path_is_file $midx &&
-       test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
-       test_path_is_file $midx-$(midx_checksum $objdir).rev &&
-
-       git multi-pack-index write --no-bitmap &&
-
-       test_path_is_file $midx &&
-       test_path_is_missing $midx-$(midx_checksum $objdir).bitmap &&
-       test_path_is_missing $midx-$(midx_checksum $objdir).rev
-'
-
-test_expect_success 'setup midx with base from later pack' '
-       # Write a and b so that "a" is a delta on top of base "b", since Git
-       # prefers to delete contents out of a base rather than add to a shorter
-       # object.
-       test_seq 1 128 >a &&
-       test_seq 1 130 >b &&
-
-       git add a b &&
-       git commit -m "initial commit" &&
-
-       a=$(git rev-parse HEAD:a) &&
-       b=$(git rev-parse HEAD:b) &&
-
-       # In the first pack, "a" is stored as a delta to "b".
-       p1=$(git pack-objects .git/objects/pack/pack <<-EOF
-       $a
-       $b
-       EOF
-       ) &&
-
-       # In the second pack, "a" is missing, and "b" is not a delta nor base to
-       # any other object.
-       p2=$(git pack-objects .git/objects/pack/pack <<-EOF
-       $b
-       $(git rev-parse HEAD)
-       $(git rev-parse HEAD^{tree})
-       EOF
-       ) &&
-
-       git prune-packed &&
-       # Use the second pack as the preferred source, so that "b" occurs
-       # earlier in the MIDX object order, rendering "a" unusable for pack
-       # reuse.
-       git multi-pack-index write --bitmap --preferred-pack=pack-$p2.idx &&
-
-       have_delta $a $b &&
-       test $(midx_pack_source $a) != $(midx_pack_source $b)
-'
-
-rev_list_tests 'full bitmap with backwards delta'
-
-test_expect_success 'clone with bitmaps enabled' '
-       git clone --no-local --bare . clone-reverse-delta.git &&
-       test_when_finished "rm -fr clone-reverse-delta.git" &&
-
-       git rev-parse HEAD >expect &&
-       git --git-dir=clone-reverse-delta.git rev-parse HEAD >actual &&
-       test_cmp expect actual
-'
+midx_bitmap_core
 
 bitmap_reuse_tests() {
        from=$1
@@ -204,17 +92,7 @@ test_expect_success 'missing object closure fails gracefully' '
        )
 '
 
-test_expect_success 'setup partial bitmaps' '
-       test_commit packed &&
-       git repack &&
-       test_commit loose &&
-       git multi-pack-index write --bitmap 2>err &&
-       test_path_is_file $midx &&
-       test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
-       test_path_is_file $midx-$(midx_checksum $objdir).rev
-'
-
-basic_bitmap_tests HEAD~
+midx_bitmap_partial_tests
 
 test_expect_success 'removing a MIDX clears stale bitmaps' '
        rm -fr repo &&
@@ -228,7 +106,6 @@ test_expect_success 'removing a MIDX clears stale bitmaps' '
 
                # Write a MIDX and bitmap; remove the MIDX but leave the bitmap.
                stale_bitmap=$midx-$(midx_checksum $objdir).bitmap &&
-               stale_rev=$midx-$(midx_checksum $objdir).rev &&
                rm $midx &&
 
                # Then write a new MIDX.
@@ -238,9 +115,7 @@ test_expect_success 'removing a MIDX clears stale bitmaps' '
 
                test_path_is_file $midx &&
                test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
-               test_path_is_file $midx-$(midx_checksum $objdir).rev &&
-               test_path_is_missing $stale_bitmap &&
-               test_path_is_missing $stale_rev
+               test_path_is_missing $stale_bitmap
        )
 '
 
@@ -261,7 +136,6 @@ test_expect_success 'pack.preferBitmapTips' '
                git multi-pack-index write --bitmap &&
                test_path_is_file $midx &&
                test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
-               test_path_is_file $midx-$(midx_checksum $objdir).rev &&
 
                test-tool bitmap list-commits | sort >bitmaps &&
                comm -13 bitmaps commits >before &&
@@ -271,7 +145,6 @@ test_expect_success 'pack.preferBitmapTips' '
                        <before | git update-ref --stdin &&
 
                rm -fr $midx-$(midx_checksum $objdir).bitmap &&
-               rm -fr $midx-$(midx_checksum $objdir).rev &&
                rm -fr $midx &&
 
                git -c pack.preferBitmapTips=refs/tags/include \
@@ -309,7 +182,6 @@ test_expect_success 'writing a bitmap with --refs-snapshot' '
                grep "$(git rev-parse two)" bitmaps &&
 
                rm -fr $midx-$(midx_checksum $objdir).bitmap &&
-               rm -fr $midx-$(midx_checksum $objdir).rev &&
                rm -fr $midx &&
 
                # Then again, but with a refs snapshot which only sees
@@ -354,7 +226,6 @@ test_expect_success 'write a bitmap with --refs-snapshot (preferred tips)' '
                ) >snapshot &&
 
                rm -fr $midx-$(midx_checksum $objdir).bitmap &&
-               rm -fr $midx-$(midx_checksum $objdir).rev &&
                rm -fr $midx &&
 
                git multi-pack-index write --bitmap --refs-snapshot=snapshot &&
@@ -395,4 +266,23 @@ test_expect_success 'hash-cache values are propagated from pack bitmaps' '
        )
 '
 
+test_expect_success 'graceful fallback when missing reverse index' '
+       rm -fr repo &&
+       git init repo &&
+       test_when_finished "rm -fr repo" &&
+       (
+               cd repo &&
+
+               test_commit base &&
+
+               # write a pack and MIDX bitmap containing base
+               git repack -adb &&
+               git multi-pack-index write --bitmap &&
+
+               GIT_TEST_MIDX_READ_RIDX=0 \
+                       git rev-list --use-bitmap-index HEAD 2>err &&
+               ! grep "ignoring extra bitmap file" err
+       )
+'
+
 test_done
diff --git a/t/t5327-multi-pack-bitmaps-rev.sh b/t/t5327-multi-pack-bitmaps-rev.sh
new file mode 100755 (executable)
index 0000000..d30ba63
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+test_description='exercise basic multi-pack bitmap functionality (.rev files)'
+
+. ./test-lib.sh
+. "${TEST_DIRECTORY}/lib-bitmap.sh"
+
+# We'll be writing our own midx and bitmaps, so avoid getting confused by the
+# automatic ones.
+GIT_TEST_MULTI_PACK_INDEX=0
+GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
+
+# Unlike t5326, this test exercise multi-pack bitmap functionality where the
+# object order is stored in a separate .rev file.
+GIT_TEST_MIDX_WRITE_REV=1
+GIT_TEST_MIDX_READ_RIDX=0
+export GIT_TEST_MIDX_WRITE_REV
+export GIT_TEST_MIDX_READ_RIDX
+
+midx_bitmap_core rev
+midx_bitmap_partial_tests rev
+
+test_done
index f0dc4e696860a4435a1be1182457325290bb1eb4..ee6d2dde9f35677fb9c83a228839bb7ad405a4fa 100755 (executable)
@@ -927,7 +927,8 @@ test_expect_success 'fetching deepen' '
        )
 '
 
-test_expect_success 'use ref advertisement to prune "have" lines sent' '
+test_negotiation_algorithm_default () {
+       test_when_finished rm -rf clientv0 clientv2 &&
        rm -rf server client &&
        git init server &&
        test_commit -C server both_have_1 &&
@@ -946,7 +947,7 @@ test_expect_success 'use ref advertisement to prune "have" lines sent' '
        rm -f trace &&
        cp -r client clientv0 &&
        GIT_TRACE_PACKET="$(pwd)/trace" git -C clientv0 \
-               fetch origin server_has both_have_2 &&
+               "$@" fetch origin server_has both_have_2 &&
        grep "have $(git -C client rev-parse client_has)" trace &&
        grep "have $(git -C client rev-parse both_have_2)" trace &&
        ! grep "have $(git -C client rev-parse both_have_2^)" trace &&
@@ -954,10 +955,27 @@ test_expect_success 'use ref advertisement to prune "have" lines sent' '
        rm -f trace &&
        cp -r client clientv2 &&
        GIT_TRACE_PACKET="$(pwd)/trace" git -C clientv2 -c protocol.version=2 \
-               fetch origin server_has both_have_2 &&
+               "$@" fetch origin server_has both_have_2 &&
        grep "have $(git -C client rev-parse client_has)" trace &&
        grep "have $(git -C client rev-parse both_have_2)" trace &&
        ! grep "have $(git -C client rev-parse both_have_2^)" trace
+}
+
+test_expect_success 'use ref advertisement to prune "have" lines sent' '
+       test_negotiation_algorithm_default
+'
+
+test_expect_success 'same as last but with config overrides' '
+       test_negotiation_algorithm_default \
+               -c feature.experimental=true \
+               -c fetch.negotiationAlgorithm=consecutive
+'
+
+test_expect_success 'ensure bogus fetch.negotiationAlgorithm yields error' '
+       test_when_finished rm -rf clientv0 &&
+       cp -r client clientv0 &&
+       test_must_fail git -C clientv0 --fetch.negotiationAlgorithm=bogus \
+                      fetch origin server_has both_have_2
 '
 
 test_expect_success 'filtering by size' '
index 20f7110ec108fda462f5e9a0a43221d2c66e7fe1..ef0da0a63b5c2a970d63aece5e95e3efd95d2023 100755 (executable)
@@ -164,6 +164,17 @@ test_expect_success 'fetch --prune --tags with refspec prunes based on refspec'
        git rev-parse sometag
 '
 
+test_expect_success REFFILES 'fetch --prune fails to delete branches' '
+       cd "$D" &&
+       git clone . prune-fail &&
+       cd prune-fail &&
+       git update-ref refs/remotes/origin/extrabranch main &&
+       : this will prevent --prune from locking packed-refs for deleting refs, but adding loose refs still succeeds  &&
+       >.git/packed-refs.new &&
+
+       test_must_fail git fetch --prune origin
+'
+
 test_expect_success 'fetch --atomic works with a single branch' '
        test_when_finished "rm -rf \"$D\"/atomic" &&
 
index 2f04cf9a1c7700c599237cee93c3877a0f9f649e..87881726edf7f3cf187ac76a76c256662d3e16e0 100755 (executable)
@@ -229,6 +229,18 @@ test_expect_success 'push with negotiation proceeds anyway even if negotiation f
        test_i18ngrep "push negotiation failed" err
 '
 
+test_expect_success 'push with negotiation does not attempt to fetch submodules' '
+       mk_empty submodule_upstream &&
+       test_commit -C submodule_upstream submodule_commit &&
+       git submodule add ./submodule_upstream submodule &&
+       mk_empty testrepo &&
+       git push testrepo $the_first_commit:refs/remotes/origin/first_commit &&
+       test_commit -C testrepo unrelated_commit &&
+       git -C testrepo config receive.hideRefs refs/remotes/origin/first_commit &&
+       git -c submodule.recurse=true -c protocol.version=2 -c push.negotiate=1 push testrepo refs/heads/main:refs/remotes/origin/main 2>err &&
+       ! grep "Fetching submodule" err
+'
+
 test_expect_success 'push without wildcard' '
        mk_empty testrepo &&
 
index 93ecfcdd245b4ed86dd71fd6d3bc69fbf2505934..081808009b2e74aa4cb81a768a15701dd4eca9d5 100755 (executable)
@@ -330,6 +330,19 @@ test_expect_success '--rebase --autostash fast forward' '
        test_cmp_rev HEAD to-rebase-ff
 '
 
+test_expect_success '--rebase with rebase.autostash succeeds on ff' '
+       test_when_finished "rm -fr src dst actual" &&
+       git init src &&
+       test_commit -C src "initial" file "content" &&
+       git clone src dst &&
+       test_commit -C src --printf "more_content" file "more content\ncontent\n" &&
+       echo "dirty" >>dst/file &&
+       test_config -C dst rebase.autostash true &&
+       git -C dst pull --rebase >actual 2>&1 &&
+       grep -q "Fast-forward" actual &&
+       grep -q "Applied autostash." actual
+'
+
 test_expect_success '--rebase with conflicts shows advice' '
        test_when_finished "git rebase --abort; git checkout -f to-rebase" &&
        git checkout -b seq &&
index 468bd3e13e1e7d695c95dd06c201279d94ec00ef..6c8d4c6cf1c8e0f98e2a6cccfec798c19b1954a0 100755 (executable)
@@ -149,6 +149,21 @@ test_expect_success 'push with file:// using protocol v1' '
        grep "push< version 1" log
 '
 
+test_expect_success 'cloning branchless tagless but not refless remote' '
+       rm -rf server client &&
+
+       git -c init.defaultbranch=main init server &&
+       echo foo >server/foo.txt &&
+       git -C server add foo.txt &&
+       git -C server commit -m "message" &&
+       git -C server update-ref refs/notbranch/alsonottag HEAD &&
+       git -C server checkout --detach &&
+       git -C server branch -D main &&
+       git -C server symbolic-ref HEAD refs/heads/nonexistentbranch &&
+
+       git -c protocol.version=1 clone "file://$(pwd)/server" client
+'
+
 # Test protocol v1 with 'ssh://' transport
 #
 test_expect_success 'setup ssh wrapper' '
index 710f33e2aa0d1775c21c00a939d04cd129f5b3e5..00ce9aec234685bca8d16018862282290aa64201 100755 (executable)
@@ -619,7 +619,7 @@ test_expect_success 'usage: --negotiate-only without --negotiation-tip' '
        setup_negotiate_only "$SERVER" "$URI" &&
 
        cat >err.expect <<-\EOF &&
-       fatal: --negotiate-only needs one or more --negotiate-tip=*
+       fatal: --negotiate-only needs one or more --negotiation-tip=*
        EOF
 
        test_must_fail git -c protocol.version=2 -C client fetch \
@@ -628,6 +628,18 @@ test_expect_success 'usage: --negotiate-only without --negotiation-tip' '
        test_cmp err.expect err.actual
 '
 
+test_expect_success 'usage: --negotiate-only with --recurse-submodules' '
+       cat >err.expect <<-\EOF &&
+       fatal: options '\''--negotiate-only'\'' and '\''--recurse-submodules'\'' cannot be used together
+       EOF
+
+       test_must_fail git -c protocol.version=2 -C client fetch \
+               --negotiate-only \
+               --recurse-submodules \
+               origin 2>err.actual &&
+       test_cmp err.expect err.actual
+'
+
 test_expect_success 'file:// --negotiate-only' '
        SERVER="server" &&
        URI="file://$(pwd)/server" &&
index aebe4b69e13ea56444dc760cb1b6058702f2d241..6f3e5439771fa62f3b3e308ae878fa2e08a08107 100755 (executable)
@@ -58,7 +58,7 @@ EOF
 
 test_expect_success '--left-right' '
        git rev-list --left-right B...C > actual &&
-       git name-rev --stdin --name-only --refs="*tags/*" \
+       git name-rev --annotate-stdin --name-only --refs="*tags/*" \
                < actual > actual.named &&
        test_cmp expect actual.named
 '
@@ -78,14 +78,14 @@ EOF
 
 test_expect_success '--cherry-pick bar does not come up empty' '
        git rev-list --left-right --cherry-pick B...C -- bar > actual &&
-       git name-rev --stdin --name-only --refs="*tags/*" \
+       git name-rev --annotate-stdin --name-only --refs="*tags/*" \
                < actual > actual.named &&
        test_cmp expect actual.named
 '
 
 test_expect_success 'bar does not come up empty' '
        git rev-list --left-right B...C -- bar > actual &&
-       git name-rev --stdin --name-only --refs="*tags/*" \
+       git name-rev --annotate-stdin --name-only --refs="*tags/*" \
                < actual > actual.named &&
        test_cmp expect actual.named
 '
@@ -97,14 +97,14 @@ EOF
 
 test_expect_success '--cherry-pick bar does not come up empty (II)' '
        git rev-list --left-right --cherry-pick F...E -- bar > actual &&
-       git name-rev --stdin --name-only --refs="*tags/*" \
+       git name-rev --annotate-stdin --name-only --refs="*tags/*" \
                < actual > actual.named &&
        test_cmp expect actual.named
 '
 
 test_expect_success 'name-rev multiple --refs combine inclusive' '
        git rev-list --left-right --cherry-pick F...E -- bar >actual &&
-       git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" \
+       git name-rev --annotate-stdin --name-only --refs="*tags/F" --refs="*tags/E" \
                <actual >actual.named &&
        test_cmp expect actual.named
 '
@@ -116,7 +116,7 @@ EOF
 test_expect_success 'name-rev --refs excludes non-matched patterns' '
        git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect &&
        git rev-list --left-right --cherry-pick F...E -- bar >actual &&
-       git name-rev --stdin --name-only --refs="*tags/F" \
+       git name-rev --annotate-stdin --name-only --refs="*tags/F" \
                <actual >actual.named &&
        test_cmp expect actual.named
 '
@@ -128,14 +128,14 @@ EOF
 test_expect_success 'name-rev --exclude excludes matched patterns' '
        git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect &&
        git rev-list --left-right --cherry-pick F...E -- bar >actual &&
-       git name-rev --stdin --name-only --refs="*tags/*" --exclude="*E" \
+       git name-rev --annotate-stdin --name-only --refs="*tags/*" --exclude="*E" \
                <actual >actual.named &&
        test_cmp expect actual.named
 '
 
 test_expect_success 'name-rev --no-refs clears the refs list' '
        git rev-list --left-right --cherry-pick F...E -- bar >expect &&
-       git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" --no-refs --refs="*tags/G" \
+       git name-rev --annotate-stdin --name-only --refs="*tags/F" --refs="*tags/E" --no-refs --refs="*tags/G" \
                <expect >actual &&
        test_cmp expect actual
 '
@@ -149,7 +149,7 @@ EOF
 
 test_expect_success '--cherry-mark' '
        git rev-list --cherry-mark F...E -- bar > actual &&
-       git name-rev --stdin --name-only --refs="*tags/*" \
+       git name-rev --annotate-stdin --name-only --refs="*tags/*" \
                < actual > actual.named &&
        test_cmp expect actual.named
 '
@@ -163,7 +163,7 @@ EOF
 
 test_expect_success '--cherry-mark --left-right' '
        git rev-list --cherry-mark --left-right F...E -- bar > actual &&
-       git name-rev --stdin --name-only --refs="*tags/*" \
+       git name-rev --annotate-stdin --name-only --refs="*tags/*" \
                < actual > actual.named &&
        test_cmp expect actual.named
 '
@@ -174,14 +174,14 @@ EOF
 
 test_expect_success '--cherry-pick --right-only' '
        git rev-list --cherry-pick --right-only F...E -- bar > actual &&
-       git name-rev --stdin --name-only --refs="*tags/*" \
+       git name-rev --annotate-stdin --name-only --refs="*tags/*" \
                < actual > actual.named &&
        test_cmp expect actual.named
 '
 
 test_expect_success '--cherry-pick --left-only' '
        git rev-list --cherry-pick --left-only E...F -- bar > actual &&
-       git name-rev --stdin --name-only --refs="*tags/*" \
+       git name-rev --annotate-stdin --name-only --refs="*tags/*" \
                < actual > actual.named &&
        test_cmp expect actual.named
 '
@@ -193,7 +193,7 @@ EOF
 
 test_expect_success '--cherry' '
        git rev-list --cherry F...E -- bar > actual &&
-       git name-rev --stdin --name-only --refs="*tags/*" \
+       git name-rev --annotate-stdin --name-only --refs="*tags/*" \
                < actual > actual.named &&
        test_cmp expect actual.named
 '
index 4f7fa8b6c037bd45b2b4a6836609e35b820cbce6..63fcccec32e2467f7ece263195290a95232d0d50 100755 (executable)
@@ -12,17 +12,16 @@ note () {
 }
 
 unnote () {
-       git name-rev --tags --stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\)) |\1 |g"
+       git name-rev --tags --annotate-stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\)) |\1 |g"
 }
 
 #
-# Create a test repo with interesting commit graph:
+# Create a test repo with an interesting commit graph:
 #
-# A--B----------G--H--I--K--L
-#  \  \           /     /
-#   \  \         /     /
-#    C------E---F     J
-#        \_/
+# A-----B-----G--H--I--K--L
+#  \     \      /     /
+#   \     \    /     /
+#    C--D--E--F     J
 #
 # The commits are laid out from left-to-right starting with
 # the root commit A and terminating at the tip commit L.
@@ -142,6 +141,13 @@ check_result 'I B A' --author-date-order -- file
 check_result 'H' --first-parent -- another-file
 check_result 'H' --first-parent --topo-order -- another-file
 
+check_result 'L K I H G B A' --first-parent L
+check_result 'F E D C' --exclude-first-parent-only F ^L
+check_result '' F ^L
+check_result 'L K I H G J' L ^F
+check_result 'L K I H G B J' --exclude-first-parent-only L ^F
+check_result 'L K I H G B' --exclude-first-parent-only --first-parent L ^F
+
 check_result 'E C B A' --full-history E -- lost
 test_expect_success 'full history simplification without parent' '
        printf "%s\n" E C B A >expect &&
index e07b6070e0e7a546118c2f4744b9ab4e9faeff9a..90ff14164009d0e3dcdfeaf0a41763c39d2eab23 100755 (executable)
@@ -23,7 +23,8 @@ note () {
 }
 
 unnote () {
-       git name-rev --tags --stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\))\([  ]\)|\1\2|g"
+       git name-rev --tags --annotate-stdin | \
+       sed -e "s|$OID_REGEX (tags/\([^)]*\))\([        ]\)|\1\2|g"
 }
 
 test_expect_success setup '
index d8af2bb9d2b876c3277d1211468ce4717b83697e..9781b92aeddfee9fbfbfba921a15edea51de50cd 100755 (executable)
@@ -270,7 +270,7 @@ test_expect_success 'name-rev --all' '
        test_cmp expect actual
 '
 
-test_expect_success 'name-rev --stdin' '
+test_expect_success 'name-rev --annotate-stdin' '
        >expect.unsorted &&
        for rev in $(git rev-list --all)
        do
@@ -278,11 +278,16 @@ test_expect_success 'name-rev --stdin' '
                echo "$rev ($name)" >>expect.unsorted || return 1
        done &&
        sort <expect.unsorted >expect &&
-       git rev-list --all | git name-rev --stdin >actual.unsorted &&
+       git rev-list --all | git name-rev --annotate-stdin >actual.unsorted &&
        sort <actual.unsorted >actual &&
        test_cmp expect actual
 '
 
+test_expect_success 'name-rev --stdin deprecated' "
+       git rev-list --all | git name-rev --stdin 2>actual &&
+       grep -E 'warning: --stdin is deprecated' actual
+"
+
 test_expect_success 'describe --contains with the exact tags' '
        echo "A^0" >expect &&
        tag_object=$(git rev-parse refs/tags/A) &&
index eaf48e941e2a3934fde54ac9bdc22f96a7a035cd..b8735c6db4d7c9cc556231a5b0dab091f2f2eb61 100755 (executable)
@@ -108,8 +108,13 @@ test_expect_success 'refuse to merge binary files' '
        printf "\0\0" >binary-file &&
        git add binary-file &&
        git commit -m binary2 &&
-       test_must_fail git merge F >merge.out 2>merge.err &&
-       grep "Cannot merge binary files: binary-file (HEAD vs. F)" merge.err
+       if test "$GIT_TEST_MERGE_ALGORITHM" = ort
+       then
+               test_must_fail git merge F >merge_output
+       else
+               test_must_fail git merge F 2>merge_output
+       fi &&
+       grep "Cannot merge binary files: binary-file (HEAD vs. F)" merge_output
 '
 
 test_expect_success 'mark rename/delete as unmerged' '
index 57e6af5eaa09e52e9f867c957e2117cb66c46e4b..99abefd44b96e6528900bb672ba7de38c6bb9135 100755 (executable)
@@ -221,8 +221,13 @@ test_expect_success 'binary files with union attribute' '
        printf "two\0" >bin.txt &&
        git commit -am two &&
 
-       test_must_fail git merge bin-main 2>stderr &&
-       grep -i "warning.*cannot merge.*HEAD vs. bin-main" stderr
+       if test "$GIT_TEST_MERGE_ALGORITHM" = ort
+       then
+               test_must_fail git merge bin-main >output
+       else
+               test_must_fail git merge bin-main 2>output
+       fi &&
+       grep -i "warning.*cannot merge.*HEAD vs. bin-main" output
 '
 
 test_done
index 035edc40b1ebba46305fb555c903d8b44220f8de..f2bc8a7d2a20f4b6acf19b72f03defb4fa667bbb 100755 (executable)
@@ -697,4 +697,71 @@ test_expect_success 'caching renames only on upstream side, part 2' '
        )
 '
 
+#
+# The following testcase just creates two simple renames (slightly modified
+# on both sides but without conflicting changes), and a directory full of
+# files that are otherwise uninteresting.  The setup is as follows:
+#
+#   base:     unrelated/<BUNCH OF FILES>
+#             numbers
+#             values
+#   upstream: modify: numbers
+#             modify: values
+#   topic:    add: unrelated/foo
+#             modify: numbers
+#             modify: values
+#             rename: numbers -> sequence
+#             rename: values -> progression
+#
+# This is a trivial rename case, but we're curious what happens with a very
+# low renameLimit interacting with the restart optimization trying to notice
+# that unrelated/ looks like a trivial merge candidate.
+#
+test_expect_success 'avoid assuming we detected renames' '
+       git init redo-weirdness &&
+       (
+               cd redo-weirdness &&
+
+               mkdir unrelated &&
+               for i in $(test_seq 1 10)
+               do
+                       >unrelated/$i
+               done &&
+               test_seq  2 10 >numbers &&
+               test_seq 12 20 >values &&
+               git add numbers values unrelated/ &&
+               git commit -m orig &&
+
+               git branch upstream &&
+               git branch topic &&
+
+               git switch upstream &&
+               test_seq  1 10 >numbers &&
+               test_seq 11 20 >values &&
+               git add numbers &&
+               git commit -m "Some tweaks" &&
+
+               git switch topic &&
+
+               >unrelated/foo &&
+               test_seq  2 12 >numbers &&
+               test_seq 12 22 >values &&
+               git add numbers values unrelated/ &&
+               git mv numbers sequence &&
+               git mv values progression &&
+               git commit -m A &&
+
+               #
+               # Actual testing
+               #
+
+               git switch --detach topic^0 &&
+
+               test_must_fail git -c merge.renameLimit=1 rebase upstream &&
+
+               git ls-files -u >actual &&
+               ! test_file_is_empty actual
+       )
+'
+
 test_done
index 05c6c02435d5b99f406470f1cf18acdd0bc9757a..2b7ef6c41a455423c0c62087a440e51bc4c72486 100755 (executable)
@@ -1647,13 +1647,33 @@ test_expect_success '"Initial commit" should not be noted in commit template' '
 '
 
 test_expect_success '--no-optional-locks prevents index update' '
-       test-tool chmtime =1234567890 .git/index &&
+       test_set_magic_mtime .git/index &&
        git --no-optional-locks status &&
-       test-tool chmtime --get .git/index >out &&
-       grep ^1234567890 out &&
+       test_is_magic_mtime .git/index &&
        git status &&
-       test-tool chmtime --get .git/index >out &&
-       ! grep ^1234567890 out
+       ! test_is_magic_mtime .git/index
+'
+
+test_expect_success 'racy timestamps will be fixed for clean worktree' '
+       echo content >racy-dirty &&
+       echo content >racy-racy &&
+       git add racy* &&
+       git commit -m "racy test files" &&
+       # let status rewrite the index, if necessary; after that we expect
+       # no more index writes unless caused by racy timestamps; note that
+       # timestamps may already be racy now (depending on previous tests)
+       git status &&
+       test_set_magic_mtime .git/index &&
+       git status &&
+       ! test_is_magic_mtime .git/index
+'
+
+test_expect_success 'racy timestamps will be fixed for dirty worktree' '
+       echo content2 >racy-dirty &&
+       git status &&
+       test_set_magic_mtime .git/index &&
+       git status &&
+       ! test_is_magic_mtime .git/index
 '
 
 test_done
index e489869dd94daf98f900d14d75a3da97f5f7ab34..5922fb5bdd6459b2bb2f8e1e880d6cd55ffee82b 100755 (executable)
@@ -312,16 +312,13 @@ test_expect_success 'cleans up MIDX when appropriate' '
                checksum=$(midx_checksum $objdir) &&
                test_path_is_file $midx &&
                test_path_is_file $midx-$checksum.bitmap &&
-               test_path_is_file $midx-$checksum.rev &&
 
                test_commit repack-3 &&
                GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adb --write-midx &&
 
                test_path_is_file $midx &&
                test_path_is_missing $midx-$checksum.bitmap &&
-               test_path_is_missing $midx-$checksum.rev &&
                test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
-               test_path_is_file $midx-$(midx_checksum $objdir).rev &&
 
                test_commit repack-4 &&
                GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adb &&
@@ -354,7 +351,6 @@ test_expect_success '--write-midx with preferred bitmap tips' '
                test_line_count = 1 before &&
 
                rm -fr $midx-$(midx_checksum $objdir).bitmap &&
-               rm -fr $midx-$(midx_checksum $objdir).rev &&
                rm -fr $midx &&
 
                # instead of constructing the snapshot ourselves (c.f., the test
index eacd49ade636f5be6166e05d4e200b9a324073be..b067983ba1c6adf152131c96a67fc7a709e6666b 100755 (executable)
@@ -19,6 +19,48 @@ test_expect_success 'setup ' '
        GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00"
 '
 
+test_expect_success 'usage: <bad rev>' '
+       cat >expect <<-\EOF &&
+       fatal: Not a valid object name HEAD2
+       EOF
+       test_must_fail git cat-file --textconv HEAD2 2>actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'usage: <bad rev>:<bad path>' '
+       cat >expect <<-\EOF &&
+       fatal: invalid object name '\''HEAD2'\''.
+       EOF
+       test_must_fail git cat-file --textconv HEAD2:two.bin 2>actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'usage: <rev>:<bad path>' '
+       cat >expect <<-\EOF &&
+       fatal: path '\''two.bin'\'' does not exist in '\''HEAD'\''
+       EOF
+       test_must_fail git cat-file --textconv HEAD:two.bin 2>actual &&
+       test_cmp expect actual
+'
+
+
+test_expect_success 'usage: <rev> with no <path>' '
+       cat >expect <<-\EOF &&
+       fatal: <object>:<path> required, only <object> '\''HEAD'\'' given
+       EOF
+       test_must_fail git cat-file --textconv HEAD 2>actual &&
+       test_cmp expect actual
+'
+
+
+test_expect_success 'usage: <bad rev>:<good (in HEAD) path>' '
+       cat >expect <<-\EOF &&
+       fatal: invalid object name '\''HEAD2'\''.
+       EOF
+       test_must_fail git cat-file --textconv HEAD2:one.bin 2>actual &&
+       test_cmp expect actual
+'
+
 cat >expected <<EOF
 bin: test version 2
 EOF
index aa0c20499ba3e9620739dee08fbdb118b62446e4..84d0f40d76a979b1e98ca911979fd00d64c3f80e 100755 (executable)
@@ -539,7 +539,7 @@ test_expect_success $PREREQ "--validate respects relative core.hooksPath path" '
        test_path_is_file my-hooks.ran &&
        cat >expect <<-EOF &&
        fatal: longline.patch: rejected by sendemail-validate hook
-       fatal: command '"'"'my-hooks/sendemail-validate'"'"' died with exit code 1
+       fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1
        warning: no patches were sent
        EOF
        test_cmp expect actual
@@ -558,7 +558,7 @@ test_expect_success $PREREQ "--validate respects absolute core.hooksPath path" '
        test_path_is_file my-hooks.ran &&
        cat >expect <<-EOF &&
        fatal: longline.patch: rejected by sendemail-validate hook
-       fatal: command '"'"'$hooks_path/sendemail-validate'"'"' died with exit code 1
+       fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1
        warning: no patches were sent
        EOF
        test_cmp expect actual
index 7b2049caa0ce496f5c1b07152a3092363fb71c64..946ef85eb9803f005da614bf4f58aa3949f4d882 100755 (executable)
@@ -1,7 +1,6 @@
 #!/bin/sh
 test_description='git svn rmdir'
 
-TEST_PASSES_SANITIZE_LEAK=true
 . ./lib-git-svn.sh
 
 test_expect_success 'initialize repo' '
index 3320b1f39cf65ca770a6257e62779eb397eda8c2..ead404589eb622edd30adda06ad9684517b7e1f4 100755 (executable)
@@ -5,7 +5,6 @@
 
 test_description='git svn respects rewriteRoot during rebuild'
 
-TEST_PASSES_SANITIZE_LEAK=true
 . ./lib-git-svn.sh
 
 mkdir import
index 9871f5abc933b8f86b33f1efcbea5e1966b55eee..783e3ba0c531e380eda014a3c706d143118809f8 100755 (executable)
@@ -5,7 +5,6 @@
 
 test_description='git svn partial-rebuild tests'
 
-TEST_PASSES_SANITIZE_LEAK=true
 . ./lib-git-svn.sh
 
 test_expect_success 'initialize svnrepo' '
index d9fd111c1052a251b918ad7e66f30e52c2b4574c..d8128430a8d0833b3806aa7211c17008146cec92 100755 (executable)
@@ -5,7 +5,6 @@
 
 test_description='git svn branch for subproject clones'
 
-TEST_PASSES_SANITIZE_LEAK=true
 . ./lib-git-svn.sh
 
 test_expect_success 'initialize svnrepo' '
index c6d6d6ef896818206a90ac5d39ef424e194f290e..24117cb901647b98210c3e90c1e542814390ef7b 100755 (executable)
@@ -1444,6 +1444,144 @@ test_expect_success 'git checkout - with --detach, complete only references' '
        EOF
 '
 
+test_expect_success 'setup sparse-checkout tests' '
+       # set up sparse-checkout repo
+       git init sparse-checkout &&
+       (
+               cd sparse-checkout &&
+               mkdir -p folder1/0/1 folder2/0 folder3 &&
+               touch folder1/0/1/t.txt &&
+               touch folder2/0/t.txt &&
+               touch folder3/t.txt &&
+               git add . &&
+               git commit -am "Initial commit"
+       )
+'
+
+test_expect_success 'sparse-checkout completes subcommands' '
+       test_completion "git sparse-checkout " <<-\EOF
+       list Z
+       init Z
+       set Z
+       add Z
+       reapply Z
+       disable Z
+       EOF
+'
+
+test_expect_success 'cone mode sparse-checkout completes directory names' '
+       # initialize sparse-checkout definitions
+       git -C sparse-checkout sparse-checkout set --cone folder1/0 folder3 &&
+
+       # test tab completion
+       (
+               cd sparse-checkout &&
+               test_completion "git sparse-checkout set f" <<-\EOF
+               folder1/
+               folder2/
+               folder3/
+               EOF
+       ) &&
+
+       (
+               cd sparse-checkout &&
+               test_completion "git sparse-checkout set folder1/" <<-\EOF
+               folder1/0/
+               EOF
+       ) &&
+
+       (
+               cd sparse-checkout &&
+               test_completion "git sparse-checkout set folder1/0/" <<-\EOF
+               folder1/0/1/
+               EOF
+       ) &&
+
+       (
+               cd sparse-checkout/folder1 &&
+               test_completion "git sparse-checkout add 0" <<-\EOF
+               0/
+               EOF
+       )
+'
+
+test_expect_success 'cone mode sparse-checkout completes directory names with spaces and accents' '
+       # reset sparse-checkout
+       git -C sparse-checkout sparse-checkout disable &&
+       (
+               cd sparse-checkout &&
+               mkdir "directory with spaces" &&
+               mkdir "directory-with-áccent" &&
+               >"directory with spaces/randomfile" &&
+               >"directory-with-áccent/randomfile" &&
+               git add . &&
+               git commit -m "Add directory with spaces and directory with accent" &&
+               git sparse-checkout set --cone "directory with spaces" \
+                       "directory-with-áccent" &&
+               test_completion "git sparse-checkout add dir" <<-\EOF &&
+               directory with spaces/
+               directory-with-áccent/
+               EOF
+               rm -rf "directory with spaces" &&
+               rm -rf "directory-with-áccent" &&
+               git add . &&
+               git commit -m "Remove directory with spaces and directory with accent"
+       )
+'
+
+# use FUNNYNAMES to avoid running on Windows, which doesn't permit backslashes or tabs in paths
+test_expect_success FUNNYNAMES 'cone mode sparse-checkout completes directory names with backslashes and tabs' '
+       # reset sparse-checkout
+       git -C sparse-checkout sparse-checkout disable &&
+       (
+               cd sparse-checkout &&
+               mkdir "directory\with\backslashes" &&
+               mkdir "$(printf "directory\twith\ttabs")" &&
+               >"directory\with\backslashes/randomfile" &&
+               >"$(printf "directory\twith\ttabs")/randomfile" &&
+               git add . &&
+               git commit -m "Add directory with backslashes and directory with tabs" &&
+               git sparse-checkout set --cone "directory\with\backslashes" \
+                       "$(printf "directory\twith\ttabs")" &&
+               test_completion "git sparse-checkout add dir" <<-\EOF &&
+               directory\with\backslashes/
+               directory       with    tabs/
+               EOF
+               rm -rf "directory\with\backslashes" &&
+               rm -rf "$(printf "directory\twith\ttabs")" &&
+               git add . &&
+               git commit -m "Remove directory with backslashes and directory with tabs"
+       )
+'
+
+test_expect_success 'non-cone mode sparse-checkout uses bash completion' '
+       # reset sparse-checkout repo to non-cone mode
+       git -C sparse-checkout sparse-checkout disable &&
+       git -C sparse-checkout sparse-checkout set --no-cone &&
+
+       (
+               cd sparse-checkout &&
+               # expected to be empty since we have not configured
+               # custom completion for non-cone mode
+               test_completion "git sparse-checkout set f" <<-\EOF
+
+               EOF
+       )
+'
+
+test_expect_success 'git sparse-checkout set --cone completes directory names' '
+       git -C sparse-checkout sparse-checkout disable &&
+
+       (
+               cd sparse-checkout &&
+               test_completion "git sparse-checkout set --cone f" <<-\EOF
+               folder1/
+               folder2/
+               folder3/
+               EOF
+       )
+'
+
 test_expect_success 'git switch - with -d, complete all references' '
        test_completion "git switch -d " <<-\EOF
        HEAD Z
index c3d38aaccbd526be398416de3a942f6335aa9bb4..85385d2ede7eeaac6febd11772c0e2589cfa0d67 100644 (file)
@@ -1840,3 +1840,36 @@ test_region () {
 test_readlink () {
        perl -le 'print readlink($_) for @ARGV' "$@"
 }
+
+# Set mtime to a fixed "magic" timestamp in mid February 2009, before we
+# run an operation that may or may not touch the file.  If the file was
+# touched, its timestamp will not accidentally have such an old timestamp,
+# as long as your filesystem clock is reasonably correct.  To verify the
+# timestamp, follow up with test_is_magic_mtime.
+#
+# An optional increment to the magic timestamp may be specified as second
+# argument.
+test_set_magic_mtime () {
+       local inc=${2:-0} &&
+       local mtime=$((1234567890 + $inc)) &&
+       test-tool chmtime =$mtime "$1" &&
+       test_is_magic_mtime "$1" $inc
+}
+
+# Test whether the given file has the "magic" mtime set.  This is meant to
+# be used in combination with test_set_magic_mtime.
+#
+# An optional increment to the magic timestamp may be specified as second
+# argument.  Usually, this should be the same increment which was used for
+# the associated test_set_magic_mtime.
+test_is_magic_mtime () {
+       local inc=${2:-0} &&
+       local mtime=$((1234567890 + $inc)) &&
+       echo $mtime >.git/test-mtime-expect &&
+       test-tool chmtime --get "$1" >.git/test-mtime-actual &&
+       test_cmp .git/test-mtime-expect .git/test-mtime-actual
+       local ret=$?
+       rm -f .git/test-mtime-expect
+       rm -f .git/test-mtime-actual
+       return $ret
+}
index 0f7a137c7d8d1ba15cc88f04c471aff4fcf7ecf6..e4716b0b867fcbdf9f2438523a8b4ca5098f1ecc 100644 (file)
@@ -449,6 +449,8 @@ unset VISUAL EMAIL LANGUAGE $("$PERL_PATH" -e '
 unset XDG_CACHE_HOME
 unset XDG_CONFIG_HOME
 unset GITPERLLIB
+unset GIT_TRACE2_PARENT_NAME
+unset GIT_TRACE2_PARENT_SID
 TEST_AUTHOR_LOCALNAME=author
 TEST_AUTHOR_DOMAIN=example.com
 GIT_AUTHOR_EMAIL=${TEST_AUTHOR_LOCALNAME}@${TEST_AUTHOR_DOMAIN}
index 3d38eeab66bfb048c20dd3490f92f2a6e15e040b..adf6033549ee7369c370e333a57db18ac8bdf0d6 100644 (file)
@@ -79,6 +79,11 @@ static void remove_tmp_objdir_on_signal(int signo)
        raise(signo);
 }
 
+void tmp_objdir_discard_objects(struct tmp_objdir *t)
+{
+       remove_dir_recursively(&t->path, REMOVE_DIR_KEEP_TOPLEVEL);
+}
+
 /*
  * These env_* functions are for setting up the child environment; the
  * "replace" variant overrides the value of any existing variable with that
index cda5ec7677881c5a47bb5f2ebf3edc3a3140a73c..76efc7edee5be4a9197a242f526c74b05f49802a 100644 (file)
@@ -46,6 +46,12 @@ int tmp_objdir_migrate(struct tmp_objdir *);
  */
 int tmp_objdir_destroy(struct tmp_objdir *);
 
+/*
+ * Remove all objects from the temporary object directory, while leaving it
+ * around so more objects can be added.
+ */
+void tmp_objdir_discard_objects(struct tmp_objdir *);
+
 /*
  * Add the temporary object directory as an alternate object store in the
  * current process.
index 6f598dcfcdf96fa7b3e4f59bb6a028abef89fdf5..e8f6f6ae6f46ec4580fea61bfb1f2a4046ee3587 100644 (file)
@@ -28,13 +28,11 @@ static void add_head_info(struct worktree *wt)
 {
        int flags;
        const char *target;
-       int ignore_errno;
 
        target = refs_resolve_ref_unsafe(get_worktree_ref_store(wt),
                                         "HEAD",
                                         0,
-                                        &wt->head_oid, &flags,
-                                        &ignore_errno);
+                                        &wt->head_oid, &flags);
        if (!target)
                return;
 
@@ -416,7 +414,6 @@ const struct worktree *find_shared_symref(struct worktree **worktrees,
                const char *symref_target;
                struct ref_store *refs;
                int flags;
-               int ignore_errno;
 
                if (wt->is_bare)
                        continue;
@@ -434,8 +431,7 @@ const struct worktree *find_shared_symref(struct worktree **worktrees,
 
                refs = get_worktree_ref_store(wt);
                symref_target = refs_resolve_ref_unsafe(refs, symref, 0,
-                                                       NULL, &flags,
-                                                       &ignore_errno);
+                                                       NULL, &flags);
                if ((flags & REF_ISSYMREF) &&
                    symref_target && !strcmp(symref_target, target)) {
                        existing = wt;
@@ -563,7 +559,6 @@ int other_head_refs(each_ref_fn fn, void *cb_data)
                struct worktree *wt = *p;
                struct object_id oid;
                int flag;
-               int ignore_errno;
 
                if (wt->is_current)
                        continue;
@@ -573,7 +568,7 @@ int other_head_refs(each_ref_fn fn, void *cb_data)
                if (refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
                                            refname.buf,
                                            RESOLVE_REF_READING,
-                                           &oid, &flag, &ignore_errno))
+                                           &oid, &flag))
                        ret = fn(refname.buf, &oid, flag, cb_data);
                if (ret)
                        break;
index 36e12119d76556a710dbc8da2953a4710e630fdb..3258cdb171f5181b869f547a607a5e35733333c8 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -463,8 +463,6 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
        static const int num_letters = ARRAY_SIZE(letters) - 1;
        static const char x_pattern[] = "XXXXXX";
        static const int num_x = ARRAY_SIZE(x_pattern) - 1;
-       uint64_t value;
-       struct timeval tv;
        char *filename_template;
        size_t len;
        int fd, count;
@@ -485,12 +483,13 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
         * Replace pattern's XXXXXX characters with randomness.
         * Try TMP_MAX different filenames.
         */
-       gettimeofday(&tv, NULL);
-       value = ((uint64_t)tv.tv_usec << 16) ^ tv.tv_sec ^ getpid();
        filename_template = &pattern[len - num_x - suffix_len];
        for (count = 0; count < TMP_MAX; ++count) {
-               uint64_t v = value;
                int i;
+               uint64_t v;
+               if (csprng_bytes(&v, sizeof(v)) < 0)
+                       return error_errno("unable to get random bytes for temporary file");
+
                /* Fill in the random bits. */
                for (i = 0; i < num_x; i++) {
                        filename_template[i] = letters[v % num_letters];
@@ -506,12 +505,6 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
                 */
                if (errno != EEXIST)
                        break;
-               /*
-                * This is a random value.  It is only necessary that
-                * the next TMP_MAX values generated by adding 7777 to
-                * VALUE are different with (module 2^32).
-                */
-               value += 7777;
        }
        /* We return the null string if we can't find a unique file name.  */
        pattern[0] = '\0';
@@ -702,3 +695,69 @@ int open_nofollow(const char *path, int flags)
        return open(path, flags);
 #endif
 }
+
+int csprng_bytes(void *buf, size_t len)
+{
+#if defined(HAVE_ARC4RANDOM) || defined(HAVE_ARC4RANDOM_LIBBSD)
+       /* This function never returns an error. */
+       arc4random_buf(buf, len);
+       return 0;
+#elif defined(HAVE_GETRANDOM)
+       ssize_t res;
+       char *p = buf;
+       while (len) {
+               res = getrandom(p, len, 0);
+               if (res < 0)
+                       return -1;
+               len -= res;
+               p += res;
+       }
+       return 0;
+#elif defined(HAVE_GETENTROPY)
+       int res;
+       char *p = buf;
+       while (len) {
+               /* getentropy has a maximum size of 256 bytes. */
+               size_t chunk = len < 256 ? len : 256;
+               res = getentropy(p, chunk);
+               if (res < 0)
+                       return -1;
+               len -= chunk;
+               p += chunk;
+       }
+       return 0;
+#elif defined(HAVE_RTLGENRANDOM)
+       if (!RtlGenRandom(buf, len))
+               return -1;
+       return 0;
+#elif defined(HAVE_OPENSSL_CSPRNG)
+       int res = RAND_bytes(buf, len);
+       if (res == 1)
+               return 0;
+       if (res == -1)
+               errno = ENOTSUP;
+       else
+               errno = EIO;
+       return -1;
+#else
+       ssize_t res;
+       char *p = buf;
+       int fd, err;
+       fd = open("/dev/urandom", O_RDONLY);
+       if (fd < 0)
+               return -1;
+       while (len) {
+               res = xread(fd, p, len);
+               if (res < 0) {
+                       err = errno;
+                       close(fd);
+                       errno = err;
+                       return -1;
+               }
+               len -= res;
+               p += res;
+       }
+       close(fd);
+       return 0;
+#endif
+}