]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'ew/delta-islands-free'
authorJunio C Hamano <gitster@pobox.com>
Wed, 23 Nov 2022 02:22:25 +0000 (11:22 +0900)
committerJunio C Hamano <gitster@pobox.com>
Wed, 23 Nov 2022 02:22:25 +0000 (11:22 +0900)
Free structures related to delta islands after use.

* ew/delta-islands-free:
  delta-islands: free island-related data after use

100 files changed:
.gitignore
Documentation/RelNotes/2.39.0.txt
Documentation/git-credential-cache.txt
Documentation/git-credential.txt
Documentation/git-maintenance.txt
Documentation/git-repack.txt
Documentation/gitcredentials.txt
Documentation/howto/maintain-git.txt
Documentation/technical/sparse-checkout.txt [new file with mode: 0644]
INSTALL
Makefile
builtin/bisect--helper.c
builtin/branch.c
builtin/for-each-repo.c
builtin/gc.c
builtin/notes.c
builtin/read-tree.c
builtin/rebase.c
builtin/repack.c
builtin/reset.c
builtin/rm.c
builtin/submodule--helper.c
ci/lib.sh
contrib/buildsystems/CMakeLists.txt
contrib/coccinelle/.gitignore
contrib/coccinelle/README
contrib/coccinelle/hashmap.cocci
contrib/coccinelle/preincr.cocci
contrib/coccinelle/spatchcache [new file with mode: 0755]
contrib/coccinelle/strbuf.cocci
contrib/coccinelle/swap.cocci
contrib/coccinelle/the_repository.pending.cocci
git-bisect.sh
git-submodule.sh
git.c
http.c
reset.c
scalar.c
sequencer.c
sequencer.h
sha1dc_git.h
shared.mak
submodule.c
submodule.h
t/Makefile
t/chainlint.pl
t/chainlint/block-comment.expect
t/chainlint/case-comment.expect
t/chainlint/close-subshell.expect
t/chainlint/comment.expect
t/chainlint/double-here-doc.expect
t/chainlint/empty-here-doc.expect
t/chainlint/for-loop.expect
t/chainlint/here-doc-close-subshell.expect
t/chainlint/here-doc-indent-operator.expect
t/chainlint/here-doc-multi-line-command-subst.expect
t/chainlint/here-doc-multi-line-string.expect
t/chainlint/here-doc.expect
t/chainlint/if-then-else.expect
t/chainlint/incomplete-line.expect
t/chainlint/inline-comment.expect
t/chainlint/loop-detect-status.expect
t/chainlint/nested-here-doc.expect
t/chainlint/nested-subshell-comment.expect
t/chainlint/subshell-here-doc.expect
t/chainlint/t7900-subtree.expect
t/chainlint/while-loop.expect
t/helper/test-cache-tree.c [new file with mode: 0644]
t/helper/test-sha1.c
t/helper/test-submodule.c
t/helper/test-tool.c
t/helper/test-tool.h
t/lib-httpd.sh
t/lib-httpd/apache.conf
t/perf/p0006-read-tree-checkout.sh
t/perf/p0090-cache-tree.sh [new file with mode: 0755]
t/perf/p7102-reset.sh [new file with mode: 0755]
t/t0013-sha1dc.sh
t/t0061-run-command.sh
t/t0068-for-each-repo.sh
t/t1022-read-tree-partial-clone.sh
t/t1800-hook.sh
t/t3200-branch.sh
t/t3404-rebase-interactive.sh
t/t3430-rebase-merges.sh
t/t5526-fetch-submodules.sh
t/t5551-http-fetch-smart.sh
t/t5559-http-fetch-smart-http2.sh [new file with mode: 0755]
t/t6030-bisect-porcelain.sh
t/t7400-submodule-basic.sh
t/t7407-submodule-foreach.sh
t/t7411-submodule-config.sh
t/t7418-submodule-sparse-gitmodules.sh
t/t7422-submodule-output.sh [new file with mode: 0755]
t/t7610-mergetool.sh
t/t7700-repack.sh
t/t7900-maintenance.sh
t/t9210-scalar.sh
unpack-trees.c
unpack-trees.h

index cb0231fb4019fca79a142ababe534fd7fc0a38a1..0832f1da77b0a1903d2db65689864c627899c5e5 100644 (file)
@@ -8,6 +8,7 @@
 /GIT-PERL-HEADER
 /GIT-PYTHON-VARS
 /GIT-SCRIPT-DEFINES
+/GIT-SPATCH-DEFINES
 /GIT-USER-AGENT
 /GIT-VERSION-FILE
 /bin-wrappers/
index f9bc29f7d29cf96aaece6936d8398e2544cb6a45..153bf6d89b83ce08b3cb46805f9426afda30f42a 100644 (file)
@@ -32,6 +32,9 @@ UI, Workflows & Features
  * Enable gc.cruftpacks by default for those who opt into
    feature.experimental setting.
 
+ * "git repack" learns to send cruft objects out of the way into
+   packfiles outside the repository.
+
 Performance, Internal Implementation, Development Support etc.
 --------------------------------------------------------------
 
@@ -109,6 +112,18 @@ Performance, Internal Implementation, Development Support etc.
 
  * Modernize test script to avoid "test -f" and friends.
 
+ * Avoid calling 'cache_tree_update()' when doing so would be
+   redundant.
+
+ * Update the credential-cache documentation to provide a more
+   realistic example.
+
+ * Makefile comments updates and reordering to clarify knobs used to
+   choose SHA implementations.
+
+ * A design document for sparse-checkout's future directions has been
+   added.
+
 Fixes since v2.38
 -----------------
 
@@ -250,6 +265,12 @@ Fixes since v2.38
  * "git archive" mistakenly complained twice about a missing
    executable, which has been corrected.
 
+ * Fix a bug where `git branch -d` did not work on an orphaned HEAD.
+
+ * `git rebase --update-refs` would delete references when all
+   `update-ref` commands in the sequencer were removed, which has been
+   corrected.
+
  * Other code cleanup, docfix, build fix, etc.
    (merge 413bc6d20a ds/cmd-main-reorder later to maint).
    (merge 8d2863e4ed nw/t1002-cleanup later to maint).
index 0216c18ef80c9a9d32095ec3969091aff1c20344..432e159d95246ccb9e716ad1554c8b6047db1870 100644 (file)
@@ -69,10 +69,10 @@ $ git push http://example.com/repo.git
 ------------------------------------
 
 You can provide options via the credential.helper configuration
-variable (this example drops the cache time to 5 minutes):
+variable (this example increases the cache time to 1 hour):
 
 -------------------------------------------------------
-$ git config credential.helper 'cache --timeout=300'
+$ git config credential.helper 'cache --timeout=3600'
 -------------------------------------------------------
 
 GIT
index f18673017f577f523b1190ce87ce2d448e4dbd70..ac2818b9f66778d02bce6f344bdc4ef88a2bd263 100644 (file)
@@ -160,6 +160,8 @@ empty string.
 Components which are missing from the URL (e.g., there is no
 username in the example above) will be left unset.
 
+Unrecognised attributes are silently discarded.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index bb888690e4dbd2642fc46d3c8775fa1638741cf4..805e5a2e3a044b4e1f5a345ecafa2dccb87565ce 100644 (file)
@@ -50,13 +50,13 @@ stop::
        the background maintenance is restarted later.
 
 register::
-       Initialize Git config values so any scheduled maintenance will
-       start running on this repository. This adds the repository to the
-       `maintenance.repo` config variable in the current user's global
-       config and enables some recommended configuration values for
-       `maintenance.<task>.schedule`. The tasks that are enabled are safe
-       for running in the background without disrupting foreground
-       processes.
+       Initialize Git config values so any scheduled maintenance will start
+       running on this repository. This adds the repository to the
+       `maintenance.repo` config variable in the current user's global config,
+       or the config specified by --config-file option, and enables some
+       recommended configuration values for `maintenance.<task>.schedule`. The
+       tasks that are enabled are safe for running in the background without
+       disrupting foreground processes.
 +
 The `register` subcommand will also set the `maintenance.strategy` config
 value to `incremental`, if this value is not previously set. The
index 0bf13893d8147d872dbedeed9def497b2a5a0fea..4017157949e6d764a619f870c0eed538d3e99f64 100644 (file)
@@ -74,6 +74,12 @@ to the new separate pack will be written.
        immediately instead of waiting for the next `git gc` invocation.
        Only useful with `--cruft -d`.
 
+--expire-to=<dir>::
+       Write a cruft pack containing pruned objects (if any) to the
+       directory `<dir>`. This option is useful for keeping a copy of
+       any pruned objects in a separate directory as a backup. Only
+       useful with `--cruft -d`.
+
 -l::
        Pass the `--local` option to 'git pack-objects'. See
        linkgit:git-pack-objects[1].
index 6df50e8a144f29af1139a7180096055e1b45c37b..d6665d2f31f6ea2f418c6e01bc2cba47f98f671f 100644 (file)
@@ -270,6 +270,7 @@ stdout in the same format (see linkgit:git-credential[1] for common
 attributes). A helper is free to produce a subset, or even no values at
 all if it has nothing useful to provide. Any provided attributes will
 overwrite those already known about by Git's credential subsystem.
+Unrecognised attributes are silently discarded.
 
 While it is possible to override all attributes, well behaving helpers
 should refrain from doing so for any attribute other than username and
index 215e2edb0f5f47fac8e1cf5dea91c7f8d1881d5b..d07c6d44e53c3bd0e7805efa036f2cf3eeaf79ac 100644 (file)
@@ -231,7 +231,7 @@ by doing the following:
  - Prepare 'jch' branch, which is used to represent somewhere
    between 'master' and 'seen' and often is slightly ahead of 'next'.
 
-     $ Meta/Reintegrate master..seen >Meta/redo-jch.sh
+     $ Meta/Reintegrate master..jch >Meta/redo-jch.sh
 
    The result is a script that lists topics to be merged in order to
    rebuild 'seen' as the input to Meta/Reintegrate script.  Remove
@@ -283,6 +283,11 @@ by doing the following:
 
      $ git diff jch next
 
+   Then build the rest of 'jch':
+
+     $ git checkout jch
+     $ sh Meta/redo-jch.sh
+
    When all is well, clean up the redo-jch.sh script with
 
      $ sh Meta/redo-jch.sh -u
@@ -293,7 +298,7 @@ by doing the following:
 
  - Rebuild 'seen'.
 
-     $ Meta/Reintegrate master..seen >Meta/redo-seen.sh
+     $ Meta/Reintegrate jch..seen >Meta/redo-seen.sh
 
    Edit the result by adding new topics that are not still in 'seen'
    in the script.  Then
diff --git a/Documentation/technical/sparse-checkout.txt b/Documentation/technical/sparse-checkout.txt
new file mode 100644 (file)
index 0000000..fa0d01c
--- /dev/null
@@ -0,0 +1,1103 @@
+Table of contents:
+
+  * Terminology
+  * Purpose of sparse-checkouts
+  * Usecases of primary concern
+  * Oversimplified mental models ("Cliff Notes" for this document!)
+  * Desired behavior
+  * Behavior classes
+  * Subcommand-dependent defaults
+  * Sparse specification vs. sparsity patterns
+  * Implementation Questions
+  * Implementation Goals/Plans
+  * Known bugs
+  * Reference Emails
+
+
+=== Terminology ===
+
+cone mode: one of two modes for specifying the desired subset of files
+       in a sparse-checkout.  In cone-mode, the user specifies
+       directories (getting both everything under that directory as
+       well as everything in leading directories), while in non-cone
+       mode, the user specifies gitignore-style patterns.  Controlled
+       by the --[no-]cone option to sparse-checkout init|set.
+
+SKIP_WORKTREE: When tracked files do not match the sparse specification and
+       are removed from the working tree, the file in the index is marked
+       with a SKIP_WORKTREE bit.  Note that if a tracked file has the
+       SKIP_WORKTREE bit set but the file is later written by the user to
+       the working tree anyway, the SKIP_WORKTREE bit will be cleared at
+       the beginning of any subsequent Git operation.
+
+       Most sparse checkout users are unaware of this implementation
+       detail, and the term should generally be avoided in user-facing
+       descriptions and command flags.  Unfortunately, prior to the
+       `sparse-checkout` subcommand this low-level detail was exposed,
+       and as of time of writing, is still exposed in various places.
+
+sparse-checkout: a subcommand in git used to reduce the files present in
+       the working tree to a subset of all tracked files.  Also, the
+       name of the file in the $GIT_DIR/info directory used to track
+       the sparsity patterns corresponding to the user's desired
+       subset.
+
+sparse cone: see cone mode
+
+sparse directory: An entry in the index corresponding to a directory, which
+       appears in the index instead of all the files under that directory
+       that would normally appear.  See also sparse-index.  Something that
+       can cause confusion is that the "sparse directory" does NOT match
+       the sparse specification, i.e. the directory is NOT present in the
+       working tree.  May be renamed in the future (e.g. to "skipped
+       directory").
+
+sparse index: A special mode for sparse-checkout that also makes the
+       index sparse by recording a directory entry in lieu of all the
+       files underneath that directory (thus making that a "skipped
+       directory" which unfortunately has also been called a "sparse
+       directory"), and does this for potentially multiple
+       directories.  Controlled by the --[no-]sparse-index option to
+       init|set|reapply.
+
+sparsity patterns: patterns from $GIT_DIR/info/sparse-checkout used to
+       define the set of files of interest.  A warning: It is easy to
+       over-use this term (or the shortened "patterns" term), for two
+       reasons: (1) users in cone mode specify directories rather than
+       patterns (their directories are transformed into patterns, but
+       users may think you are talking about non-cone mode if you use the
+       word "patterns"), and (b) the sparse specification might
+       transiently differ in the working tree or index from the sparsity
+       patterns (see "Sparse specification vs. sparsity patterns").
+
+sparse specification: The set of paths in the user's area of focus.  This
+       is typically just the tracked files that match the sparsity
+       patterns, but the sparse specification can temporarily differ and
+       include additional files.  (See also "Sparse specification
+       vs. sparsity patterns")
+
+       * When working with history, the sparse specification is exactly
+         the set of files matching the sparsity patterns.
+       * When interacting with the working tree, the sparse specification
+         is the set of tracked files with a clear SKIP_WORKTREE bit or
+         tracked files present in the working copy.
+       * When modifying or showing results from the index, the sparse
+         specification is the set of files with a clear SKIP_WORKTREE bit
+         or that differ in the index from HEAD.
+       * If working with the index and the working copy, the sparse
+         specification is the union of the paths from above.
+
+vivifying: When a command restores a tracked file to the working tree (and
+       hopefully also clears the SKIP_WORKTREE bit in the index for that
+       file), this is referred to as "vivifying" the file.
+
+
+=== Purpose of sparse-checkouts ===
+
+sparse-checkouts exist to allow users to work with a subset of their
+files.
+
+You can think of sparse-checkouts as subdividing "tracked" files into two
+categories -- a sparse subset, and all the rest.  Implementationally, we
+mark "all the rest" in the index with a SKIP_WORKTREE bit and leave them
+out of the working tree.  The SKIP_WORKTREE files are still tracked, just
+not present in the working tree.
+
+In the past, sparse-checkouts were defined by "SKIP_WORKTREE means the file
+is missing from the working tree but pretend the file contents match HEAD".
+That was not only bogus (it actually meant the file missing from the
+working tree matched the index rather than HEAD), but it was also a
+low-level detail which only provided decent behavior for a few commands.
+There were a surprising number of ways in which that guiding principle gave
+command results that violated user expectations, and as such was a bad
+mental model.  However, it persisted for many years and may still be found
+in some corners of the code base.
+
+Anyway, the idea of "working with a subset of files" is simple enough, but
+there are multiple different high-level usecases which affect how some Git
+subcommands should behave.  Further, even if we only considered one of
+those usecases, sparse-checkouts can modify different subcommands in over a
+half dozen different ways.  Let's start by considering the high level
+usecases:
+
+  A) Users are _only_ interested in the sparse portion of the repo
+
+  A*) Users are _only_ interested in the sparse portion of the repo
+      that they have downloaded so far
+
+  B) Users want a sparse working tree, but are working in a larger whole
+
+  C) sparse-checkout is a behind-the-scenes implementation detail allowing
+     Git to work with a specially crafted in-house virtual file system;
+     users are actually working with a "full" working tree that is
+     lazily populated, and sparse-checkout helps with the lazy population
+     piece.
+
+It may be worth explaining each of these in a bit more detail:
+
+
+  (Behavior A) Users are _only_ interested in the sparse portion of the repo
+
+These folks might know there are other things in the repository, but
+don't care.  They are uninterested in other parts of the repository, and
+only want to know about changes within their area of interest.  Showing
+them other files from history (e.g. from diff/log/grep/etc.)  is a
+usability annoyance, potentially a huge one since other changes in
+history may dwarf the changes they are interested in.
+
+Some of these users also arrive at this usecase from wanting to use partial
+clones together with sparse checkouts (in a way where they have downloaded
+blobs within the sparse specification) and do disconnected development.
+Not only do these users generally not care about other parts of the
+repository, but consider it a blocker for Git commands to try to operate on
+those.  If commands attempt to access paths in history outside the sparsity
+specification, then the partial clone will attempt to download additional
+blobs on demand, fail, and then fail the user's command.  (This may be
+unavoidable in some cases, e.g. when `git merge` has non-trivial changes to
+reconcile outside the sparse specification, but we should limit how often
+users are forced to connect to the network.)
+
+Also, even for users using partial clones that do not mind being
+always connected to the network, the need to download blobs as
+side-effects of various other commands (such as the printed diffstat
+after a merge or pull) can lead to worries about local repository size
+growing unnecessarily[10].
+
+  (Behavior A*) Users are _only_ interested in the sparse portion of the repo
+      that they have downloaded so far (a variant on the first usecase)
+
+This variant is driven by folks who using partial clones together with
+sparse checkouts and do disconnected development (so far sounding like a
+subset of behavior A users) and doing so on very large repositories.  The
+reason for yet another variant is that downloading even just the blobs
+through history within their sparse specification may be too much, so they
+only download some.  They would still like operations to succeed without
+network connectivity, though, so things like `git log -S${SEARCH_TERM} -p`
+or `git grep ${SEARCH_TERM} OLDREV ` would need to be prepared to provide
+partial results that depend on what happens to have been downloaded.
+
+This variant could be viewed as Behavior A with the sparse specification
+for history querying operations modified from "sparsity patterns" to
+"sparsity patterns limited to the blobs we have already downloaded".
+
+  (Behavior B) Users want a sparse working tree, but are working in a
+      larger whole
+
+Stolee described this usecase this way[11]:
+
+"I'm also focused on users that know that they are a part of a larger
+whole. They know they are operating on a large repository but focus on
+what they need to contribute their part. I expect multiple "roles" to
+use very different, almost disjoint parts of the codebase. Some other
+"architect" users operate across the entire tree or hop between different
+sections of the codebase as necessary. In this situation, I'm wary of
+scoping too many features to the sparse-checkout definition, especially
+"git log," as it can be too confusing to have their view of the codebase
+depend on your "point of view."
+
+People might also end up wanting behavior B due to complex inter-project
+dependencies.  The initial attempts to use sparse-checkouts usually involve
+the directories you are directly interested in plus what those directories
+depend upon within your repository.  But there's a monkey wrench here: if
+you have integration tests, they invert the hierarchy: to run integration
+tests, you need not only what you are interested in and its in-tree
+dependencies, you also need everything that depends upon what you are
+interested in or that depends upon one of your dependencies...AND you need
+all the in-tree dependencies of that expanded group.  That can easily
+change your sparse-checkout into a nearly dense one.
+
+Naturally, that tends to kill the benefits of sparse-checkouts.  There are
+a couple solutions to this conundrum: either avoid grabbing in-repo
+dependencies (maybe have built versions of your in-repo dependencies pulled
+from a CI cache somewhere), or say that users shouldn't run integration
+tests directly and instead do it on the CI server when they submit a code
+review.  Or do both.  Regardless of whether you stub out your in-repo
+dependencies or stub out the things that depend upon you, there is
+certainly a reason to want to query and be aware of those other stubbed-out
+parts of the repository, particularly when the dependencies are complex or
+change relatively frequently.  Thus, for such uses, sparse-checkouts can be
+used to limit what you directly build and modify, but these users do not
+necessarily want their sparse checkout paths to limit their queries of
+versions in history.
+
+Some people may also be interested in behavior B over behavior A simply as
+a performance workaround: if they are using non-cone mode, then they have
+to deal with its inherent quadratic performance problems.  In that mode,
+every operation that checks whether paths match the sparsity specification
+can be expensive.  As such, these users may only be willing to pay for
+those expensive checks when interacting with the working copy, and may
+prefer getting "unrelated" results from their history queries over having
+slow commands.
+
+  (Behavior C) sparse-checkout is an implementational detail supporting a
+              special VFS.
+
+This usecase goes slightly against the traditional definition of
+sparse-checkout in that it actually tries to present a full or dense
+checkout to the user.  However, this usecase utilizes the same underlying
+technical underpinnings in a new way which does provide some performance
+advantages to users.  The basic idea is that a company can have an in-house
+Git-aware Virtual File System which pretends all files are present in the
+working tree, by intercepting all file system accesses and using those to
+fetch and write accessed files on demand via partial clones.  The VFS uses
+sparse-checkout to prevent Git from writing or paying attention to many
+files, and manually updates the sparse checkout patterns itself based on
+user access and modification of files in the working tree.  See commit
+ecc7c8841d ("repo_read_index: add config to expect files outside sparse
+patterns", 2022-02-25) and the link at [17] for a more detailed description
+of such a VFS.
+
+The biggest difference here is that users are completely unaware that the
+sparse-checkout machinery is even in use.  The sparse patterns are not
+specified by the user but rather are under the complete control of the VFS
+(and the patterns are updated frequently and dynamically by it).  The user
+will perceive the checkout as dense, and commands should thus behave as if
+all files are present.
+
+
+=== Usecases of primary concern ===
+
+Most of the rest of this document will focus on Behavior A and Behavior
+B.  Some notes about the other two cases and why we are not focusing on
+them:
+
+  (Behavior A*)
+
+Supporting this usecase is estimated to be difficult and a lot of work.
+There are no plans to implement it currently, but it may be a potential
+future alternative.  Knowing about the existence of additional alternatives
+may affect our choice of command line flags (e.g. if we need tri-state or
+quad-state flags rather than just binary flags), so it was still important
+to at least note.
+
+Further, I believe the descriptions below for Behavior A are probably still
+valid for this usecase, with the only exception being that it redefines the
+sparse specification to restrict it to already-downloaded blobs.  The hard
+part is in making commands capable of respecting that modified definition.
+
+  (Behavior C)
+
+This usecase violates some of the early sparse-checkout documented
+assumptions (since files marked as SKIP_WORKTREE will be displayed to users
+as present in the working tree).  That violation may mean various
+sparse-checkout related behaviors are not well suited to this usecase and
+we may need tweaks -- to both documentation and code -- to handle it.
+However, this usecase is also perhaps the simplest model to support in that
+everything behaves like a dense checkout with a few exceptions (e.g. branch
+checkouts and switches write fewer things, knowing the VFS will lazily
+write the rest on an as-needed basis).
+
+Since there is no publically available VFS-related code for folks to try,
+the number of folks who can test such a usecase is limited.
+
+The primary reason to note the Behavior C usecase is that as we fix things
+to better support Behaviors A and B, there may be additional places where
+we need to make tweaks allowing folks in this usecase to get the original
+non-sparse treatment.  For an example, see ecc7c8841d ("repo_read_index:
+add config to expect files outside sparse patterns", 2022-02-25).  The
+secondary reason to note Behavior C, is so that folks taking advantage of
+Behavior C do not assume they are part of the Behavior B camp and propose
+patches that break things for the real Behavior B folks.
+
+
+=== Oversimplified mental models ===
+
+An oversimplification of the differences in the above behaviors is:
+
+  Behavior A: Restrict worktree and history operations to sparse specification
+  Behavior B: Restrict worktree operations to sparse specification; have any
+             history operations work across all files
+  Behavior C: Do not restrict either worktree or history operations to the
+             sparse specification...with the exception of branch checkouts or
+             switches which avoid writing files that will match the index so
+             they can later lazily be populated instead.
+
+
+=== Desired behavior ===
+
+As noted previously, despite the simple idea of just working with a subset
+of files, there are a range of different behavioral changes that need to be
+made to different subcommands to work well with such a feature.  See
+[1,2,3,4,5,6,7,8,9,10] for various examples.  In particular, at [2], we saw
+that mere composition of other commands that individually worked correctly
+in a sparse-checkout context did not imply that the higher level command
+would work correctly; it sometimes requires further tweaks.  So,
+understanding these differences can be beneficial.
+
+* Commands behaving the same regardless of high-level use-case
+
+  * commands that only look at files within the sparsity specification
+
+      * diff (without --cached or REVISION arguments)
+      * grep (without --cached or REVISION arguments)
+      * diff-files
+
+  * commands that restore files to the working tree that match sparsity
+    patterns, and remove unmodified files that don't match those
+    patterns:
+
+      * switch
+      * checkout (the switch-like half)
+      * read-tree
+      * reset --hard
+
+  * commands that write conflicted files to the working tree, but otherwise
+    will omit writing files to the working tree that do not match the
+    sparsity patterns:
+
+      * merge
+      * rebase
+      * cherry-pick
+      * revert
+
+      * `am` and `apply --cached` should probably be in this section but
+       are buggy (see the "Known bugs" section below)
+
+    The behavior for these commands somewhat depends upon the merge
+    strategy being used:
+      * `ort` behaves as described above
+      * `recursive` tries to not vivify files unnecessarily, but does sometimes
+       vivify files without conflicts.
+      * `octopus` and `resolve` will always vivify any file changed in the merge
+       relative to the first parent, which is rather suboptimal.
+
+    It is also important to note that these commands WILL update the index
+    outside the sparse specification relative to when the operation began,
+    BUT these commands often make a commit just before or after such that
+    by the end of the operation there is no change to the index outside the
+    sparse specification.  Of course, if the operation hits conflicts or
+    does not make a commit, then these operations clearly can modify the
+    index outside the sparse specification.
+
+    Finally, it is important to note that at least the first four of these
+    commands also try to remove differences between the sparse
+    specification and the sparsity patterns (much like the commands in the
+    previous section).
+
+  * commands that always ignore sparsity since commits must be full-tree
+
+      * archive
+      * bundle
+      * commit
+      * format-patch
+      * fast-export
+      * fast-import
+      * commit-tree
+
+  * commands that write any modified file to the working tree (conflicted
+    or not, and whether those paths match sparsity patterns or not):
+
+      * stash
+      * apply (without `--index` or `--cached`)
+
+* Commands that may slightly differ for behavior A vs. behavior B:
+
+  Commands in this category behave mostly the same between the two
+  behaviors, but may differ in verbosity and types of warning and error
+  messages.
+
+  * commands that make modifications to which files are tracked:
+      * add
+      * rm
+      * mv
+      * update-index
+
+    The fact that files can move between the 'tracked' and 'untracked'
+    categories means some commands will have to treat untracked files
+    differently.  But if we have to treat untracked files differently,
+    then additional commands may also need changes:
+
+      * status
+      * clean
+
+    In particular, `status` may need to report any untracked files outside
+    the sparsity specification as an erroneous condition (especially to
+    avoid the user trying to `git add` them, forcing `git add` to display
+    an error).
+
+    It's not clear to me exactly how (or even if) `clean` would change,
+    but it's the other command that also affects untracked files.
+
+    `update-index` may be slightly special.  Its --[no-]skip-worktree flag
+    may need to ignore the sparse specification by its nature.  Also, its
+    current --[no-]ignore-skip-worktree-entries default is totally bogus.
+
+  * commands for manually tweaking paths in both the index and the working tree
+      * `restore`
+      * the restore-like half of `checkout`
+
+    These commands should be similar to add/rm/mv in that they should
+    only operate on the sparse specification by default, and require a
+    special flag to operate on all files.
+
+    Also, note that these commands currently have a number of issues (see
+    the "Known bugs" section below)
+
+* Commands that significantly differ for behavior A vs. behavior B:
+
+  * commands that query history
+      * diff (with --cached or REVISION arguments)
+      * grep (with --cached or REVISION arguments)
+      * show (when given commit arguments)
+      * blame (only matters when one or more -C flags are passed)
+       * and annotate
+      * log
+      * whatchanged
+      * ls-files
+      * diff-index
+      * diff-tree
+      * ls-tree
+
+    Note: for log and whatchanged, revision walking logic is unaffected
+    but displaying of patches is affected by scoping the command to the
+    sparse-checkout.  (The fact that revision walking is unaffected is
+    why rev-list, shortlog, show-branch, and bisect are not in this
+    list.)
+
+    ls-files may be slightly special in that e.g. `git ls-files -t` is
+    often used to see what is sparse and what is not.  Perhaps -t should
+    always work on the full tree?
+
+* Commands I don't know how to classify
+
+  * range-diff
+
+    Is this like `log` or `format-patch`?
+
+  * cherry
+
+    See range-diff
+
+* Commands unaffected by sparse-checkouts
+
+  * shortlog
+  * show-branch
+  * rev-list
+  * bisect
+
+  * branch
+  * describe
+  * fetch
+  * gc
+  * init
+  * maintenance
+  * notes
+  * pull (merge & rebase have the necessary changes)
+  * push
+  * submodule
+  * tag
+
+  * config
+  * filter-branch (works in separate checkout without sparse-checkout setup)
+  * pack-refs
+  * prune
+  * remote
+  * repack
+  * replace
+
+  * bugreport
+  * count-objects
+  * fsck
+  * gitweb
+  * help
+  * instaweb
+  * merge-tree (doesn't touch worktree or index, and merges always compute full-tree)
+  * rerere
+  * verify-commit
+  * verify-tag
+
+  * commit-graph
+  * hash-object
+  * index-pack
+  * mktag
+  * mktree
+  * multi-pack-index
+  * pack-objects
+  * prune-packed
+  * symbolic-ref
+  * unpack-objects
+  * update-ref
+  * write-tree (operates on index, possibly optimized to use sparse dir entries)
+
+  * for-each-ref
+  * get-tar-commit-id
+  * ls-remote
+  * merge-base (merges are computed full tree, so merge base should be too)
+  * name-rev
+  * pack-redundant
+  * rev-parse
+  * show-index
+  * show-ref
+  * unpack-file
+  * var
+  * verify-pack
+
+  * <Everything under 'Interacting with Others' in 'git help --all'>
+  * <Everything under 'Low-level...Syncing' in 'git help --all'>
+  * <Everything under 'Low-level...Internal Helpers' in 'git help --all'>
+  * <Everything under 'External commands' in 'git help --all'>
+
+* Commands that might be affected, but who cares?
+
+  * merge-file
+  * merge-index
+  * gitk?
+
+
+=== Behavior classes ===
+
+From the above there are a few classes of behavior:
+
+  * "restrict"
+
+    Commands in this class only read or write files in the working tree
+    within the sparse specification.
+
+    When moving to a new commit (e.g. switch, reset --hard), these commands
+    may update index files outside the sparse specification as of the start
+    of the operation, but by the end of the operation those index files
+    will match HEAD again and thus those files will again be outside the
+    sparse specification.
+
+    When paths are explicitly specified, these paths are intersected with
+    the sparse specification and will only operate on such paths.
+    (e.g. `git restore [--staged] -- '*.png'`, `git reset -p -- '*.md'`)
+
+    Some of these commands may also attempt, at the end of their operation,
+    to cull transient differences between the sparse specification and the
+    sparsity patterns (see "Sparse specification vs. sparsity patterns" for
+    details, but this basically means either removing unmodified files not
+    matching the sparsity patterns and marking those files as
+    SKIP_WORKTREE, or vivifying files that match the sparsity patterns and
+    marking those files as !SKIP_WORKTREE).
+
+  * "restrict modulo conflicts"
+
+    Commands in this class generally behave like the "restrict" class,
+    except that:
+      (1) they will ignore the sparse specification and write files with
+         conflicts to the working tree (thus temporarily expanding the
+         sparse specification to include such files.)
+      (2) they are grouped with commands which move to a new commit, since
+         they often create a commit and then move to it, even though we
+         know there are many exceptions to moving to the new commit.  (For
+         example, the user may rebase a commit that becomes empty, or have
+         a cherry-pick which conflicts, or a user could run `merge
+         --no-commit`, and we also view `apply --index` kind of like `am
+         --no-commit`.)  As such, these commands can make changes to index
+         files outside the sparse specification, though they'll mark such
+         files with SKIP_WORKTREE.
+
+  * "restrict also specially applied to untracked files"
+
+    Commands in this class generally behave like the "restrict" class,
+    except that they have to handle untracked files differently too, often
+    because these commands are dealing with files changing state between
+    'tracked' and 'untracked'.  Often, this may mean printing an error
+    message if the command had nothing to do, but the arguments may have
+    referred to files whose tracked-ness state could have changed were it
+    not for the sparsity patterns excluding them.
+
+  * "no restrict"
+
+    Commands in this class ignore the sparse specification entirely.
+
+  * "restrict or no restrict dependent upon behavior A vs. behavior B"
+
+    Commands in this class behave like "no restrict" for folks in the
+    behavior B camp, and like "restrict" for folks in the behavior A camp.
+    However, when behaving like "restrict" a warning of some sort might be
+    provided that history queries have been limited by the sparse-checkout
+    specification.
+
+
+=== Subcommand-dependent defaults ===
+
+Note that we have different defaults depending on the command for the
+desired behavior :
+
+  * Commands defaulting to "restrict":
+    * diff-files
+    * diff (without --cached or REVISION arguments)
+    * grep (without --cached or REVISION arguments)
+    * switch
+    * checkout (the switch-like half)
+    * reset (<commit>)
+
+    * restore
+    * checkout (the restore-like half)
+    * checkout-index
+    * reset (with pathspec)
+
+    This behavior makes sense; these interact with the working tree.
+
+  * Commands defaulting to "restrict modulo conflicts":
+    * merge
+    * rebase
+    * cherry-pick
+    * revert
+
+    * am
+    * apply --index (which is kind of like an `am --no-commit`)
+
+    * read-tree (especially with -m or -u; is kind of like a --no-commit merge)
+    * reset (<tree-ish>, due to similarity to read-tree)
+
+    These also interact with the working tree, but require slightly
+    different behavior either so that (a) conflicts can be resolved or (b)
+    because they are kind of like a merge-without-commit operation.
+
+    (See also the "Known bugs" section below regarding `am` and `apply`)
+
+  * Commands defaulting to "no restrict":
+    * archive
+    * bundle
+    * commit
+    * format-patch
+    * fast-export
+    * fast-import
+    * commit-tree
+
+    * stash
+    * apply (without `--index`)
+
+    These have completely different defaults and perhaps deserve the most
+    detailed explanation:
+
+    In the case of commands in the first group (format-patch,
+    fast-export, bundle, archive, etc.), these are commands for
+    communicating history, which will be broken if they restrict to a
+    subset of the repository.  As such, they operate on full paths and
+    have no `--restrict` option for overriding.  Some of these commands may
+    take paths for manually restricting what is exported, but it needs to
+    be very explicit.
+
+    In the case of stash, it needs to vivify files to avoid losing the
+    user's changes.
+
+    In the case of apply without `--index`, that command needs to update
+    the working tree without the index (or the index without the working
+    tree if `--cached` is passed), and if we restrict those updates to the
+    sparse specification then we'll lose changes from the user.
+
+  * Commands defaulting to "restrict also specially applied to untracked files":
+    * add
+    * rm
+    * mv
+    * update-index
+    * status
+    * clean (?)
+
+    Our original implementation for the first three of these commands was
+    "no restrict", but it had some severe usability issues:
+      * `git add <somefile>` if honored and outside the sparse
+       specification, can result in the file randomly disappearing later
+       when some subsequent command is run (since various commands
+       automatically clean up unmodified files outside the sparse
+       specification).
+      * `git rm '*.jpg'` could very negatively surprise users if it deletes
+       files outside the range of the user's interest.
+      * `git mv` has similar surprises when moving into or out of the cone,
+       so best to restrict by default
+
+    So, we switched `add` and `rm` to default to "restrict", which made
+    usability problems much less severe and less frequent, but we still got
+    complaints because commands like:
+       git add <file-outside-sparse-specification>
+       git rm <file-outside-sparse-specification>
+    would silently do nothing.  We should instead print an error in those
+    cases to get usability right.
+
+    update-index needs to be updated to match, and status and maybe clean
+    also need to be updated to specially handle untracked paths.
+
+    There may be a difference in here between behavior A and behavior B in
+    terms of verboseness of errors or additional warnings.
+
+  * Commands falling under "restrict or no restrict dependent upon behavior
+    A vs. behavior B"
+
+    * diff (with --cached or REVISION arguments)
+    * grep (with --cached or REVISION arguments)
+    * show (when given commit arguments)
+    * blame (only matters when one or more -C flags passed)
+      * and annotate
+    * log
+      * and variants: shortlog, gitk, show-branch, whatchanged, rev-list
+    * ls-files
+    * diff-index
+    * diff-tree
+    * ls-tree
+
+    For now, we default to behavior B for these, which want a default of
+    "no restrict".
+
+    Note that two of these commands -- diff and grep -- also appeared in a
+    different list with a default of "restrict", but only when limited to
+    searching the working tree.  The working tree vs. history distinction
+    is fundamental in how behavior B operates, so this is expected.  Note,
+    though, that for diff and grep with --cached, when doing "restrict"
+    behavior, the difference between sparse specification and sparsity
+    patterns is important to handle.
+
+    "restrict" may make more sense as the long term default for these[12].
+    Also, supporting "restrict" for these commands might be a fair amount
+    of work to implement, meaning it might be implemented over multiple
+    releases.  If that behavior were the default in the commands that
+    supported it, that would force behavior B users to need to learn to
+    slowly add additional flags to their commands, depending on git
+    version, to get the behavior they want.  That gradual switchover would
+    be painful, so we should avoid it at least until it's fully
+    implemented.
+
+
+=== Sparse specification vs. sparsity patterns ===
+
+In a well-behaved situation, the sparse specification is given directly
+by the $GIT_DIR/info/sparse-checkout file.  However, it can transiently
+diverge for a few reasons:
+
+    * needing to resolve conflicts (merging will vivify conflicted files)
+    * running Git commands that implicitly vivify files (e.g. "git stash apply")
+    * running Git commands that explicitly vivify files (e.g. "git checkout
+      --ignore-skip-worktree-bits FILENAME")
+    * other commands that write to these files (perhaps a user copies it
+      from elsewhere)
+
+For the last item, note that we do automatically clear the SKIP_WORKTREE
+bit for files that are present in the working tree.  This has been true
+since 82386b4496 ("Merge branch 'en/present-despite-skipped'",
+2022-03-09)
+
+However, such a situation is transient because:
+
+   * Such transient differences can and will be automatically removed as
+     a side-effect of commands which call unpack_trees() (checkout,
+     merge, reset, etc.).
+   * Users can also request such transient differences be corrected via
+     running `git sparse-checkout reapply`.  Various places recommend
+     running that command.
+   * Additional commands are also welcome to implicitly fix these
+     differences; we may add more in the future.
+
+While we avoid dropping unstaged changes or files which have conflicts,
+we otherwise aggressively try to fix these transient differences.  If
+users want these differences to persist, they should run the `set` or
+`add` subcommands of `git sparse-checkout` to reflect their intended
+sparse specification.
+
+However, when we need to do a query on history restricted to the
+"relevant subset of files" such a transiently expanded sparse
+specification is ignored.  There are a couple reasons for this:
+
+   * The behavior wanted when doing something like
+        git grep expression REVISION
+     is roughly what the users would expect from
+        git checkout REVISION && git grep expression
+     (modulo a "REVISION:" prefix), which has a couple ramifications:
+
+   * REVISION may have paths not in the current index, so there is no
+     path we can consult for a SKIP_WORKTREE setting for those paths.
+
+   * Since `checkout` is one of those commands that tries to remove
+     transient differences in the sparse specification, it makes sense
+     to use the corrected sparse specification
+     (i.e. $GIT_DIR/info/sparse-checkout) rather than attempting to
+     consult SKIP_WORKTREE anyway.
+
+So, a transiently expanded (or restricted) sparse specification applies to
+the working tree, but not to history queries where we always use the
+sparsity patterns.  (See [16] for an early discussion of this.)
+
+Similar to a transiently expanded sparse specification of the working tree
+based on additional files being present in the working tree, we also need
+to consider additional files being modified in the index.  In particular,
+if the user has staged changes to files (relative to HEAD) that do not
+match the sparsity patterns, and the file is not present in the working
+tree, we still want to consider the file part of the sparse specification
+if we are specifically performing a query related to the index (e.g. git
+diff --cached [REVISION], git diff-index [REVISION], git restore --staged
+--source=REVISION -- PATHS, etc.)  Note that a transiently expanded sparse
+specification for the index usually only matters under behavior A, since
+under behavior B index operations are lumped with history and tend to
+operate full-tree.
+
+
+=== Implementation Questions ===
+
+  * Do the options --scope={sparse,all} sound good to others?  Are there better
+    options?
+    * Names in use, or appearing in patches, or previously suggested:
+      * --sparse/--dense
+      * --ignore-skip-worktree-bits
+      * --ignore-skip-worktree-entries
+      * --ignore-sparsity
+      * --[no-]restrict-to-sparse-paths
+      * --full-tree/--sparse-tree
+      * --[no-]restrict
+      * --scope={sparse,all}
+      * --focus/--unfocus
+      * --limit/--unlimited
+    * Rationale making me lean slightly towards --scope={sparse,all}:
+      * We want a name that works for many commands, so we need a name that
+       does not conflict
+      * We know that we have more than two possible usecases, so it is best
+       to avoid a flag that appears to be binary.
+      * --scope={sparse,all} isn't overly long and seems relatively
+       explanatory
+      * `--sparse`, as used in add/rm/mv, is totally backwards for
+       grep/log/etc.  Changing the meaning of `--sparse` for these
+       commands would fix the backwardness, but possibly break existing
+       scripts.  Using a new name pairing would allow us to treat
+       `--sparse` in these commands as a deprecated alias.
+      * There is a different `--sparse`/`--dense` pair for commands using
+       revision machinery, so using that naming might cause confusion
+      * There is also a `--sparse` in both pack-objects and show-branch, which
+       don't conflict but do suggest that `--sparse` is overloaded
+      * The name --ignore-skip-worktree-bits is a double negative, is
+       quite a mouthful, refers to an implementation detail that many
+       users may not be familiar with, and we'd need a negation for it
+       which would probably be even more ridiculously long.  (But we
+       can make --ignore-skip-worktree-bits a deprecated alias for
+       --no-restrict.)
+
+  * If a config option is added (sparse.scope?) what should the values and
+    description be?  "sparse" (behavior A), "worktree-sparse-history-dense"
+    (behavior B), "dense" (behavior C)?  There's a risk of confusion,
+    because even for Behaviors A and B we want some commands to be
+    full-tree and others to operate sparsely, so the wording may need to be
+    more tied to the usecases and somehow explain that.  Also, right now,
+    the primary difference we are focusing is just the history-querying
+    commands (log/diff/grep).  Previous config suggestion here: [13]
+
+  * Is `--no-expand` a good alias for ls-files's `--sparse` option?
+    (`--sparse` does not map to either `--scope=sparse` or `--scope=all`,
+    because in non-cone mode it does nothing and in cone-mode it shows the
+    sparse directory entries which are technically outside the sparse
+    specification)
+
+  * Under Behavior A:
+    * Does ls-files' `--no-expand` override the default `--scope=all`, or
+      does it need an extra flag?
+    * Does ls-files' `-t` option imply `--scope=all`?
+    * Does update-index's `--[no-]skip-worktree` option imply `--scope=all`?
+
+  * sparse-checkout: once behavior A is fully implemented, should we take
+    an interim measure to ease people into switching the default?  Namely,
+    if folks are not already in a sparse checkout, then require
+    `sparse-checkout init/set` to take a
+    `--set-scope=(sparse|worktree-sparse-history-dense|dense)` flag (which
+    would set sparse.scope according to the setting given), and throw an
+    error if the flag is not provided?  That error would be a great place
+    to warn folks that the default may change in the future, and get them
+    used to specifying what they want so that the eventual default switch
+    is seamless for them.
+
+
+=== Implementation Goals/Plans ===
+
+ * Get buy-in on this document in general.
+
+ * Figure out answers to the 'Implementation Questions' sections (above)
+
+ * Fix bugs in the 'Known bugs' section (below)
+
+ * Provide some kind of method for backfilling the blobs within the sparse
+   specification in a partial clone
+
+ [Below here is kind of spitballing since the first two haven't been resolved]
+
+ * update-index: flip the default to --no-ignore-skip-worktree-entries,
+   nuke this stupid "Oh, there's a bug?  Let me add a flag to let users
+   request that they not trigger this bug." flag
+
+ * Flags & Config
+   * Make `--sparse` in add/rm/mv a deprecated alias for `--scope=all`
+   * Make `--ignore-skip-worktree-bits` in checkout-index/checkout/restore
+     a deprecated aliases for `--scope=all`
+   * Create config option (sparse.scope?), tie it to the "Cliff notes"
+     overview
+
+   * Add --scope=sparse (and --scope=all) flag to each of the history querying
+     commands.  IMPORTANT: make sure diff machinery changes don't mess with
+     format-patch, fast-export, etc.
+
+=== Known bugs ===
+
+This list used to be a lot longer (see e.g. [1,2,3,4,5,6,7,8,9]), but we've
+been working on it.
+
+0. Behavior A is not well supported in Git.  (Behavior B didn't used to
+   be either, but was the easier of the two to implement.)
+
+1. am and apply:
+
+   apply, without `--index` or `--cached`, relies on files being present
+   in the working copy, and also writes to them unconditionally.  As
+   such, it should first check for the files' presence, and if found to
+   be SKIP_WORKTREE, then clear the bit and vivify the paths, then do
+   its work.  Currently, it just throws an error.
+
+   apply, with either `--cached` or `--index`, will not preserve the
+   SKIP_WORKTREE bit.  This is fine if the file has conflicts, but
+   otherwise SKIP_WORKTREE bits should be preserved for --cached and
+   probably also for --index.
+
+   am, if there are no conflicts, will vivify files and fail to preserve
+   the SKIP_WORKTREE bit.  If there are conflicts and `-3` is not
+   specified, it will vivify files and then complain the patch doesn't
+   apply.  If there are conflicts and `-3` is specified, it will vivify
+   files and then complain that those vivified files would be
+   overwritten by merge.
+
+2. reset --hard:
+
+   reset --hard provides confusing error message (works correctly, but
+   misleads the user into believing it didn't):
+
+    $ touch addme
+    $ git add addme
+    $ git ls-files -t
+    H addme
+    H tracked
+    S tracked-but-maybe-skipped
+    $ git reset --hard                           # usually works great
+    error: Path 'addme' not uptodate; will not remove from working tree.
+    HEAD is now at bdbbb6f third
+    $ git ls-files -t
+    H tracked
+    S tracked-but-maybe-skipped
+    $ ls -1
+    tracked
+
+    `git reset --hard` DID remove addme from the index and the working tree, contrary
+    to the error message, but in line with how reset --hard should behave.
+
+3. read-tree
+
+   `read-tree` doesn't apply the 'SKIP_WORKTREE' bit to *any* of the
+   entries it reads into the index, resulting in all your files suddenly
+   appearing to be "deleted".
+
+4. Checkout, restore:
+
+   These command do not handle path & revision arguments appropriately:
+
+    $ ls
+    tracked
+    $ git ls-files -t
+    H tracked
+    S tracked-but-maybe-skipped
+    $ git status --porcelain
+    $ git checkout -- '*skipped'
+    error: pathspec '*skipped' did not match any file(s) known to git
+    $ git ls-files -- '*skipped'
+    tracked-but-maybe-skipped
+    $ git checkout HEAD -- '*skipped'
+    error: pathspec '*skipped' did not match any file(s) known to git
+    $ git ls-tree HEAD | grep skipped
+    100644 blob 276f5a64354b791b13840f02047738c77ad0584f       tracked-but-maybe-skipped
+    $ git status --porcelain
+    $ git checkout HEAD~1 -- '*skipped'
+    $ git ls-files -t
+    H tracked
+    H tracked-but-maybe-skipped
+    $ git status --porcelain
+    M  tracked-but-maybe-skipped
+    $ git checkout HEAD -- '*skipped'
+    $ git status --porcelain
+    $
+
+    Note that checkout without a revision (or restore --staged) fails to
+    find a file to restore from the index, even though ls-files shows
+    such a file certainly exists.
+
+    Similar issues occur with HEAD (--source=HEAD in restore's case),
+    but suddenly works when HEAD~1 is specified.  And then after that it
+    will work with HEAD specified, even though it didn't before.
+
+    Directories are also an issue:
+
+    $ git sparse-checkout set nomatches
+    $ git status
+    On branch main
+    You are in a sparse checkout with 0% of tracked files present.
+
+    nothing to commit, working tree clean
+    $ git checkout .
+    error: pathspec '.' did not match any file(s) known to git
+    $ git checkout HEAD~1 .
+    Updated 1 path from 58916d9
+    $ git ls-files -t
+    S tracked
+    H tracked-but-maybe-skipped
+
+5. checkout and restore --staged, continued:
+
+   These commands do not correctly scope operations to the sparse
+   specification, and make it worse by not setting important SKIP_WORKTREE
+   bits:
+
+   $ git restore --source OLDREV --staged outside-sparse-cone/
+   $ git status --porcelain
+   MD outside-sparse-cone/file1
+   MD outside-sparse-cone/file2
+   MD outside-sparse-cone/file3
+
+   We can add a --scope=all mode to `git restore` to let it operate outside
+   the sparse specification, but then it will be important to set the
+   SKIP_WORKTREE bits appropriately.
+
+6. Performance issues; see:
+    https://lore.kernel.org/git/CABPp-BEkJQoKZsQGCYioyga_uoDQ6iBeW+FKr8JhyuuTMK1RDw@mail.gmail.com/
+
+
+=== Reference Emails ===
+
+Emails that detail various bugs we've had in sparse-checkout:
+
+[1] (Original descriptions of behavior A & behavior B)
+    https://lore.kernel.org/git/CABPp-BGJ_Nvi5TmgriD9Bh6eNXE2EDq2f8e8QKXAeYG3BxZafA@mail.gmail.com/
+[2] (Fix stash applications in sparse checkouts; bugs from behavioral differences)
+    https://lore.kernel.org/git/ccfedc7140dbf63ba26a15f93bd3885180b26517.1606861519.git.gitgitgadget@gmail.com/
+[3] (Present-despite-skipped entries)
+    https://lore.kernel.org/git/11d46a399d26c913787b704d2b7169cafc28d639.1642175983.git.gitgitgadget@gmail.com/
+[4] (Clone --no-checkout interaction)
+    https://lore.kernel.org/git/pull.801.v2.git.git.1591324899170.gitgitgadget@gmail.com/ (clone --no-checkout)
+[5] (The need for update_sparsity() and avoiding `read-tree -mu HEAD`)
+    https://lore.kernel.org/git/3a1f084641eb47515b5a41ed4409a36128913309.1585270142.git.gitgitgadget@gmail.com/
+[6] (SKIP_WORKTREE is advisory, not mandatory)
+    https://lore.kernel.org/git/844306c3e86ef67591cc086decb2b760e7d710a3.1585270142.git.gitgitgadget@gmail.com/
+[7] (`worktree add` should copy sparsity settings from current worktree)
+    https://lore.kernel.org/git/c51cb3714e7b1d2f8c9370fe87eca9984ff4859f.1644269584.git.gitgitgadget@gmail.com/
+[8] (Avoid negative surprises in add, rm, and mv)
+    https://lore.kernel.org/git/cover.1617914011.git.matheus.bernardino@usp.br/
+    https://lore.kernel.org/git/pull.1018.v4.git.1632497954.gitgitgadget@gmail.com/
+[9] (Move from out-of-cone to in-cone)
+    https://lore.kernel.org/git/20220630023737.473690-6-shaoxuan.yuan02@gmail.com/
+    https://lore.kernel.org/git/20220630023737.473690-4-shaoxuan.yuan02@gmail.com/
+[10] (Unnecessarily downloading objects outside sparse specification)
+     https://lore.kernel.org/git/CAOLTT8QfwOi9yx_qZZgyGa8iL8kHWutEED7ok_jxwTcYT_hf9Q@mail.gmail.com/
+
+[11] (Stolee's comments on high-level usecases)
+     https://lore.kernel.org/git/1a1e33f6-3514-9afc-0a28-5a6b85bd8014@gmail.com/
+
+[12] Others commenting on eventually switching default to behavior A:
+  * https://lore.kernel.org/git/xmqqh719pcoo.fsf@gitster.g/
+  * https://lore.kernel.org/git/xmqqzgeqw0sy.fsf@gitster.g/
+  * https://lore.kernel.org/git/a86af661-cf58-a4e5-0214-a67d3a794d7e@github.com/
+
+[13] Previous config name suggestion and description
+  * https://lore.kernel.org/git/CABPp-BE6zW0nJSStcVU=_DoDBnPgLqOR8pkTXK3dW11=T01OhA@mail.gmail.com/
+
+[14] Tangential issue: switch to cone mode as default sparse specification mechanism:
+  https://lore.kernel.org/git/a1b68fd6126eb341ef3637bb93fedad4309b36d0.1650594746.git.gitgitgadget@gmail.com/
+
+[15] Lengthy email on grep behavior, covering what should be searched:
+  * https://lore.kernel.org/git/CABPp-BGVO3QdbfE84uF_3QDF0-y2iHHh6G5FAFzNRfeRitkuHw@mail.gmail.com/
+
+[16] Email explaining sparsity patterns vs. SKIP_WORKTREE and history operations,
+     search for the parenthetical comment starting "We do not check".
+    https://lore.kernel.org/git/CABPp-BFsCPPNOZ92JQRJeGyNd0e-TCW-LcLyr0i_+VSQJP+GCg@mail.gmail.com/
+
+[17] https://lore.kernel.org/git/20220207190320.2960362-1-jonathantanmy@google.com/
diff --git a/INSTALL b/INSTALL
index 89b15d71df521b2b710b77481e9607c03b78fccc..334478839744b48ce273ca75f464fada3596ce98 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -133,10 +133,6 @@ Issues of note:
          you are using libcurl older than 7.34.0.  Otherwise you can use
          NO_OPENSSL without losing git-imap-send.
 
-         By default, git uses OpenSSL for SHA1 but it will use its own
-         library (inspired by Mozilla's) with either NO_OPENSSL or
-         BLK_SHA1.
-
        - "libcurl" library is used for fetching and pushing
          repositories over http:// or https://, as well as by
          git-imap-send if the curl version is >= 7.34.0. If you do
index 4927379184cf9edb7ebc883c5ef40accea130232..b258fdbed8623d44b014d3ec0dde0350ecb73d43 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -4,8 +4,20 @@ all::
 # Import tree-wide shared Makefile behavior and libraries
 include shared.mak
 
+# == Makefile defines ==
+#
+# These defines change the behavior of the Makefile itself, but have
+# no impact on what it builds:
+#
 # Define V=1 to have a more verbose compile.
 #
+# == Portability and optional library defines ==
+#
+# These defines indicate what Git can expect from the OS, what
+# libraries are available etc. Much of this is auto-detected in
+# config.mak.uname, or in configure.ac when using the optional "make
+# configure && ./configure" (see INSTALL).
+#
 # Define SHELL_PATH to a POSIX shell if your /bin/sh is broken.
 #
 # Define SANE_TOOL_PATH to a colon-separated list of paths to prepend
@@ -30,68 +42,8 @@ include shared.mak
 #
 # Define NO_OPENSSL environment variable if you do not have OpenSSL.
 #
-# Define USE_LIBPCRE if you have and want to use libpcre. Various
-# commands such as log and grep offer runtime options to use
-# Perl-compatible regular expressions instead of standard or extended
-# POSIX regular expressions.
-#
-# Only libpcre version 2 is supported. USE_LIBPCRE2 is a synonym for
-# USE_LIBPCRE, support for the old USE_LIBPCRE1 has been removed.
-#
-# Define LIBPCREDIR=/foo/bar if your PCRE header and library files are
-# in /foo/bar/include and /foo/bar/lib directories.
-#
 # Define HAVE_ALLOCA_H if you have working alloca(3) defined in that header.
 #
-# Define NO_CURL if you do not have libcurl installed.  git-http-fetch and
-# git-http-push are not built, and you cannot use http:// and https://
-# transports (neither smart nor dumb).
-#
-# Define CURLDIR=/foo/bar if your curl header and library files are in
-# /foo/bar/include and /foo/bar/lib directories.
-#
-# Define CURL_CONFIG to curl's configuration program that prints information
-# about the library (e.g., its version number).  The default is 'curl-config'.
-#
-# Define CURL_LDFLAGS to specify flags that you need to link when using libcurl,
-# if you do not want to rely on the libraries provided by CURL_CONFIG.  The
-# default value is a result of `curl-config --libs`.  An example value for
-# CURL_LDFLAGS is as follows:
-#
-#     CURL_LDFLAGS=-lcurl
-#
-# Define NO_EXPAT if you do not have expat installed.  git-http-push is
-# not built, and you cannot push using http:// and https:// transports (dumb).
-#
-# Define EXPATDIR=/foo/bar if your expat header and library files are in
-# /foo/bar/include and /foo/bar/lib directories.
-#
-# Define EXPAT_NEEDS_XMLPARSE_H if you have an old version of expat (e.g.,
-# 1.1 or 1.2) that provides xmlparse.h instead of expat.h.
-#
-# Define NO_GETTEXT if you don't want Git output to be translated.
-# A translated Git requires GNU libintl or another gettext implementation,
-# plus libintl-perl at runtime.
-#
-# Define USE_GETTEXT_SCHEME and set it to 'fallthrough', if you don't trust
-# the installed gettext translation of the shell scripts output.
-#
-# Define HAVE_LIBCHARSET_H if you haven't set NO_GETTEXT and you can't
-# trust the langinfo.h's nl_langinfo(CODESET) function to return the
-# current character set. GNU and Solaris have a nl_langinfo(CODESET),
-# FreeBSD can use either, but MinGW and some others need to use
-# libcharset.h's locale_charset() instead.
-#
-# Define CHARSET_LIB to the library you need to link with in order to
-# use locale_charset() function.  On some platforms this needs to set to
-# -lcharset, on others to -liconv .
-#
-# Define LIBC_CONTAINS_LIBINTL if your gettext implementation doesn't
-# need -lintl when linking.
-#
-# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt
-# doesn't support GNU extensions like --check and --statistics
-#
 # Define HAVE_PATHS_H if you have paths.h and want to use the default PATH
 # it specifies.
 #
@@ -152,39 +104,6 @@ include shared.mak
 # and do not want to use Apple's CommonCrypto library.  This allows you
 # to provide your own OpenSSL library, for example from MacPorts.
 #
-# Define BLK_SHA1 environment variable to make use of the bundled
-# optimized C SHA1 routine.
-#
-# Define DC_SHA1 to unconditionally enable the collision-detecting sha1
-# algorithm. This is slower, but may detect attempted collision attacks.
-# Takes priority over other *_SHA1 knobs.
-#
-# Define DC_SHA1_EXTERNAL in addition to DC_SHA1 if you want to build / link
-# git with the external SHA1 collision-detect library.
-# Without this option, i.e. the default behavior is to build git with its
-# own built-in code (or submodule).
-#
-# Define DC_SHA1_SUBMODULE in addition to DC_SHA1 to use the
-# sha1collisiondetection shipped as a submodule instead of the
-# non-submodule copy in sha1dc/. This is an experimental option used
-# by the git project to migrate to using sha1collisiondetection as a
-# submodule.
-#
-# Define OPENSSL_SHA1 environment variable when running make to link
-# with the SHA1 routine from openssl library.
-#
-# Define SHA1_MAX_BLOCK_SIZE to limit the amount of data that will be hashed
-# in one call to the platform's SHA1_Update(). e.g. APPLE_COMMON_CRYPTO
-# wants 'SHA1_MAX_BLOCK_SIZE=1024L*1024L*1024L' defined.
-#
-# Define BLK_SHA256 to use the built-in SHA-256 routines.
-#
-# Define NETTLE_SHA256 to use the SHA-256 routines in libnettle.
-#
-# Define GCRYPT_SHA256 to use the SHA-256 routines in libgcrypt.
-#
-# Define OPENSSL_SHA256 to use the SHA-256 routines in OpenSSL.
-#
 # Define NEEDS_CRYPTO_WITH_SSL if you need -lcrypto when using -lssl (Darwin).
 #
 # Define NEEDS_SSL_WITH_CRYPTO if you need -lssl when using -lcrypto (Darwin).
@@ -490,6 +409,151 @@ include shared.mak
 # to the "<name>" of the corresponding `compat/fsmonitor/fsm-settings-<name>.c`
 # that implements the `fsm_os_settings__*()` routines.
 #
+# === Optional library: libintl ===
+#
+# Define NO_GETTEXT if you don't want Git output to be translated.
+# A translated Git requires GNU libintl or another gettext implementation,
+# plus libintl-perl at runtime.
+#
+# Define USE_GETTEXT_SCHEME and set it to 'fallthrough', if you don't trust
+# the installed gettext translation of the shell scripts output.
+#
+# Define HAVE_LIBCHARSET_H if you haven't set NO_GETTEXT and you can't
+# trust the langinfo.h's nl_langinfo(CODESET) function to return the
+# current character set. GNU and Solaris have a nl_langinfo(CODESET),
+# FreeBSD can use either, but MinGW and some others need to use
+# libcharset.h's locale_charset() instead.
+#
+# Define CHARSET_LIB to the library you need to link with in order to
+# use locale_charset() function.  On some platforms this needs to set to
+# -lcharset, on others to -liconv .
+#
+# Define LIBC_CONTAINS_LIBINTL if your gettext implementation doesn't
+# need -lintl when linking.
+#
+# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt
+# doesn't support GNU extensions like --check and --statistics
+#
+# === Optional library: libexpat ===
+#
+# Define NO_EXPAT if you do not have expat installed.  git-http-push is
+# not built, and you cannot push using http:// and https:// transports (dumb).
+#
+# Define EXPATDIR=/foo/bar if your expat header and library files are in
+# /foo/bar/include and /foo/bar/lib directories.
+#
+# Define EXPAT_NEEDS_XMLPARSE_H if you have an old version of expat (e.g.,
+# 1.1 or 1.2) that provides xmlparse.h instead of expat.h.
+
+# === Optional library: libcurl ===
+#
+# Define NO_CURL if you do not have libcurl installed.  git-http-fetch and
+# git-http-push are not built, and you cannot use http:// and https://
+# transports (neither smart nor dumb).
+#
+# Define CURLDIR=/foo/bar if your curl header and library files are in
+# /foo/bar/include and /foo/bar/lib directories.
+#
+# Define CURL_CONFIG to curl's configuration program that prints information
+# about the library (e.g., its version number).  The default is 'curl-config'.
+#
+# Define CURL_LDFLAGS to specify flags that you need to link when using libcurl,
+# if you do not want to rely on the libraries provided by CURL_CONFIG.  The
+# default value is a result of `curl-config --libs`.  An example value for
+# CURL_LDFLAGS is as follows:
+#
+#     CURL_LDFLAGS=-lcurl
+#
+# === Optional library: libpcre2 ===
+#
+# Define USE_LIBPCRE if you have and want to use libpcre. Various
+# commands such as log and grep offer runtime options to use
+# Perl-compatible regular expressions instead of standard or extended
+# POSIX regular expressions.
+#
+# Only libpcre version 2 is supported. USE_LIBPCRE2 is a synonym for
+# USE_LIBPCRE, support for the old USE_LIBPCRE1 has been removed.
+#
+# Define LIBPCREDIR=/foo/bar if your PCRE header and library files are
+# in /foo/bar/include and /foo/bar/lib directories.
+#
+# == SHA-1 and SHA-256 defines ==
+#
+# === SHA-1 backend ===
+#
+# ==== Security ====
+#
+# Due to the SHAttered (https://shattered.io) attack vector on SHA-1
+# it's strongly recommended to use the sha1collisiondetection
+# counter-cryptanalysis library for SHA-1 hashing.
+#
+# If you know that you can trust the repository contents, or where
+# potential SHA-1 attacks are otherwise mitigated the other backends
+# listed in "SHA-1 implementations" are faster than
+# sha1collisiondetection.
+#
+# ==== Default SHA-1 backend ====
+#
+# If no *_SHA1 backend is picked, the first supported one listed in
+# "SHA-1 implementations" will be picked.
+#
+# ==== Options common to all SHA-1 implementations ====
+#
+# Define SHA1_MAX_BLOCK_SIZE to limit the amount of data that will be hashed
+# in one call to the platform's SHA1_Update(). e.g. APPLE_COMMON_CRYPTO
+# wants 'SHA1_MAX_BLOCK_SIZE=1024L*1024L*1024L' defined.
+#
+# ==== SHA-1 implementations ====
+#
+# Define OPENSSL_SHA1 to link to the SHA-1 routines from the OpenSSL
+# library.
+#
+# Define BLK_SHA1 to make use of optimized C SHA-1 routines bundled
+# with git (in the block-sha1/ directory).
+#
+# Define NO_APPLE_COMMON_CRYPTO on OSX to opt-out of using the
+# "APPLE_COMMON_CRYPTO" backend for SHA-1, which is currently the
+# default on that OS. On macOS 01.4 (Tiger) or older,
+# NO_APPLE_COMMON_CRYPTO is defined by default.
+#
+# If don't enable any of the *_SHA1 settings in this section, Git will
+# default to its built-in sha1collisiondetection library, which is a
+# collision-detecting sha1 This is slower, but may detect attempted
+# collision attacks.
+#
+# ==== Options for the sha1collisiondetection library ====
+#
+# Define DC_SHA1_EXTERNAL if you want to build / link
+# git with the external SHA1 collision-detect library.
+# Without this option, i.e. the default behavior is to build git with its
+# own built-in code (or submodule).
+#
+# Define DC_SHA1_SUBMODULE to use the
+# sha1collisiondetection shipped as a submodule instead of the
+# non-submodule copy in sha1dc/. This is an experimental option used
+# by the git project to migrate to using sha1collisiondetection as a
+# submodule.
+#
+# === SHA-256 backend ===
+#
+# ==== Security ====
+#
+# Unlike SHA-1 the SHA-256 algorithm does not suffer from any known
+# vulnerabilities, so any implementation will do.
+#
+# ==== SHA-256 implementations ====
+#
+# Define OPENSSL_SHA256 to use the SHA-256 routines in OpenSSL.
+#
+# Define NETTLE_SHA256 to use the SHA-256 routines in libnettle.
+#
+# Define GCRYPT_SHA256 to use the SHA-256 routines in libgcrypt.
+#
+# If don't enable any of the *_SHA256 settings in this section, Git
+# will default to its built-in sha256 implementation.
+#
+# == DEVELOPER defines ==
+#
 # Define DEVELOPER to enable more compiler warnings. Compiler version
 # and family are auto detected, but could be overridden by defining
 # COMPILER_FEATURES (see config.mak.dev). You can still set
@@ -723,6 +787,7 @@ TEST_BUILTINS_OBJS += test-advise.o
 TEST_BUILTINS_OBJS += test-bitmap.o
 TEST_BUILTINS_OBJS += test-bloom.o
 TEST_BUILTINS_OBJS += test-bundle-uri.o
+TEST_BUILTINS_OBJS += test-cache-tree.o
 TEST_BUILTINS_OBJS += test-chmtime.o
 TEST_BUILTINS_OBJS += test-config.o
 TEST_BUILTINS_OBJS += test-crontab.o
@@ -1302,11 +1367,53 @@ SP_EXTRA_FLAGS = -Wno-universal-initializer
 SANITIZE_LEAK =
 SANITIZE_ADDRESS =
 
-# For the 'coccicheck' target; setting SPATCH_BATCH_SIZE higher will
-# usually result in less CPU usage at the cost of higher peak memory.
-# Setting it to 0 will feed all files in a single spatch invocation.
-SPATCH_FLAGS = --all-includes
-SPATCH_BATCH_SIZE = 1
+# For the 'coccicheck' target
+SPATCH_INCLUDE_FLAGS = --all-includes
+SPATCH_FLAGS =
+SPATCH_TEST_FLAGS =
+
+# If *.o files are present, have "coccicheck" depend on them, with
+# COMPUTE_HEADER_DEPENDENCIES this will speed up the common-case of
+# only needing to re-generate coccicheck results for the users of a
+# given API if it's changed, and not all files in the project. If
+# COMPUTE_HEADER_DEPENDENCIES=no this will be unset too.
+SPATCH_USE_O_DEPENDENCIES = YesPlease
+
+# Set SPATCH_CONCAT_COCCI to concatenate the contrib/cocci/*.cocci
+# files into a single contrib/cocci/ALL.cocci before running
+# "coccicheck".
+#
+# Pros:
+#
+# - Speeds up a one-shot run of "make coccicheck", as we won't have to
+#   parse *.[ch] files N times for the N *.cocci rules
+#
+# Cons:
+#
+# - Will make incremental development of *.cocci slower, as
+#   e.g. changing strbuf.cocci will re-run all *.cocci.
+#
+# - Makes error and performance analysis harder, as rules will be
+#   applied from a monolithic ALL.cocci, rather than
+#   e.g. strbuf.cocci. To work around this either undefine this, or
+#   generate a specific patch, e.g. this will always use strbuf.cocci,
+#   not ALL.cocci:
+#
+#      make contrib/coccinelle/strbuf.cocci.patch
+SPATCH_CONCAT_COCCI = YesPlease
+
+# Rebuild 'coccicheck' if $(SPATCH), its flags etc. change
+TRACK_SPATCH_DEFINES =
+TRACK_SPATCH_DEFINES += $(SPATCH)
+TRACK_SPATCH_DEFINES += $(SPATCH_INCLUDE_FLAGS)
+TRACK_SPATCH_DEFINES += $(SPATCH_FLAGS)
+TRACK_SPATCH_DEFINES += $(SPATCH_TEST_FLAGS)
+GIT-SPATCH-DEFINES: FORCE
+       @FLAGS='$(TRACK_SPATCH_DEFINES)'; \
+           if test x"$$FLAGS" != x"`cat GIT-SPATCH-DEFINES 2>/dev/null`" ; then \
+               echo >&2 "    * new spatch flags"; \
+               echo "$$FLAGS" >GIT-SPATCH-DEFINES; \
+            fi
 
 include config.mak.uname
 -include config.mak.autogen
@@ -1826,7 +1933,6 @@ ifdef APPLE_COMMON_CRYPTO
        COMPAT_CFLAGS += -DCOMMON_DIGEST_FOR_OPENSSL
        BASIC_CFLAGS += -DSHA1_APPLE
 else
-       DC_SHA1 := YesPlease
        BASIC_CFLAGS += -DSHA1_DC
        LIB_OBJS += sha1dc_git.o
 ifdef DC_SHA1_EXTERNAL
@@ -2989,7 +3095,6 @@ GIT-BUILD-OPTIONS: FORCE
        @echo NO_REGEX=\''$(subst ','\'',$(subst ','\'',$(NO_REGEX)))'\' >>$@+
        @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@+
        @echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+
-       @echo DC_SHA1=\''$(subst ','\'',$(subst ','\'',$(DC_SHA1)))'\' >>$@+
        @echo SANITIZE_LEAK=\''$(subst ','\'',$(subst ','\'',$(SANITIZE_LEAK)))'\' >>$@+
        @echo SANITIZE_ADDRESS=\''$(subst ','\'',$(subst ','\'',$(SANITIZE_ADDRESS)))'\' >>$@+
        @echo X=\'$(X)\' >>$@+
@@ -3144,35 +3249,113 @@ check: $(GENERATED_H)
                exit 1; \
        fi
 
+COCCI_GEN_ALL = .build/contrib/coccinelle/ALL.cocci
+COCCI_GLOB = $(wildcard contrib/coccinelle/*.cocci)
+COCCI_RULES_TRACKED = $(COCCI_GLOB:%=.build/%)
+COCCI_RULES_TRACKED_NO_PENDING = $(filter-out %.pending.cocci,$(COCCI_RULES_TRACKED))
+COCCI_RULES =
+COCCI_RULES += $(COCCI_GEN_ALL)
+COCCI_RULES += $(COCCI_RULES_TRACKED)
+COCCI_NAMES =
+COCCI_NAMES += $(COCCI_RULES:.build/contrib/coccinelle/%.cocci=%)
+
+COCCICHECK_PENDING = $(filter %.pending.cocci,$(COCCI_RULES))
+COCCICHECK = $(filter-out $(COCCICHECK_PENDING),$(COCCI_RULES))
+
+COCCICHECK_PATCHES = $(COCCICHECK:%=%.patch)
+COCCICHECK_PATCHES_PENDING = $(COCCICHECK_PENDING:%=%.patch)
+
+COCCICHECK_PATCHES_INTREE = $(COCCICHECK_PATCHES:.build/%=%)
+COCCICHECK_PATCHES_PENDING_INTREE = $(COCCICHECK_PATCHES_PENDING:.build/%=%)
+
+# It's expensive to compute the many=many rules below, only eval them
+# on $(MAKECMDGOALS) that match these $(COCCI_RULES)
+COCCI_RULES_GLOB =
+COCCI_RULES_GLOB += cocci%
+COCCI_RULES_GLOB += .build/contrib/coccinelle/%
+COCCI_RULES_GLOB += $(COCCICHECK_PATCHES)
+COCCI_RULES_GLOB += $(COCCICHEC_PATCHES_PENDING)
+COCCI_RULES_GLOB += $(COCCICHECK_PATCHES_INTREE)
+COCCI_RULES_GLOB += $(COCCICHECK_PATCHES_PENDING_INTREE)
+COCCI_GOALS = $(filter $(COCCI_RULES_GLOB),$(MAKECMDGOALS))
+
 COCCI_TEST_RES = $(wildcard contrib/coccinelle/tests/*.res)
 
-%.cocci.patch: %.cocci $(COCCI_SOURCES)
-       $(QUIET_SPATCH) \
-       if test $(SPATCH_BATCH_SIZE) = 0; then \
-               limit=; \
-       else \
-               limit='-n $(SPATCH_BATCH_SIZE)'; \
-       fi; \
-       if ! echo $(COCCI_SOURCES) | xargs $$limit \
-               $(SPATCH) $(SPATCH_FLAGS) \
-               --sp-file $< --patch . \
-               >$@+ 2>$@.log; \
+$(COCCI_RULES_TRACKED): .build/% : %
+       $(call mkdir_p_parent_template)
+       $(QUIET_CP)cp $< $@
+
+.build/contrib/coccinelle/FOUND_H_SOURCES: $(FOUND_H_SOURCES)
+       $(call mkdir_p_parent_template)
+       $(QUIET_GEN) >$@
+
+$(COCCI_GEN_ALL): $(COCCI_RULES_TRACKED_NO_PENDING)
+       $(call mkdir_p_parent_template)
+       $(QUIET_SPATCH_CAT)cat $^ >$@
+
+ifeq ($(COMPUTE_HEADER_DEPENDENCIES),no)
+SPATCH_USE_O_DEPENDENCIES =
+endif
+define cocci-rule
+
+## Rule for .build/$(1).patch/$(2); Params:
+# $(1) = e.g. ".build/contrib/coccinelle/free.cocci"
+# $(2) = e.g. "grep.c"
+# $(3) = e.g. "grep.o"
+COCCI_$(1:.build/contrib/coccinelle/%.cocci=%) += $(1).d/$(2).patch
+$(1).d/$(2).patch: GIT-SPATCH-DEFINES
+$(1).d/$(2).patch: $(if $(and $(SPATCH_USE_O_DEPENDENCIES),$(wildcard $(3))),$(3),.build/contrib/coccinelle/FOUND_H_SOURCES)
+$(1).d/$(2).patch: $(1)
+$(1).d/$(2).patch: $(1).d/%.patch : %
+       $$(call mkdir_p_parent_template)
+       $$(QUIET_SPATCH)if ! $$(SPATCH) $$(SPATCH_FLAGS) \
+               $$(SPATCH_INCLUDE_FLAGS) \
+               --sp-file $(1) --patch . $$< \
+               >$$@ 2>$$@.log; \
        then \
-               cat $@.log; \
+               echo "ERROR when applying '$(1)' to '$$<'; '$$@.log' follows:"; \
+               cat $$@.log; \
                exit 1; \
-       fi; \
-       mv $@+ $@; \
-       if test -s $@; \
+       fi
+endef
+
+define cocci-matrix
+
+$(foreach s,$(COCCI_SOURCES),$(call cocci-rule,$(c),$(s),$(s:%.c=%.o)))
+endef
+
+ifdef COCCI_GOALS
+$(eval $(foreach c,$(COCCI_RULES),$(call cocci-matrix,$(c))))
+endif
+
+define spatch-rule
+
+.build/contrib/coccinelle/$(1).cocci.patch: $$(COCCI_$(1))
+       $$(QUIET_SPATCH_CAT)cat $$^ >$$@ && \
+       if test -s $$@; \
        then \
-               echo '    ' SPATCH result: $@; \
+               echo '    ' SPATCH result: $$@; \
        fi
+contrib/coccinelle/$(1).cocci.patch: .build/contrib/coccinelle/$(1).cocci.patch
+       $$(QUIET_CP)cp $$< $$@
+
+endef
+
+ifdef COCCI_GOALS
+$(eval $(foreach n,$(COCCI_NAMES),$(call spatch-rule,$(n))))
+endif
 
 COCCI_TEST_RES_GEN = $(addprefix .build/,$(COCCI_TEST_RES))
+$(COCCI_TEST_RES_GEN): GIT-SPATCH-DEFINES
 $(COCCI_TEST_RES_GEN): .build/%.res : %.c
 $(COCCI_TEST_RES_GEN): .build/%.res : %.res
+ifdef SPATCH_CONCAT_COCCI
+$(COCCI_TEST_RES_GEN): .build/contrib/coccinelle/tests/%.res : $(COCCI_GEN_ALL)
+else
 $(COCCI_TEST_RES_GEN): .build/contrib/coccinelle/tests/%.res : contrib/coccinelle/%.cocci
+endif
        $(call mkdir_p_parent_template)
-       $(QUIET_SPATCH_T)$(SPATCH) $(SPATCH_FLAGS) \
+       $(QUIET_SPATCH_TEST)$(SPATCH) $(SPATCH_TEST_FLAGS) \
                --very-quiet --no-show-diff \
                --sp-file $< -o $@ \
                $(@:.build/%.res=%.c) && \
@@ -3183,11 +3366,15 @@ $(COCCI_TEST_RES_GEN): .build/contrib/coccinelle/tests/%.res : contrib/coccinell
 coccicheck-test: $(COCCI_TEST_RES_GEN)
 
 coccicheck: coccicheck-test
-coccicheck: $(addsuffix .patch,$(filter-out %.pending.cocci,$(wildcard contrib/coccinelle/*.cocci)))
+ifdef SPATCH_CONCAT_COCCI
+coccicheck: contrib/coccinelle/ALL.cocci.patch
+else
+coccicheck: $(COCCICHECK_PATCHES_INTREE)
+endif
 
 # See contrib/coccinelle/README
 coccicheck-pending: coccicheck-test
-coccicheck-pending: $(addsuffix .patch,$(wildcard contrib/coccinelle/*.pending.cocci))
+coccicheck-pending: $(COCCICHECK_PATCHES_PENDING_INTREE)
 
 .PHONY: coccicheck coccicheck-pending
 
@@ -3454,8 +3641,9 @@ profile-clean:
        $(RM) $(addsuffix *.gcno,$(addprefix $(PROFILE_DIR)/, $(object_dirs)))
 
 cocciclean:
+       $(RM) GIT-SPATCH-DEFINES
        $(RM) -r .build/contrib/coccinelle
-       $(RM) contrib/coccinelle/*.cocci.patch*
+       $(RM) contrib/coccinelle/*.cocci.patch
 
 clean: profile-clean coverage-clean cocciclean
        $(RM) -r .build
index 1d2ce8a0e12c8b6ebfc5c452c2a0b5b688e13d39..6e41cbdb2d3785cadb5ab901586585daee8718e0 100644 (file)
@@ -1279,115 +1279,144 @@ static int bisect_run(struct bisect_terms *terms, const char **argv, int argc)
        return res;
 }
 
+static int cmd_bisect__reset(int argc, const char **argv, const char *prefix UNUSED)
+{
+       if (argc > 1)
+               return error(_("--bisect-reset requires either no argument or a commit"));
+       return bisect_reset(argc ? argv[0] : NULL);
+}
+
+static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED)
+{
+       int res;
+       struct bisect_terms terms = { 0 };
+
+       if (argc > 1)
+               return error(_("--bisect-terms requires 0 or 1 argument"));
+       res = bisect_terms(&terms, argc == 1 ? argv[0] : NULL);
+       free_terms(&terms);
+       return res;
+}
+
+static int cmd_bisect__start(int argc, const char **argv, const char *prefix UNUSED)
+{
+       int res;
+       struct bisect_terms terms = { 0 };
+
+       set_terms(&terms, "bad", "good");
+       res = bisect_start(&terms, argv, argc);
+       free_terms(&terms);
+       return res;
+}
+
+static int cmd_bisect__next(int argc, const char **argv UNUSED, const char *prefix)
+{
+       int res;
+       struct bisect_terms terms = { 0 };
+
+       if (argc)
+               return error(_("--bisect-next requires 0 arguments"));
+       get_terms(&terms);
+       res = bisect_next(&terms, prefix);
+       free_terms(&terms);
+       return res;
+}
+
+static int cmd_bisect__state(int argc, const char **argv, const char *prefix UNUSED)
+{
+       int res;
+       struct bisect_terms terms = { 0 };
+
+       set_terms(&terms, "bad", "good");
+       get_terms(&terms);
+       res = bisect_state(&terms, argv, argc);
+       free_terms(&terms);
+       return res;
+}
+
+static int cmd_bisect__log(int argc, const char **argv UNUSED, const char *prefix UNUSED)
+{
+       if (argc)
+               return error(_("--bisect-log requires 0 arguments"));
+       return bisect_log();
+}
+
+static int cmd_bisect__replay(int argc, const char **argv, const char *prefix UNUSED)
+{
+       int res;
+       struct bisect_terms terms = { 0 };
+
+       if (argc != 1)
+               return error(_("no logfile given"));
+       set_terms(&terms, "bad", "good");
+       res = bisect_replay(&terms, argv[0]);
+       free_terms(&terms);
+       return res;
+}
+
+static int cmd_bisect__skip(int argc, const char **argv, const char *prefix UNUSED)
+{
+       int res;
+       struct bisect_terms terms = { 0 };
+
+       set_terms(&terms, "bad", "good");
+       get_terms(&terms);
+       res = bisect_skip(&terms, argv, argc);
+       free_terms(&terms);
+       return res;
+}
+
+static int cmd_bisect__visualize(int argc, const char **argv, const char *prefix UNUSED)
+{
+       int res;
+       struct bisect_terms terms = { 0 };
+
+       get_terms(&terms);
+       res = bisect_visualize(&terms, argv, argc);
+       free_terms(&terms);
+       return res;
+}
+
+static int cmd_bisect__run(int argc, const char **argv, const char *prefix UNUSED)
+{
+       int res;
+       struct bisect_terms terms = { 0 };
+
+       if (!argc)
+               return error(_("bisect run failed: no command provided."));
+       get_terms(&terms);
+       res = bisect_run(&terms, argv, argc);
+       free_terms(&terms);
+       return res;
+}
+
 int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
 {
-       enum {
-               BISECT_RESET = 1,
-               BISECT_NEXT_CHECK,
-               BISECT_TERMS,
-               BISECT_START,
-               BISECT_AUTOSTART,
-               BISECT_NEXT,
-               BISECT_STATE,
-               BISECT_LOG,
-               BISECT_REPLAY,
-               BISECT_SKIP,
-               BISECT_VISUALIZE,
-               BISECT_RUN,
-       } cmdmode = 0;
-       int res = 0, nolog = 0;
+       int res = 0;
+       parse_opt_subcommand_fn *fn = NULL;
        struct option options[] = {
-               OPT_CMDMODE(0, "bisect-reset", &cmdmode,
-                        N_("reset the bisection state"), BISECT_RESET),
-               OPT_CMDMODE(0, "bisect-next-check", &cmdmode,
-                        N_("check whether bad or good terms exist"), BISECT_NEXT_CHECK),
-               OPT_CMDMODE(0, "bisect-terms", &cmdmode,
-                        N_("print out the bisect terms"), BISECT_TERMS),
-               OPT_CMDMODE(0, "bisect-start", &cmdmode,
-                        N_("start the bisect session"), BISECT_START),
-               OPT_CMDMODE(0, "bisect-next", &cmdmode,
-                        N_("find the next bisection commit"), BISECT_NEXT),
-               OPT_CMDMODE(0, "bisect-state", &cmdmode,
-                        N_("mark the state of ref (or refs)"), BISECT_STATE),
-               OPT_CMDMODE(0, "bisect-log", &cmdmode,
-                        N_("list the bisection steps so far"), BISECT_LOG),
-               OPT_CMDMODE(0, "bisect-replay", &cmdmode,
-                        N_("replay the bisection process from the given file"), BISECT_REPLAY),
-               OPT_CMDMODE(0, "bisect-skip", &cmdmode,
-                        N_("skip some commits for checkout"), BISECT_SKIP),
-               OPT_CMDMODE(0, "bisect-visualize", &cmdmode,
-                        N_("visualize the bisection"), BISECT_VISUALIZE),
-               OPT_CMDMODE(0, "bisect-run", &cmdmode,
-                        N_("use <cmd>... to automatically bisect"), BISECT_RUN),
-               OPT_BOOL(0, "no-log", &nolog,
-                        N_("no log for BISECT_WRITE")),
+               OPT_SUBCOMMAND("reset", &fn, cmd_bisect__reset),
+               OPT_SUBCOMMAND("terms", &fn, cmd_bisect__terms),
+               OPT_SUBCOMMAND("start", &fn, cmd_bisect__start),
+               OPT_SUBCOMMAND("next", &fn, cmd_bisect__next),
+               OPT_SUBCOMMAND("state", &fn, cmd_bisect__state),
+               OPT_SUBCOMMAND("log", &fn, cmd_bisect__log),
+               OPT_SUBCOMMAND("replay", &fn, cmd_bisect__replay),
+               OPT_SUBCOMMAND("skip", &fn, cmd_bisect__skip),
+               OPT_SUBCOMMAND("visualize", &fn, cmd_bisect__visualize),
+               OPT_SUBCOMMAND("view", &fn, cmd_bisect__visualize),
+               OPT_SUBCOMMAND("run", &fn, cmd_bisect__run),
                OPT_END()
        };
-       struct bisect_terms terms = { .term_good = NULL, .term_bad = NULL };
-
        argc = parse_options(argc, argv, prefix, options,
-                            git_bisect_helper_usage,
-                            PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_UNKNOWN_OPT);
+                            git_bisect_helper_usage, 0);
 
-       if (!cmdmode)
+       if (!fn)
                usage_with_options(git_bisect_helper_usage, options);
+       argc--;
+       argv++;
 
-       switch (cmdmode) {
-       case BISECT_RESET:
-               if (argc > 1)
-                       return error(_("--bisect-reset requires either no argument or a commit"));
-               res = bisect_reset(argc ? argv[0] : NULL);
-               break;
-       case BISECT_TERMS:
-               if (argc > 1)
-                       return error(_("--bisect-terms requires 0 or 1 argument"));
-               res = bisect_terms(&terms, argc == 1 ? argv[0] : NULL);
-               break;
-       case BISECT_START:
-               set_terms(&terms, "bad", "good");
-               res = bisect_start(&terms, argv, argc);
-               break;
-       case BISECT_NEXT:
-               if (argc)
-                       return error(_("--bisect-next requires 0 arguments"));
-               get_terms(&terms);
-               res = bisect_next(&terms, prefix);
-               break;
-       case BISECT_STATE:
-               set_terms(&terms, "bad", "good");
-               get_terms(&terms);
-               res = bisect_state(&terms, argv, argc);
-               break;
-       case BISECT_LOG:
-               if (argc)
-                       return error(_("--bisect-log requires 0 arguments"));
-               res = bisect_log();
-               break;
-       case BISECT_REPLAY:
-               if (argc != 1)
-                       return error(_("no logfile given"));
-               set_terms(&terms, "bad", "good");
-               res = bisect_replay(&terms, argv[0]);
-               break;
-       case BISECT_SKIP:
-               set_terms(&terms, "bad", "good");
-               get_terms(&terms);
-               res = bisect_skip(&terms, argv, argc);
-               break;
-       case BISECT_VISUALIZE:
-               get_terms(&terms);
-               res = bisect_visualize(&terms, argv, argc);
-               break;
-       case BISECT_RUN:
-               if (!argc)
-                       return error(_("bisect run failed: no command provided."));
-               get_terms(&terms);
-               res = bisect_run(&terms, argv, argc);
-               break;
-       default:
-               BUG("unknown subcommand %d", cmdmode);
-       }
-       free_terms(&terms);
+       res = fn(argc, argv, prefix);
 
        /*
         * Handle early success
index 15be0c03ef263f59cc24affa5e7693621f869e35..9470c980c15dca8a96c264772991b51813f1b355 100644 (file)
@@ -150,7 +150,7 @@ static int branch_merged(int kind, const char *name,
        if (!reference_rev)
                reference_rev = head_rev;
 
-       merged = in_merge_bases(rev, reference_rev);
+       merged = reference_rev ? in_merge_bases(rev, reference_rev) : 0;
 
        /*
         * After the safety valve is fully redefined to "check with
@@ -160,7 +160,7 @@ static int branch_merged(int kind, const char *name,
         * a gentle reminder is in order.
         */
        if ((head_rev != reference_rev) &&
-           in_merge_bases(rev, head_rev) != merged) {
+           (head_rev ? in_merge_bases(rev, head_rev) : 0) != merged) {
                if (merged)
                        warning(_("deleting branch '%s' that has been merged to\n"
                                "         '%s', but not yet merged to HEAD."),
@@ -235,11 +235,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
        }
        branch_name_pos = strcspn(fmt, "%");
 
-       if (!force) {
+       if (!force)
                head_rev = lookup_commit_reference(the_repository, &head_oid);
-               if (!head_rev)
-                       die(_("Couldn't look up commit object for HEAD"));
-       }
 
        for (i = 0; i < argc; i++, strbuf_reset(&bname)) {
                char *target = NULL;
index d45d873f5797c9d1aa836f5d53e7c5b7b80e9ef4..6aeac37148830d316f533e5399c79ed551fda2fe 100644 (file)
@@ -14,13 +14,16 @@ static int run_command_on_repo(const char *path, int argc, const char ** argv)
 {
        int i;
        struct child_process child = CHILD_PROCESS_INIT;
+       char *abspath = interpolate_path(path, 0);
 
        child.git_cmd = 1;
-       strvec_pushl(&child.args, "-C", path, NULL);
+       strvec_pushl(&child.args, "-C", abspath, NULL);
 
        for (i = 0; i < argc; i++)
                strvec_push(&child.args, argv[i]);
 
+       free(abspath);
+
        return run_command(&child);
 }
 
index 6b08dcf3c56482f3ed794071c5f2ea780e141cdb..02455fdcd73603ad640a7a54c697c7d7f58225cf 100644 (file)
@@ -1480,13 +1480,15 @@ static char *get_maintpath(void)
 }
 
 static char const * const builtin_maintenance_register_usage[] = {
-       "git maintenance register",
+       "git maintenance register [--config-file <path>]",
        NULL
 };
 
 static int maintenance_register(int argc, const char **argv, const char *prefix)
 {
+       char *config_file = NULL;
        struct option options[] = {
+               OPT_STRING(0, "config-file", &config_file, N_("file"), N_("use given config file")),
                OPT_END(),
        };
        int found = 0;
@@ -1523,12 +1525,16 @@ static int maintenance_register(int argc, const char **argv, const char *prefix)
 
        if (!found) {
                int rc;
-               char *user_config, *xdg_config;
-               git_global_config(&user_config, &xdg_config);
-               if (!user_config)
-                       die(_("$HOME not set"));
+               char *user_config = NULL, *xdg_config = NULL;
+
+               if (!config_file) {
+                       git_global_config(&user_config, &xdg_config);
+                       config_file = user_config;
+                       if (!user_config)
+                               die(_("$HOME not set"));
+               }
                rc = git_config_set_multivar_in_file_gently(
-                       user_config, "maintenance.repo", maintpath,
+                       config_file, "maintenance.repo", maintpath,
                        CONFIG_REGEX_NONE, 0);
                free(user_config);
                free(xdg_config);
@@ -1543,14 +1549,16 @@ static int maintenance_register(int argc, const char **argv, const char *prefix)
 }
 
 static char const * const builtin_maintenance_unregister_usage[] = {
-       "git maintenance unregister [--force]",
+       "git maintenance unregister [--config-file <path>] [--force]",
        NULL
 };
 
 static int maintenance_unregister(int argc, const char **argv, const char *prefix)
 {
        int force = 0;
+       char *config_file = NULL;
        struct option options[] = {
+               OPT_STRING(0, "config-file", &config_file, N_("file"), N_("use given config file")),
                OPT__FORCE(&force,
                           N_("return success even if repository was not registered"),
                           PARSE_OPT_NOCOMPLETE),
@@ -1561,6 +1569,7 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi
        int found = 0;
        struct string_list_item *item;
        const struct string_list *list;
+       struct config_set cs = { { 0 } };
 
        argc = parse_options(argc, argv, prefix, options,
                             builtin_maintenance_unregister_usage, 0);
@@ -1568,7 +1577,13 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi
                usage_with_options(builtin_maintenance_unregister_usage,
                                   options);
 
-       list = git_config_get_value_multi(key);
+       if (config_file) {
+               git_configset_init(&cs);
+               git_configset_add_file(&cs, config_file);
+               list = git_configset_get_value_multi(&cs, key);
+       } else {
+               list = git_config_get_value_multi(key);
+       }
        if (list) {
                for_each_string_list_item(item, list) {
                        if (!strcmp(maintpath, item->string)) {
@@ -1580,12 +1595,15 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi
 
        if (found) {
                int rc;
-               char *user_config, *xdg_config;
-               git_global_config(&user_config, &xdg_config);
-               if (!user_config)
-                       die(_("$HOME not set"));
+               char *user_config = NULL, *xdg_config = NULL;
+               if (!config_file) {
+                       git_global_config(&user_config, &xdg_config);
+                       config_file = user_config;
+                       if (!user_config)
+                               die(_("$HOME not set"));
+               }
                rc = git_config_set_multivar_in_file_gently(
-                       user_config, key, NULL, maintpath,
+                       config_file, key, NULL, maintpath,
                        CONFIG_FLAGS_MULTI_REPLACE | CONFIG_FLAGS_FIXED_VALUE);
                free(user_config);
                free(xdg_config);
@@ -1598,6 +1616,7 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi
                die(_("repository '%s' is not registered"), maintpath);
        }
 
+       git_configset_clear(&cs);
        free(maintpath);
        return 0;
 }
index be51f692257f67e2765feb6ff63e8037f3a4f00d..80d9dfd25cad2afcb90c0219894529bcc1558cac 100644 (file)
@@ -181,7 +181,7 @@ static void prepare_note_data(const struct object_id *object, struct note_data *
                strbuf_addch(&buf, '\n');
                strbuf_add_commented_lines(&buf, "\n", strlen("\n"));
                strbuf_add_commented_lines(&buf, _(note_template), strlen(_(note_template)));
-               strbuf_addch(&buf, '\n');
+               strbuf_add_commented_lines(&buf, "\n", strlen("\n"));
                write_or_die(fd, buf.buf, buf.len);
 
                write_commented_object(fd, object);
index f4cbe460b978447ba10bbb3e71b4faee1015d56d..45c6652444b6c12183cc43b38c3bc864b74e028b 100644 (file)
@@ -249,6 +249,10 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
        if (opts.debug_unpack)
                opts.fn = debug_merge;
 
+       /* If we're going to prime_cache_tree later, skip cache tree update */
+       if (nr_trees == 1 && !opts.prefix)
+               opts.skip_cache_tree_update = 1;
+
        cache_tree_free(&active_cache_tree);
        for (i = 0; i < nr_trees; i++) {
                struct tree *tree = trees[i];
index 5d855fd8f51ffe95cde24012e3bed4398b54f85a..4d6839a578587f813752f09f991cb5a71dce0eeb 100644 (file)
@@ -30,8 +30,6 @@
 #include "reset.h"
 #include "hook.h"
 
-#define DEFAULT_REFLOG_ACTION "rebase"
-
 static char const * const builtin_rebase_usage[] = {
        N_("git rebase [-i] [options] [--exec <cmd>] "
                "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"),
@@ -106,6 +104,7 @@ struct rebase_options {
        } flags;
        struct strvec git_am_opts;
        enum action action;
+       char *reflog_action;
        int signoff;
        int allow_rerere_autoupdate;
        int keep_empty;
@@ -159,6 +158,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
                                        opts->committer_date_is_author_date;
        replay.ignore_date = opts->ignore_date;
        replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
+       replay.reflog_action = xstrdup(opts->reflog_action);
        if (opts->strategy)
                replay.strategy = xstrdup_or_null(opts->strategy);
        else if (!replay.strategy && replay.default_strategy) {
@@ -585,10 +585,10 @@ static int move_to_original_branch(struct rebase_options *opts)
                BUG("move_to_original_branch without onto");
 
        strbuf_addf(&branch_reflog, "%s (finish): %s onto %s",
-                   getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
+                   opts->reflog_action,
                    opts->head_name, oid_to_hex(&opts->onto->object.oid));
        strbuf_addf(&head_reflog, "%s (finish): returning to %s",
-                   getenv(GIT_REFLOG_ACTION_ENVIRONMENT), opts->head_name);
+                   opts->reflog_action, opts->head_name);
        ropts.branch = opts->head_name;
        ropts.flags = RESET_HEAD_REFS_ONLY;
        ropts.branch_msg = branch_reflog.buf;
@@ -618,7 +618,7 @@ static int run_am(struct rebase_options *opts)
        am.git_cmd = 1;
        strvec_push(&am.args, "am");
        strvec_pushf(&am.env, GIT_REFLOG_ACTION_ENVIRONMENT "=%s (pick)",
-                    getenv(GIT_REFLOG_ACTION_ENVIRONMENT));
+                    opts->reflog_action);
        if (opts->action == ACTION_CONTINUE) {
                strvec_push(&am.args, "--resolved");
                strvec_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
@@ -685,7 +685,7 @@ static int run_am(struct rebase_options *opts)
 
                ropts.oid = &opts->orig_head->object.oid;
                ropts.branch = opts->head_name;
-               ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
+               ropts.default_reflog_action = opts->reflog_action;
                reset_head(the_repository, &ropts);
                error(_("\ngit encountered an error while preparing the "
                        "patches to replay\n"
@@ -834,8 +834,7 @@ static int checkout_up_to_date(struct rebase_options *options)
        int ret = 0;
 
        strbuf_addf(&buf, "%s: checkout %s",
-                   getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
-                   options->switch_to);
+                   options->reflog_action, options->switch_to);
        ropts.oid = &options->orig_head->object.oid;
        ropts.branch = options->head_name;
        ropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
@@ -1243,7 +1242,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
        if (options.action != ACTION_NONE && !in_progress)
                die(_("No rebase in progress?"));
-       setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0);
 
        if (options.action == ACTION_EDIT_TODO && !is_merge(&options))
                die(_("The --edit-todo action can only be used during "
@@ -1258,6 +1256,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                        trace2_cmd_mode(action_names[options.action]);
        }
 
+       options.reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
+       options.reflog_action =
+               xstrdup(options.reflog_action ? options.reflog_action : "rebase");
+
        switch (options.action) {
        case ACTION_CONTINUE: {
                struct object_id head;
@@ -1310,7 +1312,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                        exit(1);
 
                strbuf_addf(&head_msg, "%s (abort): returning to %s",
-                           getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
+                           options.reflog_action,
                            options.head_name ? options.head_name
                                              : oid_to_hex(&options.orig_head->object.oid));
                ropts.oid = &options.orig_head->object.oid;
@@ -1786,13 +1788,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                         "it...\n"));
 
        strbuf_addf(&msg, "%s (start): checkout %s",
-                   getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);
+                   options.reflog_action, options.onto_name);
        ropts.oid = &options.onto->object.oid;
        ropts.orig_head = &options.orig_head->object.oid,
        ropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |
                        RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
        ropts.head_msg = msg.buf;
-       ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
+       ropts.default_reflog_action = options.reflog_action;
        if (reset_head(the_repository, &ropts))
                die(_("Could not detach HEAD"));
        strbuf_release(&msg);
@@ -1824,6 +1826,7 @@ run_rebase:
 cleanup:
        strbuf_release(&buf);
        strbuf_release(&revisions);
+       free(options.reflog_action);
        free(options.head_name);
        free(options.gpg_sign_opt);
        free(options.cmd);
index 10e23f9ee1fc4baea34152c23ea8ab5459986ae5..65eb1b8bd22b4119908abe700cfd3a6d143289f1 100644 (file)
@@ -32,7 +32,6 @@ static int write_bitmaps = -1;
 static int use_delta_islands;
 static int run_update_server_info = 1;
 static char *packdir, *packtmp_name, *packtmp;
-static char *cruft_expiration;
 
 static const char *const git_repack_usage[] = {
        N_("git repack [<options>]"),
@@ -150,7 +149,8 @@ static void remove_redundant_pack(const char *dir_name, const char *base_name)
 }
 
 static void prepare_pack_objects(struct child_process *cmd,
-                                const struct pack_objects_args *args)
+                                const struct pack_objects_args *args,
+                                const char *out)
 {
        strvec_push(&cmd->args, "pack-objects");
        if (args->window)
@@ -173,7 +173,7 @@ static void prepare_pack_objects(struct child_process *cmd,
                strvec_push(&cmd->args,  "--quiet");
        if (delta_base_offset)
                strvec_push(&cmd->args,  "--delta-base-offset");
-       strvec_push(&cmd->args, packtmp);
+       strvec_push(&cmd->args, out);
        cmd->git_cmd = 1;
        cmd->out = -1;
 }
@@ -241,7 +241,7 @@ static void repack_promisor_objects(const struct pack_objects_args *args,
        FILE *out;
        struct strbuf line = STRBUF_INIT;
 
-       prepare_pack_objects(&cmd, args);
+       prepare_pack_objects(&cmd, args, packtmp);
        cmd.in = -1;
 
        /*
@@ -657,7 +657,9 @@ static void remove_redundant_bitmaps(struct string_list *include,
 }
 
 static int write_cruft_pack(const struct pack_objects_args *args,
+                           const char *destination,
                            const char *pack_prefix,
+                           const char *cruft_expiration,
                            struct string_list *names,
                            struct string_list *existing_packs,
                            struct string_list *existing_kept_packs)
@@ -667,8 +669,10 @@ static int write_cruft_pack(const struct pack_objects_args *args,
        struct string_list_item *item;
        FILE *in, *out;
        int ret;
+       const char *scratch;
+       int local = skip_prefix(destination, packdir, &scratch);
 
-       prepare_pack_objects(&cmd, args);
+       prepare_pack_objects(&cmd, args, destination);
 
        strvec_push(&cmd.args, "--cruft");
        if (cruft_expiration)
@@ -693,6 +697,10 @@ static int write_cruft_pack(const struct pack_objects_args *args,
         * By the time it is read here, it contains only the pack(s)
         * that were just written, which is exactly the set of packs we
         * want to consider kept.
+        *
+        * If `--expire-to` is given, the double-use served by `names`
+        * ensures that the pack written to `--expire-to` excludes any
+        * objects contained in the cruft pack.
         */
        in = xfdopen(cmd.in, "w");
        for_each_string_list_item(item, names)
@@ -710,9 +718,14 @@ static int write_cruft_pack(const struct pack_objects_args *args,
                if (line.len != the_hash_algo->hexsz)
                        die(_("repack: Expecting full hex object ID lines only "
                              "from pack-objects."));
-
-               item = string_list_append(names, line.buf);
-               item->util = populate_pack_exts(line.buf);
+               /*
+                * avoid putting packs written outside of the repository in the
+                * list of names
+                */
+               if (local) {
+                       item = string_list_append(names, line.buf);
+                       item->util = populate_pack_exts(line.buf);
+               }
        }
        fclose(out);
 
@@ -744,6 +757,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
        struct pack_objects_args cruft_po_args = {NULL};
        int geometric_factor = 0;
        int write_midx = 0;
+       const char *cruft_expiration = NULL;
+       const char *expire_to = NULL;
 
        struct option builtin_repack_options[] = {
                OPT_BIT('a', NULL, &pack_everything,
@@ -793,6 +808,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
                            N_("find a geometric progression with factor <N>")),
                OPT_BOOL('m', "write-midx", &write_midx,
                           N_("write a multi-pack index of the resulting packs")),
+               OPT_STRING(0, "expire-to", &expire_to, N_("dir"),
+                          N_("pack prefix to store a pack containing pruned objects")),
                OPT_END()
        };
 
@@ -858,7 +875,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
                split_pack_geometry(geometry, geometric_factor);
        }
 
-       prepare_pack_objects(&cmd, &po_args);
+       prepare_pack_objects(&cmd, &po_args, packtmp);
 
        show_progress = !po_args.quiet && isatty(2);
 
@@ -984,11 +1001,45 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
                cruft_po_args.local = po_args.local;
                cruft_po_args.quiet = po_args.quiet;
 
-               ret = write_cruft_pack(&cruft_po_args, pack_prefix, &names,
+               ret = write_cruft_pack(&cruft_po_args, packtmp, pack_prefix,
+                                      cruft_expiration, &names,
                                       &existing_nonkept_packs,
                                       &existing_kept_packs);
                if (ret)
                        return ret;
+
+               if (delete_redundant && expire_to) {
+                       /*
+                        * If `--expire-to` is given with `-d`, it's possible
+                        * that we're about to prune some objects. With cruft
+                        * packs, pruning is implicit: any objects from existing
+                        * packs that weren't picked up by new packs are removed
+                        * when their packs are deleted.
+                        *
+                        * Generate an additional cruft pack, with one twist:
+                        * `names` now includes the name of the cruft pack
+                        * written in the previous step. So the contents of
+                        * _this_ cruft pack exclude everything contained in the
+                        * existing cruft pack (that is, all of the unreachable
+                        * objects which are no older than
+                        * `--cruft-expiration`).
+                        *
+                        * To make this work, cruft_expiration must become NULL
+                        * so that this cruft pack doesn't actually prune any
+                        * objects. If it were non-NULL, this call would always
+                        * generate an empty pack (since every object not in the
+                        * cruft pack generated above will have an mtime older
+                        * than the expiration).
+                        */
+                       ret = write_cruft_pack(&cruft_po_args, expire_to,
+                                              pack_prefix,
+                                              NULL,
+                                              &names,
+                                              &existing_nonkept_packs,
+                                              &existing_kept_packs);
+                       if (ret)
+                               return ret;
+               }
        }
 
        string_list_sort(&names);
index fdce6f8c85670c8b2b0e20304336debba3190408..ab0277748240c0d74e92a5bb5e0105af956d88a6 100644 (file)
@@ -73,9 +73,11 @@ static int reset_index(const char *ref, const struct object_id *oid, int reset_t
        case HARD:
                opts.update = 1;
                opts.reset = UNPACK_RESET_OVERWRITE_UNTRACKED;
+               opts.skip_cache_tree_update = 1;
                break;
        case MIXED:
                opts.reset = UNPACK_RESET_PROTECT_UNTRACKED;
+               opts.skip_cache_tree_update = 1;
                /* but opts.update=0, so working tree not updated */
                break;
        default:
index f0d025a4e23d3ab28f34e282e0e2ef43459b2688..05bfe20a4690d35b4d4918e03de1bc8a1fc9ecad 100644 (file)
@@ -86,8 +86,7 @@ static void submodules_absorb_gitdir_if_needed(void)
                        continue;
 
                if (!submodule_uses_gitfile(name))
-                       absorb_git_dir_into_superproject(name,
-                               ABSORB_GITDIR_RECURSE_SUBMODULES);
+                       absorb_git_dir_into_superproject(name);
        }
 }
 
index a7683d35299921c3a00e74e19cdad47379bfd1bb..c75e9e86b06e986d6d125209ec911009786aea67 100644 (file)
@@ -616,6 +616,9 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
        int diff_files_result;
        struct strbuf buf = STRBUF_INIT;
        const char *git_dir;
+       struct setup_revision_opt opt = {
+               .free_removed_argv_elements = 1,
+       };
 
        if (!submodule_from_path(the_repository, null_oid(), path))
                die(_("no submodule mapping found in .gitmodules for path '%s'"),
@@ -649,9 +652,7 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
 
        repo_init_revisions(the_repository, &rev, NULL);
        rev.abbrev = 0;
-       diff_files_args.nr = setup_revisions(diff_files_args.nr,
-                                            diff_files_args.v,
-                                            &rev, NULL);
+       setup_revisions(diff_files_args.nr, diff_files_args.v, &rev, &opt);
        diff_files_result = run_diff_files(&rev, 0);
 
        if (!diff_result_code(&rev.diffopt, diff_files_result)) {
@@ -1378,8 +1379,7 @@ static void deinit_submodule(const char *path, const char *prefix,
                                          ".git file by using absorbgitdirs."),
                                        displaypath);
 
-                       absorb_git_dir_into_superproject(path,
-                                                        ABSORB_GITDIR_RECURSE_SUBMODULES);
+                       absorb_git_dir_into_superproject(path);
 
                }
 
@@ -2643,9 +2643,6 @@ static int module_update(int argc, const char **argv, const char *prefix)
                         N_("traverse submodules recursively")),
                OPT_BOOL('N', "no-fetch", &opt.nofetch,
                         N_("don't fetch new objects from the remote site")),
-               OPT_STRING(0, "prefix", &opt.prefix,
-                          N_("path"),
-                          N_("path into the working tree")),
                OPT_SET_INT(0, "checkout", &opt.update_default,
                        N_("use the 'checkout' update strategy (default)"),
                        SM_UPDATE_CHECKOUT),
@@ -2701,6 +2698,7 @@ static int module_update(int argc, const char **argv, const char *prefix)
        }
 
        opt.filter_options = &filter_options;
+       opt.prefix = prefix;
 
        if (opt.update_default)
                opt.update_strategy.type = opt.update_default;
@@ -2830,13 +2828,7 @@ static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
        int i;
        struct pathspec pathspec = { 0 };
        struct module_list list = MODULE_LIST_INIT;
-       unsigned flags = ABSORB_GITDIR_RECURSE_SUBMODULES;
        struct option embed_gitdir_options[] = {
-               OPT_STRING(0, "prefix", &prefix,
-                          N_("path"),
-                          N_("path into the working tree")),
-               OPT_BIT(0, "--recursive", &flags, N_("recurse into submodules"),
-                       ABSORB_GITDIR_RECURSE_SUBMODULES),
                OPT_END()
        };
        const char *const git_submodule_helper_usage[] = {
@@ -2852,7 +2844,7 @@ static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
                goto cleanup;
 
        for (i = 0; i < list.nr; i++)
-               absorb_git_dir_into_superproject(list.entries[i]->name, flags);
+               absorb_git_dir_into_superproject(list.entries[i]->name);
 
        ret = 0;
 cleanup:
@@ -2861,51 +2853,6 @@ cleanup:
        return ret;
 }
 
-static int module_config(int argc, const char **argv, const char *prefix)
-{
-       enum {
-               CHECK_WRITEABLE = 1,
-               DO_UNSET = 2
-       } command = 0;
-       struct option module_config_options[] = {
-               OPT_CMDMODE(0, "check-writeable", &command,
-                           N_("check if it is safe to write to the .gitmodules file"),
-                           CHECK_WRITEABLE),
-               OPT_CMDMODE(0, "unset", &command,
-                           N_("unset the config in the .gitmodules file"),
-                           DO_UNSET),
-               OPT_END()
-       };
-       const char *const git_submodule_helper_usage[] = {
-               N_("git submodule--helper config <name> [<value>]"),
-               N_("git submodule--helper config --unset <name>"),
-               "git submodule--helper config --check-writeable",
-               NULL
-       };
-
-       argc = parse_options(argc, argv, prefix, module_config_options,
-                            git_submodule_helper_usage, PARSE_OPT_KEEP_ARGV0);
-
-       if (argc == 1 && command == CHECK_WRITEABLE)
-               return is_writing_gitmodules_ok() ? 0 : -1;
-
-       /* Equivalent to ACTION_GET in builtin/config.c */
-       if (argc == 2 && command != DO_UNSET)
-               return print_config_from_gitmodules(the_repository, argv[1]);
-
-       /* Equivalent to ACTION_SET in builtin/config.c */
-       if (argc == 3 || (argc == 2 && command == DO_UNSET)) {
-               const char *value = (argc == 3) ? argv[2] : NULL;
-
-               if (!is_writing_gitmodules_ok())
-                       die(_("please make sure that the .gitmodules file is in the working tree"));
-
-               return config_set_in_gitmodules_file_gently(argv[1], value);
-       }
-
-       usage_with_options(git_submodule_helper_usage, module_config_options);
-}
-
 static int module_set_url(int argc, const char **argv, const char *prefix)
 {
        int quiet = 0;
@@ -3404,48 +3351,45 @@ cleanup:
        return ret;
 }
 
-#define SUPPORT_SUPER_PREFIX (1<<0)
-
-struct cmd_struct {
-       const char *cmd;
-       int (*fn)(int, const char **, const char *);
-       unsigned option;
-};
-
-static struct cmd_struct commands[] = {
-       {"clone", module_clone, SUPPORT_SUPER_PREFIX},
-       {"add", module_add, 0},
-       {"update", module_update, SUPPORT_SUPER_PREFIX},
-       {"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
-       {"init", module_init, 0},
-       {"status", module_status, SUPPORT_SUPER_PREFIX},
-       {"sync", module_sync, SUPPORT_SUPER_PREFIX},
-       {"deinit", module_deinit, 0},
-       {"summary", module_summary, 0},
-       {"push-check", push_check, 0},
-       {"absorbgitdirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
-       {"config", module_config, 0},
-       {"set-url", module_set_url, 0},
-       {"set-branch", module_set_branch, 0},
-       {"create-branch", module_create_branch, 0},
-};
-
 int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
 {
-       int i;
-       if (argc < 2 || !strcmp(argv[1], "-h"))
-               usage("git submodule--helper <command>");
-
-       for (i = 0; i < ARRAY_SIZE(commands); i++) {
-               if (!strcmp(argv[1], commands[i].cmd)) {
-                       if (get_super_prefix() &&
-                           !(commands[i].option & SUPPORT_SUPER_PREFIX))
-                               die(_("%s doesn't support --super-prefix"),
-                                   commands[i].cmd);
-                       return commands[i].fn(argc - 1, argv + 1, prefix);
-               }
-       }
+       const char *cmd = argv[0];
+       const char *subcmd;
+       parse_opt_subcommand_fn *fn = NULL;
+       const char *const usage[] = {
+               N_("git submodule--helper <command>"),
+               NULL
+       };
+       struct option options[] = {
+               OPT_SUBCOMMAND("clone", &fn, module_clone),
+               OPT_SUBCOMMAND("add", &fn, module_add),
+               OPT_SUBCOMMAND("update", &fn, module_update),
+               OPT_SUBCOMMAND("foreach", &fn, module_foreach),
+               OPT_SUBCOMMAND("init", &fn, module_init),
+               OPT_SUBCOMMAND("status", &fn, module_status),
+               OPT_SUBCOMMAND("sync", &fn, module_sync),
+               OPT_SUBCOMMAND("deinit", &fn, module_deinit),
+               OPT_SUBCOMMAND("summary", &fn, module_summary),
+               OPT_SUBCOMMAND("push-check", &fn, push_check),
+               OPT_SUBCOMMAND("absorbgitdirs", &fn, absorb_git_dirs),
+               OPT_SUBCOMMAND("set-url", &fn, module_set_url),
+               OPT_SUBCOMMAND("set-branch", &fn, module_set_branch),
+               OPT_SUBCOMMAND("create-branch", &fn, module_create_branch),
+               OPT_END()
+       };
+       argc = parse_options(argc, argv, prefix, options, usage, 0);
+       subcmd = argv[0];
+
+       if (strcmp(subcmd, "clone") && strcmp(subcmd, "update") &&
+           strcmp(subcmd, "foreach") && strcmp(subcmd, "status") &&
+           strcmp(subcmd, "sync") && strcmp(subcmd, "absorbgitdirs") &&
+           get_super_prefix())
+               /*
+                * xstrfmt() rather than "%s %s" to keep the translated
+                * string identical to git.c's.
+                */
+               die(_("%s doesn't support --super-prefix"),
+                   xstrfmt("'%s %s'", cmd, subcmd));
 
-       die(_("'%s' is not a valid submodule--helper "
-             "subcommand"), argv[1]);
+       return fn(argc, argv, prefix);
 }
index 1808e3b1ce1d1c12428511bac6cba5182991ce36..24d20a5d648d74596d57bddb29597e4766cf275f 100755 (executable)
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -260,7 +260,7 @@ macos-latest)
        else
                MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python2)"
                MAKEFLAGS="$MAKEFLAGS NO_APPLE_COMMON_CRYPTO=NoThanks"
-               MAKEFLAGS="$MAKEFLAGS DC_SHA1=YesPlease NO_OPENSSL=NoThanks"
+               MAKEFLAGS="$MAKEFLAGS NO_OPENSSL=NoThanks"
        fi
        ;;
 esac
index 3957e4cf8cd5be6e2524b50a51032d6a0b811aaa..2f6e0197ffa489ccc14b0665cdf78dba43b39e3e 100644 (file)
@@ -1025,7 +1025,6 @@ set(NO_PERL )
 set(NO_PTHREADS )
 set(NO_PYTHON )
 set(PAGER_ENV "LESS=FRX LV=-c")
-set(DC_SHA1 YesPlease)
 set(RUNTIME_PREFIX true)
 set(NO_GETTEXT )
 
@@ -1061,7 +1060,6 @@ file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_PERL='${NO_PERL}'\n")
 file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_PTHREADS='${NO_PTHREADS}'\n")
 file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_UNIX_SOCKETS='${NO_UNIX_SOCKETS}'\n")
 file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "PAGER_ENV='${PAGER_ENV}'\n")
-file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "DC_SHA1='${DC_SHA1}'\n")
 file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "X='${EXE_EXTENSION}'\n")
 file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_GETTEXT='${NO_GETTEXT}'\n")
 file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "RUNTIME_PREFIX='${RUNTIME_PREFIX}'\n")
index d3f29646dc3afa52cb960f190fe2f62e9887a501..1d45c0a40c81ee6f8501cac7a204a0b9cdca376b 100644 (file)
@@ -1 +1 @@
-*.patch*
+*.patch
index f0e80bd7f037731530e7099dc2b27985dd880be3..d1daa1f62639a1bbd37e4aedf6f51c1d8d831b63 100644 (file)
@@ -41,3 +41,52 @@ There are two types of semantic patches:
 
    This allows to expose plans of pending large scale refactorings without
    impacting the bad pattern checks.
+
+Git-specific tips & things to know about how we run "spatch":
+
+ * The "make coccicheck" will piggy-back on
+   "COMPUTE_HEADER_DEPENDENCIES". If you've built a given object file
+   the "coccicheck" target will consider its depednency to decide if
+   it needs to re-run on the corresponding source file.
+
+   This means that a "make coccicheck" will re-compile object files
+   before running. This might be unexpected, but speeds up the run in
+   the common case, as e.g. a change to "column.h" won't require all
+   coccinelle rules to be re-run against "grep.c" (or another file
+   that happens not to use "column.h").
+
+   To disable this behavior use the "SPATCH_USE_O_DEPENDENCIES=NoThanks"
+   flag.
+
+ * To speed up our rules the "make coccicheck" target will by default
+   concatenate all of the *.cocci files here into an "ALL.cocci", and
+   apply it to each source file.
+
+   This makes the run faster, as we don't need to run each rule
+   against each source file. See the Makefile for further discussion,
+   this behavior can be disabled with "SPATCH_CONCAT_COCCI=".
+
+   But since they're concatenated any <id> in the <rulname> (e.g. "@
+   my_name", v.s. anonymous "@@") needs to be unique across all our
+   *.cocci files. You should only need to name rules if other rules
+   depend on them (currently only one rule is named).
+
+ * To speed up incremental runs even more use the "spatchcache" tool
+   in this directory as your "SPATCH". It aimns to be a "ccache" for
+   coccinelle, and piggy-backs on "COMPUTE_HEADER_DEPENDENCIES".
+
+   It caches in Redis by default, see it source for a how-to.
+
+   In one setup with a primed cache "make coccicheck" followed by a
+   "make clean && make" takes around 10s to run, but 2m30s with the
+   default of "SPATCH_CONCAT_COCCI=Y".
+
+   With "SPATCH_CONCAT_COCCI=" the total runtime is around ~6m, sped
+   up to ~1m with "spatchcache".
+
+   Most of the 10s (or ~1m) being spent on re-running "spatch" on
+   files we couldn't cache, as we didn't compile them (in contrib/*
+   and compat/* mostly).
+
+   The absolute times will differ for you, but the relative speedup
+   from caching should be on that order.
index d69e120ccffc04b29e7e6f83ac382f3492b82037..c5dbb4557b56b130d1ed518af1ccdff6c112d7a6 100644 (file)
@@ -1,4 +1,4 @@
-@ hashmap_entry_init_usage @
+@@
 expression E;
 struct hashmap_entry HME;
 @@
index 7fe1e8d2d9a0167bec1289da81bbe4b4cae6fe4f..ae42cb07302d5e4e480f85b1a1bac9e05474966f 100644 (file)
@@ -1,4 +1,4 @@
-@ preincrement @
+@@
 identifier i;
 @@
 - ++i > 1
diff --git a/contrib/coccinelle/spatchcache b/contrib/coccinelle/spatchcache
new file mode 100755 (executable)
index 0000000..29e9352
--- /dev/null
@@ -0,0 +1,304 @@
+#!/bin/sh
+#
+# spatchcache: a poor-man's "ccache"-alike for "spatch" in git.git
+#
+# This caching command relies on the peculiarities of the Makefile
+# driving "spatch" in git.git, in particular if we invoke:
+#
+#      make
+#      # See "spatchCache.cacheWhenStderr" for why "--very-quiet" is
+#      # used
+#      make coccicheck SPATCH_FLAGS=--very-quiet
+#
+# We can with COMPUTE_HEADER_DEPENDENCIES (auto-detected as true with
+# "gcc" and "clang") write e.g. a .depend/grep.o.d for grep.c, when we
+# compile grep.o.
+#
+# The .depend/grep.o.d will have the full header dependency tree of
+# grep.c, and we can thus cache the output of "spatch" by:
+#
+#      1. Hashing all of those files
+#      2. Hashing our source file, and the *.cocci rule we're
+#         applying
+#      3. Running spatch, if suggests no changes (by far the common
+#         case) we invoke "spatchCache.getCmd" and
+#         "spatchCache.setCmd" with a hash SHA-256 to ask "does this
+#         ID have no changes" or "say that ID had no changes>
+#      4. If no "spatchCache.{set,get}Cmd" is specified we'll use
+#         "redis-cli" and maintain a SET called "spatch-cache". Set
+#         appropriate redis memory policies to keep it from growing
+#         out of control.
+#
+# This along with the general incremental "make" support for
+# "contrib/coccinelle" makes it viable to (re-)run coccicheck
+# e.g. when merging integration branches.
+#
+# Note that the "--very-quiet" flag is currently critical. The cache
+# will refuse to cache anything that has output on STDERR (which might
+# be errors from spatch), but see spatchCache.cacheWhenStderr below.
+#
+# The STDERR (and exit code) could in principle be cached (as with
+# ccache), but then the simple structure in the Redis cache would need
+# to change, so just supply "--very-quiet" for now.
+#
+# To use this, simply set SPATCH to
+# contrib/coccinelle/spatchcache. Then optionally set:
+#
+#      [spatchCache]
+#              # Optional: path to a custom spatch
+#              spatch = ~/g/coccicheck/spatch.opt
+#
+# As well as this trace config (debug implies trace):
+#
+#              cacheWhenStderr = true
+#              trace = false
+#              debug = false
+#
+# The ".depend/grep.o.d" can also be customized, as a string that will
+# be eval'd, it has access to a "$dirname" and "$basename":
+#
+#      [spatchCache]
+#              dependFormat = "$dirname/.depend/${basename%.c}.o.d"
+#
+# Setting "trace" to "true" allows for seeing when we have a cache HIT
+# or MISS. To debug whether the cache is working do that, and run e.g.:
+#
+#      redis-cli FLUSHALL
+#      <make && make coccicheck, as above>
+#      grep -hore HIT -e MISS -e SET -e NOCACHE -e CANTCACHE .build/contrib/coccinelle | sort | uniq -c
+#          600 CANTCACHE
+#         7365 MISS
+#         7365 SET
+#
+# A subsequent "make cocciclean && make coccicheck" should then have
+# all "HIT"'s and "CANTCACHE"'s.
+#
+# The "spatchCache.cacheWhenStderr" option is critical when using
+# spatchCache.{trace,debug} to debug whether something is set in the
+# cache, as we'll write to the spatch logs in .build/* we'd otherwise
+# always emit a NOCACHE.
+#
+# Reading the config can make the command much slower, to work around
+# this the config can be set in the environment, with environment
+# variable name corresponding to the config key. "default" can be used
+# to use whatever's the script default, e.g. setting
+# spatchCache.cacheWhenStderr=true and deferring to the defaults for
+# the rest is:
+#
+#      export GIT_CONTRIB_SPATCHCACHE_DEBUG=default
+#      export GIT_CONTRIB_SPATCHCACHE_TRACE=default
+#      export GIT_CONTRIB_SPATCHCACHE_CACHEWHENSTDERR=true
+#      export GIT_CONTRIB_SPATCHCACHE_SPATCH=default
+#      export GIT_CONTRIB_SPATCHCACHE_DEPENDFORMAT=default
+#      export GIT_CONTRIB_SPATCHCACHE_SETCMD=default
+#      export GIT_CONTRIB_SPATCHCACHE_GETCMD=default
+
+set -e
+
+env_or_config () {
+       env="$1"
+       shift
+       if test "$env" = "default"
+       then
+               # Avoid expensive "git config" invocation
+               return
+       elif test -n "$env"
+       then
+               echo "$env"
+       else
+               git config $@ || :
+       fi
+}
+
+## Our own configuration & options
+debug=$(env_or_config "$GIT_CONTRIB_SPATCHCACHE_DEBUG" --bool "spatchCache.debug")
+if test "$debug" != "true"
+then
+       debug=
+fi
+if test -n "$debug"
+then
+       set -x
+fi
+
+trace=$(env_or_config "$GIT_CONTRIB_SPATCHCACHE_TRACE" --bool "spatchCache.trace")
+if test "$trace" != "true"
+then
+       trace=
+fi
+if test -n "$debug"
+then
+       # debug implies trace
+       trace=true
+fi
+
+cacheWhenStderr=$(env_or_config "$GIT_CONTRIB_SPATCHCACHE_CACHEWHENSTDERR" --bool "spatchCache.cacheWhenStderr")
+if test "$cacheWhenStderr" != "true"
+then
+       cacheWhenStderr=
+fi
+
+trace_it () {
+       if test -z "$trace"
+       then
+               return
+       fi
+       echo "$@" >&2
+}
+
+spatch=$(env_or_config "$GIT_CONTRIB_SPATCHCACHE_SPATCH" --path "spatchCache.spatch")
+if test -n "$spatch"
+then
+       if test -n "$debug"
+       then
+               trace_it "custom spatchCache.spatch='$spatch'"
+       fi
+else
+       spatch=spatch
+fi
+
+dependFormat='$dirname/.depend/${basename%.c}.o.d'
+dependFormatCfg=$(env_or_config "$GIT_CONTRIB_SPATCHCACHE_DEPENDFORMAT" "spatchCache.dependFormat")
+if test -n "$dependFormatCfg"
+then
+       dependFormat="$dependFormatCfg"
+fi
+
+set=$(env_or_config "$GIT_CONTRIB_SPATCHCACHE_SETCMD" "spatchCache.setCmd")
+get=$(env_or_config "$GIT_CONTRIB_SPATCHCACHE_GETCMD" "spatchCache.getCmd")
+
+## Parse spatch()-like command-line for caching info
+arg_sp=
+arg_file=
+args="$@"
+spatch_opts() {
+       while test $# != 0
+       do
+               arg_file="$1"
+               case "$1" in
+               --sp-file)
+                       arg_sp="$2"
+                       ;;
+               esac
+               shift
+       done
+}
+spatch_opts "$@"
+if ! test -f "$arg_file"
+then
+       arg_file=
+fi
+
+hash_for_cache() {
+       # Parameters that should affect the cache
+       echo "args=$args"
+       echo "config spatchCache.spatch=$spatch"
+       echo "config spatchCache.debug=$debug"
+       echo "config spatchCache.trace=$trace"
+       echo "config spatchCache.cacheWhenStderr=$cacheWhenStderr"
+       echo
+
+       # Our target file and its dependencies
+       git hash-object "$1" "$2" $(grep -E -o '^[^:]+:$' "$3" | tr -d ':')
+}
+
+# Sanity checks
+if ! test -f "$arg_sp" && ! test -f "$arg_file"
+then
+       echo $0: no idea how to cache "$@" >&2
+       exit 128
+fi
+
+# Main logic
+dirname=$(dirname "$arg_file")
+basename=$(basename "$arg_file")
+eval "dep=$dependFormat"
+
+if ! test -f "$dep"
+then
+       trace_it "$0: CANTCACHE have no '$dep' for '$arg_file'!"
+       exec "$spatch" "$@"
+fi
+
+if test -n "$debug"
+then
+       trace_it "$0: The full cache input for '$arg_sp' '$arg_file' '$dep'"
+       hash_for_cache "$arg_sp" "$arg_file" "$dep" >&2
+fi
+sum=$(hash_for_cache "$arg_sp" "$arg_file" "$dep" | git hash-object --stdin)
+
+trace_it "$0: processing '$arg_file' with '$arg_sp' rule, and got hash '$sum' for it + '$dep'"
+
+getret=
+if test -z "$get"
+then
+       if test $(redis-cli SISMEMBER spatch-cache "$sum") = 1
+       then
+               getret=0
+       else
+               getret=1
+       fi
+else
+       $set "$sum"
+       getret=$?
+fi
+
+if test "$getret" = 0
+then
+       trace_it "$0: HIT for '$arg_file' with '$arg_sp'"
+       exit 0
+else
+       trace_it "$0: MISS: for '$arg_file' with '$arg_sp'"
+fi
+
+out="$(mktemp)"
+err="$(mktemp)"
+
+set +e
+"$spatch" "$@" >"$out" 2>>"$err"
+ret=$?
+cat "$out"
+cat "$err" >&2
+set -e
+
+nocache=
+if test $ret != 0
+then
+       nocache="exited non-zero: $ret"
+elif test -s "$out"
+then
+       nocache="had patch output"
+elif test -z "$cacheWhenStderr" && test -s "$err"
+then
+       nocache="had stderr (use --very-quiet or spatchCache.cacheWhenStderr=true?)"
+fi
+
+if test -n "$nocache"
+then
+       trace_it "$0: NOCACHE ($nocache): for '$arg_file' with '$arg_sp'"
+       exit "$ret"
+fi
+
+trace_it "$0: SET: for '$arg_file' with '$arg_sp'"
+
+setret=
+if test -z "$set"
+then
+       if test $(redis-cli SADD spatch-cache "$sum") = 1
+       then
+               setret=0
+       else
+               setret=1
+       fi
+else
+       "$set" "$sum"
+       setret=$?
+fi
+
+if test "$setret" != 0
+then
+       echo "FAILED to set '$sum' in cache!" >&2
+       exit 128
+fi
+
+exit "$ret"
index 0970d98ad72f82bab1fc2b6ea6238c48526fa112..5f06105df6db7b790a89d3af8633c511d68bc85b 100644 (file)
@@ -1,4 +1,4 @@
-@ strbuf_addf_with_format_only @
+@@
 expression E;
 constant fmt !~ "%";
 @@
index a0934d1fdaf07e9748fb2055e4a47b029bcf8948..522177afb66354110b6a63a044d183c512ed1662 100644 (file)
@@ -1,4 +1,4 @@
-@ swap_with_declaration @
+@@
 type T;
 identifier tmp;
 T a, b;
index 072ea0d92287a780f80a4d8e0ecb1cd46bdcd064..747d382ff5f1ba82a3c967483850cfd5a139cb2f 100644 (file)
@@ -20,7 +20,6 @@ expression E;
 
 @@
 expression E;
-expression F;
 @@
 - has_object_file_with_flags(
 + repo_has_object_file_with_flags(the_repository,
index 405cf76f2a3d94d5cb03b50d59ff729b745842ad..dfce4b4f44e05d1074acc103e8b8c16f702a1411 100755 (executable)
@@ -57,28 +57,11 @@ case "$#" in
        case "$cmd" in
        help)
                git bisect -h ;;
-       start)
-               git bisect--helper --bisect-start "$@" ;;
        bad|good|new|old|"$TERM_BAD"|"$TERM_GOOD")
-               git bisect--helper --bisect-state "$cmd" "$@" ;;
-       skip)
-               git bisect--helper --bisect-skip "$@" || exit;;
-       next)
-               # Not sure we want "next" at the UI level anymore.
-               git bisect--helper --bisect-next "$@" || exit ;;
-       visualize|view)
-               git bisect--helper --bisect-visualize "$@" || exit;;
-       reset)
-               git bisect--helper --bisect-reset "$@" ;;
-       replay)
-               git bisect--helper --bisect-replay "$@" || exit;;
+               git bisect--helper state "$cmd" "$@" ;;
        log)
-               git bisect--helper --bisect-log || exit ;;
-       run)
-               git bisect--helper --bisect-run "$@" || exit;;
-       terms)
-               git bisect--helper --bisect-terms "$@" || exit;;
+               git bisect--helper log || exit ;;
        *)
-               usage ;;
+               git bisect--helper "$cmd" "$@" ;;
        esac
 esac
index 5e5d21c010f7d4337dbf95cd50eeaf8c97791ba8..9a50f2e9124492589aa3a298a8bec74dbded8e32 100755 (executable)
@@ -343,7 +343,6 @@ cmd_update()
                ${recursive:+--recursive} \
                ${init:+--init} \
                ${nofetch:+--no-fetch} \
-               ${wt_prefix:+--prefix "$wt_prefix"} \
                ${rebase:+--rebase} \
                ${merge:+--merge} \
                ${checkout:+--checkout} \
@@ -557,7 +556,7 @@ cmd_sync()
 
 cmd_absorbgitdirs()
 {
-       git submodule--helper absorbgitdirs --prefix "$wt_prefix" "$@"
+       git ${wt_prefix:+-C "$wt_prefix"} submodule--helper absorbgitdirs "$@"
 }
 
 # This loop parses the command line arguments to find the
diff --git a/git.c b/git.c
index 6662548986f7b95422fa7858430b06dbb04c6f2b..10202a7f126aba88c41c51e140f8c0aa0b148c02 100644 (file)
--- a/git.c
+++ b/git.c
@@ -610,7 +610,7 @@ static struct cmd_struct commands[] = {
        { "stash", cmd_stash, RUN_SETUP | NEED_WORK_TREE },
        { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
        { "stripspace", cmd_stripspace },
-       { "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
+       { "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX },
        { "switch", cmd_switch, RUN_SETUP | NEED_WORK_TREE },
        { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
        { "tag", cmd_tag, RUN_SETUP | DELAY_PAGER_CONFIG },
diff --git a/http.c b/http.c
index 5d0502f51fd85d49ab2fa03995b8afcc585b12cc..8a5ba3f47769424e8315658b1f14903cc4957ecf 100644 (file)
--- a/http.c
+++ b/http.c
@@ -560,13 +560,15 @@ static void set_curl_keepalive(CURL *c)
 }
 #endif
 
-static void redact_sensitive_header(struct strbuf *header)
+/* Return 1 if redactions have been made, 0 otherwise. */
+static int redact_sensitive_header(struct strbuf *header, size_t offset)
 {
+       int ret = 0;
        const char *sensitive_header;
 
        if (trace_curl_redact &&
-           (skip_iprefix(header->buf, "Authorization:", &sensitive_header) ||
-            skip_iprefix(header->buf, "Proxy-Authorization:", &sensitive_header))) {
+           (skip_iprefix(header->buf + offset, "Authorization:", &sensitive_header) ||
+            skip_iprefix(header->buf + offset, "Proxy-Authorization:", &sensitive_header))) {
                /* The first token is the type, which is OK to log */
                while (isspace(*sensitive_header))
                        sensitive_header++;
@@ -575,8 +577,9 @@ static void redact_sensitive_header(struct strbuf *header)
                /* Everything else is opaque and possibly sensitive */
                strbuf_setlen(header,  sensitive_header - header->buf);
                strbuf_addstr(header, " <redacted>");
+               ret = 1;
        } else if (trace_curl_redact &&
-                  skip_iprefix(header->buf, "Cookie:", &sensitive_header)) {
+                  skip_iprefix(header->buf + offset, "Cookie:", &sensitive_header)) {
                struct strbuf redacted_header = STRBUF_INIT;
                const char *cookie;
 
@@ -612,6 +615,26 @@ static void redact_sensitive_header(struct strbuf *header)
 
                strbuf_setlen(header, sensitive_header - header->buf);
                strbuf_addbuf(header, &redacted_header);
+               ret = 1;
+       }
+       return ret;
+}
+
+/* Redact headers in info */
+static void redact_sensitive_info_header(struct strbuf *header)
+{
+       const char *sensitive_header;
+
+       /*
+        * curl's h2h3 prints headers in info, e.g.:
+        *   h2h3 [<header-name>: <header-val>]
+        */
+       if (trace_curl_redact &&
+           skip_iprefix(header->buf, "h2h3 [", &sensitive_header)) {
+               if (redact_sensitive_header(header, sensitive_header - header->buf)) {
+                       /* redaction ate our closing bracket */
+                       strbuf_addch(header, ']');
+               }
        }
 }
 
@@ -629,7 +652,7 @@ static void curl_dump_header(const char *text, unsigned char *ptr, size_t size,
 
        for (header = headers; *header; header++) {
                if (hide_sensitive_header)
-                       redact_sensitive_header(*header);
+                       redact_sensitive_header(*header, 0);
                strbuf_insertstr((*header), 0, text);
                strbuf_insertstr((*header), strlen(text), ": ");
                strbuf_rtrim((*header));
@@ -668,6 +691,18 @@ static void curl_dump_data(const char *text, unsigned char *ptr, size_t size)
        strbuf_release(&out);
 }
 
+static void curl_dump_info(char *data, size_t size)
+{
+       struct strbuf buf = STRBUF_INIT;
+
+       strbuf_add(&buf, data, size);
+
+       redact_sensitive_info_header(&buf);
+       trace_printf_key(&trace_curl, "== Info: %s", buf.buf);
+
+       strbuf_release(&buf);
+}
+
 static int curl_trace(CURL *handle, curl_infotype type, char *data, size_t size, void *userp)
 {
        const char *text;
@@ -675,7 +710,7 @@ static int curl_trace(CURL *handle, curl_infotype type, char *data, size_t size,
 
        switch (type) {
        case CURLINFO_TEXT:
-               trace_printf_key(&trace_curl, "== Info: %s", data);
+               curl_dump_info(data, size);
                break;
        case CURLINFO_HEADER_OUT:
                text = "=> Send header";
diff --git a/reset.c b/reset.c
index e3383a93343e3df16ee9eda6ee7886a49e980a1a..5ded23611f3f7316e2327872a54474ce6e4f6200 100644 (file)
--- a/reset.c
+++ b/reset.c
@@ -128,6 +128,7 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)
        unpack_tree_opts.update = 1;
        unpack_tree_opts.merge = 1;
        unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
+       unpack_tree_opts.skip_cache_tree_update = 1;
        init_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);
        if (reset_hard)
                unpack_tree_opts.reset = UNPACK_RESET_PROTECT_UNTRACKED;
index 03f9e480dd6a4a6bdafb57d6da48be74e6ba3bf6..6c52243cdf1f68bb3716891bfabbdf85fe26fcd4 100644 (file)
--- a/scalar.c
+++ b/scalar.c
@@ -596,6 +596,24 @@ static int get_scalar_repos(const char *key, const char *value, void *data)
        return 0;
 }
 
+static int remove_deleted_enlistment(struct strbuf *path)
+{
+       int res = 0;
+       strbuf_realpath_forgiving(path, path->buf, 1);
+
+       if (run_git("config", "--global",
+                   "--unset", "--fixed-value",
+                   "scalar.repo", path->buf, NULL) < 0)
+               res = -1;
+
+       if (run_git("config", "--global",
+                   "--unset", "--fixed-value",
+                   "maintenance.repo", path->buf, NULL) < 0)
+               res = -1;
+
+       return res;
+}
+
 static int cmd_reconfigure(int argc, const char **argv)
 {
        int all = 0;
@@ -635,8 +653,22 @@ static int cmd_reconfigure(int argc, const char **argv)
                strbuf_reset(&gitdir);
 
                if (chdir(dir) < 0) {
-                       warning_errno(_("could not switch to '%s'"), dir);
-                       res = -1;
+                       struct strbuf buf = STRBUF_INIT;
+
+                       if (errno != ENOENT) {
+                               warning_errno(_("could not switch to '%s'"), dir);
+                               res = -1;
+                               continue;
+                       }
+
+                       strbuf_addstr(&buf, dir);
+                       if (remove_deleted_enlistment(&buf))
+                               res = error(_("could not remove stale "
+                                             "scalar.repo '%s'"), dir);
+                       else
+                               warning(_("removing stale scalar.repo '%s'"),
+                                       dir);
+                       strbuf_release(&buf);
                } else if (discover_git_directory(&commondir, &gitdir) < 0) {
                        warning_errno(_("git repository gone in '%s'"), dir);
                        res = -1;
@@ -722,24 +754,6 @@ static int cmd_run(int argc, const char **argv)
        return 0;
 }
 
-static int remove_deleted_enlistment(struct strbuf *path)
-{
-       int res = 0;
-       strbuf_realpath_forgiving(path, path->buf, 1);
-
-       if (run_git("config", "--global",
-                   "--unset", "--fixed-value",
-                   "scalar.repo", path->buf, NULL) < 0)
-               res = -1;
-
-       if (run_git("config", "--global",
-                   "--unset", "--fixed-value",
-                   "maintenance.repo", path->buf, NULL) < 0)
-               res = -1;
-
-       return res;
-}
-
 static int cmd_unregister(int argc, const char **argv)
 {
        struct option options[] = {
index f0f1af4d4784b4ad7536a0a3de2857ab1dc8ca27..54ec90434db040a8268c95f6ac11c27bc269d0e3 100644 (file)
@@ -375,6 +375,7 @@ int sequencer_remove_state(struct replay_opts *opts)
        }
 
        free(opts->gpg_sign);
+       free(opts->reflog_action);
        free(opts->default_strategy);
        free(opts->strategy);
        for (i = 0; i < opts->xopts_nr; i++)
@@ -1050,6 +1051,8 @@ static int run_git_commit(const char *defmsg,
                             gpg_opt, gpg_opt);
        }
 
+       strvec_pushf(&cmd.env, GIT_REFLOG_ACTION "=%s", opts->reflog_message);
+
        if (opts->committer_date_is_author_date)
                strvec_pushf(&cmd.env, "GIT_COMMITTER_DATE=%s",
                             opts->ignore_date ?
@@ -1589,8 +1592,8 @@ static int try_to_commit(struct repository *r,
                goto out;
        }
 
-       if (update_head_with_reflog(current_head, oid,
-                                   getenv("GIT_REFLOG_ACTION"), msg, &err)) {
+       if (update_head_with_reflog(current_head, oid, opts->reflog_message,
+                                   msg, &err)) {
                res = error("%s", err.buf);
                goto out;
        }
@@ -3672,17 +3675,28 @@ static int do_label(struct repository *r, const char *name, int len)
        return ret;
 }
 
+static const char *sequencer_reflog_action(struct replay_opts *opts)
+{
+       if (!opts->reflog_action) {
+               opts->reflog_action = getenv(GIT_REFLOG_ACTION);
+               opts->reflog_action =
+                       xstrdup(opts->reflog_action ? opts->reflog_action
+                                                   : action_name(opts));
+       }
+
+       return opts->reflog_action;
+}
+
 __attribute__((format (printf, 3, 4)))
 static const char *reflog_message(struct replay_opts *opts,
        const char *sub_action, const char *fmt, ...)
 {
        va_list ap;
        static struct strbuf buf = STRBUF_INIT;
-       char *reflog_action = getenv(GIT_REFLOG_ACTION);
 
        va_start(ap, fmt);
        strbuf_reset(&buf);
-       strbuf_addstr(&buf, reflog_action ? reflog_action : action_name(opts));
+       strbuf_addstr(&buf, sequencer_reflog_action(opts));
        if (sub_action)
                strbuf_addf(&buf, " (%s)", sub_action);
        if (fmt) {
@@ -3694,6 +3708,28 @@ static const char *reflog_message(struct replay_opts *opts,
        return buf.buf;
 }
 
+static struct commit *lookup_label(struct repository *r, const char *label,
+                                  int len, struct strbuf *buf)
+{
+       struct commit *commit;
+       struct object_id oid;
+
+       strbuf_reset(buf);
+       strbuf_addf(buf, "refs/rewritten/%.*s", len, label);
+       if (!read_ref(buf->buf, &oid)) {
+               commit = lookup_commit_object(r, &oid);
+       } else {
+               /* fall back to non-rewritten ref or commit */
+               strbuf_splice(buf, 0, strlen("refs/rewritten/"), "", 0);
+               commit = lookup_commit_reference_by_name(buf->buf);
+       }
+
+       if (!commit)
+               error(_("could not resolve '%s'"), buf->buf);
+
+       return commit;
+}
+
 static int do_reset(struct repository *r,
                    const char *name, int len,
                    struct replay_opts *opts)
@@ -3725,6 +3761,7 @@ static int do_reset(struct repository *r,
                oidcpy(&oid, &opts->squash_onto);
        } else {
                int i;
+               struct commit *commit;
 
                /* Determine the length of the label */
                for (i = 0; i < len; i++)
@@ -3732,12 +3769,12 @@ static int do_reset(struct repository *r,
                                break;
                len = i;
 
-               strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
-               if (get_oid(ref_name.buf, &oid) &&
-                   get_oid(ref_name.buf + strlen("refs/rewritten/"), &oid)) {
-                       ret = error(_("could not read '%s'"), ref_name.buf);
+               commit = lookup_label(r, name, len, &ref_name);
+               if (!commit) {
+                       ret = -1;
                        goto cleanup;
                }
+               oid = commit->object.oid;
        }
 
        setup_unpack_trees_porcelain(&unpack_tree_opts, "reset");
@@ -3748,6 +3785,7 @@ static int do_reset(struct repository *r,
        unpack_tree_opts.merge = 1;
        unpack_tree_opts.update = 1;
        unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
+       unpack_tree_opts.skip_cache_tree_update = 1;
        init_checkout_metadata(&unpack_tree_opts.meta, name, &oid, NULL);
 
        if (repo_read_index_unmerged(r)) {
@@ -3784,26 +3822,6 @@ cleanup:
        return ret;
 }
 
-static struct commit *lookup_label(const char *label, int len,
-                                  struct strbuf *buf)
-{
-       struct commit *commit;
-
-       strbuf_reset(buf);
-       strbuf_addf(buf, "refs/rewritten/%.*s", len, label);
-       commit = lookup_commit_reference_by_name(buf->buf);
-       if (!commit) {
-               /* fall back to non-rewritten ref or commit */
-               strbuf_splice(buf, 0, strlen("refs/rewritten/"), "", 0);
-               commit = lookup_commit_reference_by_name(buf->buf);
-       }
-
-       if (!commit)
-               error(_("could not resolve '%s'"), buf->buf);
-
-       return commit;
-}
-
 static int do_merge(struct repository *r,
                    struct commit *commit,
                    const char *arg, int arg_len,
@@ -3851,7 +3869,7 @@ static int do_merge(struct repository *r,
                k = strcspn(p, " \t\n");
                if (!k)
                        continue;
-               merge_commit = lookup_label(p, k, &ref_name);
+               merge_commit = lookup_label(r, p, k, &ref_name);
                if (!merge_commit) {
                        ret = error(_("unable to parse '%.*s'"), k, p);
                        goto leave_merge;
@@ -4128,11 +4146,14 @@ static int write_update_refs_state(struct string_list *refs_to_oids)
        struct string_list_item *item;
        char *path;
 
-       if (!refs_to_oids->nr)
-               return 0;
-
        path = rebase_path_update_refs(the_repository->gitdir);
 
+       if (!refs_to_oids->nr) {
+               if (unlink(path) && errno != ENOENT)
+                       result = error_errno(_("could not unlink: %s"), path);
+               goto cleanup;
+       }
+
        if (safe_create_leading_directories(path)) {
                result = error(_("unable to create leading directories of %s"),
                               path);
@@ -4495,7 +4516,7 @@ static int checkout_onto(struct repository *r, struct replay_opts *opts,
                                RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
                .head_msg = reflog_message(opts, "start", "checkout %s",
                                           onto_name),
-               .default_reflog_action = "rebase"
+               .default_reflog_action = sequencer_reflog_action(opts)
        };
        if (reset_head(r, &ropts)) {
                apply_autostash(rebase_path_autostash());
@@ -4564,11 +4585,8 @@ static int pick_commits(struct repository *r,
                        struct replay_opts *opts)
 {
        int res = 0, reschedule = 0;
-       char *prev_reflog_action;
 
-       /* Note that 0 for 3rd parameter of setenv means set only if not set */
-       setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
-       prev_reflog_action = xstrdup(getenv(GIT_REFLOG_ACTION));
+       opts->reflog_message = sequencer_reflog_action(opts);
        if (opts->allow_ff)
                assert(!(opts->signoff || opts->no_commit ||
                         opts->record_origin || should_edit(opts) ||
@@ -4616,14 +4634,12 @@ static int pick_commits(struct repository *r,
                }
                if (item->command <= TODO_SQUASH) {
                        if (is_rebase_i(opts))
-                               setenv(GIT_REFLOG_ACTION, reflog_message(opts,
-                                       command_to_string(item->command), NULL),
-                                       1);
+                               opts->reflog_message = reflog_message(opts,
+                                     command_to_string(item->command), NULL);
+
                        res = do_pick_commit(r, item, opts,
                                             is_final_fixup(todo_list),
                                             &check_todo);
-                       if (is_rebase_i(opts))
-                               setenv(GIT_REFLOG_ACTION, prev_reflog_action, 1);
                        if (is_rebase_i(opts) && res < 0) {
                                /* Reschedule */
                                advise(_(rescheduled_advice),
@@ -5046,8 +5062,6 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
        if (read_populate_opts(opts))
                return -1;
        if (is_rebase_i(opts)) {
-               char *previous_reflog_action;
-
                if ((res = read_populate_todo(r, &todo_list, opts)))
                        goto release_todo_list;
 
@@ -5058,13 +5072,11 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
                        unlink(rebase_path_dropped());
                }
 
-               previous_reflog_action = xstrdup(getenv(GIT_REFLOG_ACTION));
-               setenv(GIT_REFLOG_ACTION, reflog_message(opts, "continue", NULL), 1);
+               opts->reflog_message = reflog_message(opts, "continue", NULL);
                if (commit_staged_changes(r, opts, &todo_list)) {
                        res = -1;
                        goto release_todo_list;
                }
-               setenv(GIT_REFLOG_ACTION, previous_reflog_action, 1);
        } else if (!file_exists(get_todo_path(opts)))
                return continue_single_pick(r, opts);
        else if ((res = read_populate_todo(r, &todo_list, opts)))
@@ -5112,7 +5124,7 @@ static int single_pick(struct repository *r,
                        TODO_PICK : TODO_REVERT;
        item.commit = cmit;
 
-       setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
+       opts->reflog_message = sequencer_reflog_action(opts);
        return do_pick_commit(r, &item, opts, 0, &check_todo);
 }
 
index 563fe5993340a0d0a18b148d4280c1d5a3f8d307..888c18aad7188e48985eb5c994b13853dc30c137 100644 (file)
@@ -63,6 +63,9 @@ struct replay_opts {
        char **xopts;
        size_t xopts_nr, xopts_alloc;
 
+       /* Reflog */
+       char *reflog_action;
+
        /* Used by fixup/squash */
        struct strbuf current_fixups;
        int current_fixup_count;
@@ -73,6 +76,9 @@ struct replay_opts {
 
        /* Only used by REPLAY_NONE */
        struct rev_info *revs;
+
+       /* Private use */
+       const char *reflog_message;
 };
 #define REPLAY_OPTS_INIT { .edit = -1, .action = -1, .current_fixups = STRBUF_INIT }
 
index 41e1c3fd3f787e04d6e4fa9eb7c56b617f1c5fa5..60e3ce84395ceaa70648a828ea1610a7fe9705a6 100644 (file)
@@ -17,6 +17,7 @@ void git_SHA1DCInit(SHA1_CTX *);
 void git_SHA1DCFinal(unsigned char [20], SHA1_CTX *);
 void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *data, unsigned long len);
 
+#define platform_SHA_IS_SHA1DC /* used by "test-tool sha1-is-sha1dc" */
 #define platform_SHA_CTX SHA1_CTX
 #define platform_SHA1_Init git_SHA1DCInit
 #define platform_SHA1_Update git_SHA1DCUpdate
index 33f43edbf9a6b2e156f15628869c1d6c1755cd6f..be1f30ff2064627869f5c1d5b7b0c7a4d054052b 100644 (file)
@@ -60,6 +60,7 @@ ifndef V
        QUIET_AR       = @echo '   ' AR $@;
        QUIET_LINK     = @echo '   ' LINK $@;
        QUIET_BUILT_IN = @echo '   ' BUILTIN $@;
+       QUIET_CP       = @echo '   ' CP $< $@;
        QUIET_LNCP     = @echo '   ' LN/CP $@;
        QUIET_XGETTEXT = @echo '   ' XGETTEXT $@;
        QUIET_MSGINIT  = @echo '   ' MSGINIT $@;
@@ -69,8 +70,11 @@ ifndef V
        QUIET_SP       = @echo '   ' SP $<;
        QUIET_HDR      = @echo '   ' HDR $(<:hcc=h);
        QUIET_RC       = @echo '   ' RC $@;
-       QUIET_SPATCH   = @echo '   ' SPATCH $<;
-       QUIET_SPATCH_T = @echo '   ' SPATCH TEST $(@:.build/%=%);
+
+## Used in "Makefile": SPATCH
+       QUIET_SPATCH                    = @echo '   ' SPATCH $< \>$@;
+       QUIET_SPATCH_TEST               = @echo '   ' SPATCH TEST $(@:.build/%=%);
+       QUIET_SPATCH_CAT                = @echo '   ' SPATCH CAT $(@:%.patch=%.d/)\*\*.patch \>$@;
 
 ## Used in "Documentation/Makefile"
        QUIET_ASCIIDOC  = @echo '   ' ASCIIDOC $@;
index b958162d28631f5298cb2739b15feade8440342a..8fa2ad457b21e1a8d931b54a1a374afad2d2ab7c 100644 (file)
@@ -2139,8 +2139,7 @@ int submodule_move_head(const char *path,
        if (!(flags & SUBMODULE_MOVE_HEAD_DRY_RUN)) {
                if (old_head) {
                        if (!submodule_uses_gitfile(path))
-                               absorb_git_dir_into_superproject(path,
-                                       ABSORB_GITDIR_RECURSE_SUBMODULES);
+                               absorb_git_dir_into_superproject(path);
                } else {
                        struct strbuf gitdir = STRBUF_INIT;
                        submodule_name_to_gitdir(&gitdir, the_repository,
@@ -2310,13 +2309,29 @@ static void relocate_single_git_dir_into_superproject(const char *path)
        strbuf_release(&new_gitdir);
 }
 
+static void absorb_git_dir_into_superproject_recurse(const char *path)
+{
+
+       struct child_process cp = CHILD_PROCESS_INIT;
+
+       cp.dir = path;
+       cp.git_cmd = 1;
+       cp.no_stdin = 1;
+       strvec_pushf(&cp.args, "--super-prefix=%s%s/",
+                    get_super_prefix_or_empty(), path);
+       strvec_pushl(&cp.args, "submodule--helper",
+                    "absorbgitdirs", NULL);
+       prepare_submodule_repo_env(&cp.env);
+       if (run_command(&cp))
+               die(_("could not recurse into submodule '%s'"), path);
+}
+
 /*
  * Migrate the git directory of the submodule given by path from
  * having its git directory within the working tree to the git dir nested
  * in its superprojects git dir under modules/.
  */
-void absorb_git_dir_into_superproject(const char *path,
-                                     unsigned flags)
+void absorb_git_dir_into_superproject(const char *path)
 {
        int err_code;
        const char *sub_git_dir;
@@ -2365,23 +2380,7 @@ void absorb_git_dir_into_superproject(const char *path,
        }
        strbuf_release(&gitdir);
 
-       if (flags & ABSORB_GITDIR_RECURSE_SUBMODULES) {
-               struct child_process cp = CHILD_PROCESS_INIT;
-
-               if (flags & ~ABSORB_GITDIR_RECURSE_SUBMODULES)
-                       BUG("we don't know how to pass the flags down?");
-
-               cp.dir = path;
-               cp.git_cmd = 1;
-               cp.no_stdin = 1;
-               strvec_pushf(&cp.args, "--super-prefix=%s%s/",
-                            get_super_prefix_or_empty(), path);
-               strvec_pushl(&cp.args, "submodule--helper",
-                            "absorbgitdirs", NULL);
-               prepare_submodule_repo_env(&cp.env);
-               if (run_command(&cp))
-                       die(_("could not recurse into submodule '%s'"), path);
-       }
+       absorb_git_dir_into_superproject_recurse(path);
 }
 
 int get_superproject_working_tree(struct strbuf *buf)
index 6a9fec6de1159f0389df2391c2d18e8d5fd7d297..b52a4ff1e73e3b137b7cd1a01e420c7f302497e6 100644 (file)
@@ -164,9 +164,7 @@ void submodule_unset_core_worktree(const struct submodule *sub);
  */
 void prepare_submodule_repo_env(struct strvec *env);
 
-#define ABSORB_GITDIR_RECURSE_SUBMODULES (1<<0)
-void absorb_git_dir_into_superproject(const char *path,
-                                     unsigned flags);
+void absorb_git_dir_into_superproject(const char *path);
 
 /*
  * Return the absolute path of the working tree of the superproject, which this
index 882782a519c97b65d2a6d487ac6849a540015fe4..2c2b2522402d087bee89ae8c08be99449c893057 100644 (file)
@@ -94,7 +94,7 @@ check-chainlint:
                done \
        } >'$(CHAINLINTTMP_SQ)'/expect && \
        $(CHAINLINT) --emit-all '$(CHAINLINTTMP_SQ)'/tests | \
-               grep -v '^[     ]*$$' >'$(CHAINLINTTMP_SQ)'/actual && \
+               sed -e 's/^[1-9][0-9]* //;/^[   ]*$$/d' >'$(CHAINLINTTMP_SQ)'/actual && \
        if test -f ../GIT-BUILD-OPTIONS; then \
                . ../GIT-BUILD-OPTIONS; \
        fi && \
index 976db4b8a01b804e7e697c308f769233083a576c..4e47e808d017c274b3fecc39ad6f7fe7026ae257 100755 (executable)
@@ -67,6 +67,7 @@ sub new {
        bless {
                parser => $parser,
                buff => $s,
+               lineno => 1,
                heretags => []
        } => $class;
 }
@@ -75,7 +76,9 @@ sub scan_heredoc_tag {
        my $self = shift @_;
        ${$self->{buff}} =~ /\G(-?)/gc;
        my $indented = $1;
-       my $tag = $self->scan_token();
+       my $token = $self->scan_token();
+       return "<<$indented" unless $token;
+       my $tag = $token->[0];
        $tag =~ s/['"\\]//g;
        push(@{$self->{heretags}}, $indented ? "\t$tag" : "$tag");
        return "<<$indented$tag";
@@ -95,7 +98,9 @@ sub scan_op {
 sub scan_sqstring {
        my $self = shift @_;
        ${$self->{buff}} =~ /\G([^']*'|.*\z)/sgc;
-       return "'" . $1;
+       my $s = $1;
+       $self->{lineno} += () = $s =~ /\n/sg;
+       return "'" . $s;
 }
 
 sub scan_dqstring {
@@ -113,7 +118,7 @@ sub scan_dqstring {
                if ($c eq '\\') {
                        $s .= '\\', last unless $$b =~ /\G(.)/sgc;
                        $c = $1;
-                       next if $c eq "\n"; # line splice
+                       $self->{lineno}++, next if $c eq "\n"; # line splice
                        # backslash escapes only $, `, ", \ in dq-string
                        $s .= '\\' unless $c =~ /^[\$`"\\]$/;
                        $s .= $c;
@@ -121,6 +126,7 @@ sub scan_dqstring {
                }
                die("internal error scanning dq-string '$c'\n");
        }
+       $self->{lineno} += () = $s =~ /\n/sg;
        return $s;
 }
 
@@ -135,6 +141,7 @@ sub scan_balanced {
                $depth--;
                last if $depth == 0;
        }
+       $self->{lineno} += () = $s =~ /\n/sg;
        return $s;
 }
 
@@ -149,7 +156,7 @@ sub scan_dollar {
        my $self = shift @_;
        my $b = $self->{buff};
        return $self->scan_balanced('(', ')') if $$b =~ /\G\((?=\()/gc; # $((...))
-       return '(' . join(' ', $self->scan_subst()) . ')' if $$b =~ /\G\(/gc; # $(...)
+       return '(' . join(' ', map {$_->[0]} $self->scan_subst()) . ')' if $$b =~ /\G\(/gc; # $(...)
        return $self->scan_balanced('{', '}') if $$b =~ /\G\{/gc; # ${...}
        return $1 if $$b =~ /\G(\w+)/gc; # $var
        return $1 if $$b =~ /\G([@*#?$!0-9-])/gc; # $*, $1, $$, etc.
@@ -161,8 +168,11 @@ sub swallow_heredocs {
        my $b = $self->{buff};
        my $tags = $self->{heretags};
        while (my $tag = shift @$tags) {
+               my $start = pos($$b);
                my $indent = $tag =~ s/^\t// ? '\\s*' : '';
                $$b =~ /(?:\G|\n)$indent\Q$tag\E(?:\n|\z)/gc;
+               my $body = substr($$b, $start, pos($$b) - $start);
+               $self->{lineno} += () = $body =~ /\n/sg;
        }
 }
 
@@ -170,34 +180,37 @@ sub scan_token {
        my $self = shift @_;
        my $b = $self->{buff};
        my $token = '';
+       my ($start, $startln);
 RESTART:
+       $startln = $self->{lineno};
        $$b =~ /\G[ \t]+/gc; # skip whitespace (but not newline)
-       return "\n" if $$b =~ /\G#[^\n]*(?:\n|\z)/gc; # comment
+       $start = pos($$b) || 0;
+       $self->{lineno}++, return ["\n", $start, pos($$b), $startln, $startln] if $$b =~ /\G#[^\n]*(?:\n|\z)/gc; # comment
        while (1) {
                # slurp up non-special characters
                $token .= $1 if $$b =~ /\G([^\\;&|<>(){}'"\$\s]+)/gc;
                # handle special characters
                last unless $$b =~ /\G(.)/sgc;
                my $c = $1;
-               last if $c =~ /^[ \t]$/; # whitespace ends token
+               pos($$b)--, last if $c =~ /^[ \t]$/; # whitespace ends token
                pos($$b)--, last if length($token) && $c =~ /^[;&|<>(){}\n]$/;
                $token .= $self->scan_sqstring(), next if $c eq "'";
                $token .= $self->scan_dqstring(), next if $c eq '"';
                $token .= $c . $self->scan_dollar(), next if $c eq '$';
-               $self->swallow_heredocs(), $token = $c, last if $c eq "\n";
+               $self->{lineno}++, $self->swallow_heredocs(), $token = $c, last if $c eq "\n";
                $token = $self->scan_op($c), last if $c =~ /^[;&|<>]$/;
                $token = $c, last if $c =~ /^[(){}]$/;
                if ($c eq '\\') {
                        $token .= '\\', last unless $$b =~ /\G(.)/sgc;
                        $c = $1;
-                       next if $c eq "\n" && length($token); # line splice
-                       goto RESTART if $c eq "\n"; # line splice
+                       $self->{lineno}++, next if $c eq "\n" && length($token); # line splice
+                       $self->{lineno}++, goto RESTART if $c eq "\n"; # line splice
                        $token .= '\\' . $c;
                        next;
                }
                die("internal error scanning character '$c'\n");
        }
-       return length($token) ? $token : undef;
+       return length($token) ? [$token, $start, pos($$b), $startln, $self->{lineno}] : undef;
 }
 
 # ShellParser parses POSIX shell scripts (with minor extensions for Bash). It
@@ -239,14 +252,14 @@ sub stop_at {
        my ($self, $token) = @_;
        return 1 unless defined($token);
        my $stop = ${$self->{stop}}[-1] if @{$self->{stop}};
-       return defined($stop) && $token =~ $stop;
+       return defined($stop) && $token->[0] =~ $stop;
 }
 
 sub expect {
        my ($self, $expect) = @_;
        my $token = $self->next_token();
-       return $token if defined($token) && $token eq $expect;
-       push(@{$self->{output}}, "?!ERR?! expected '$expect' but found '" . (defined($token) ? $token : "<end-of-input>") . "'\n");
+       return $token if defined($token) && $token->[0] eq $expect;
+       push(@{$self->{output}}, "?!ERR?! expected '$expect' but found '" . (defined($token) ? $token->[0] : "<end-of-input>") . "'\n");
        $self->untoken($token) if defined($token);
        return ();
 }
@@ -255,7 +268,7 @@ sub optional_newlines {
        my $self = shift @_;
        my @tokens;
        while (my $token = $self->peek()) {
-               last unless $token eq "\n";
+               last unless $token->[0] eq "\n";
                push(@tokens, $self->next_token());
        }
        return @tokens;
@@ -278,7 +291,7 @@ sub parse_case_pattern {
        my @tokens;
        while (defined(my $token = $self->next_token())) {
                push(@tokens, $token);
-               last if $token eq ')';
+               last if $token->[0] eq ')';
        }
        return @tokens;
 }
@@ -293,13 +306,13 @@ sub parse_case {
             $self->optional_newlines());
        while (1) {
                my $token = $self->peek();
-               last unless defined($token) && $token ne 'esac';
+               last unless defined($token) && $token->[0] ne 'esac';
                push(@tokens,
                     $self->parse_case_pattern(),
                     $self->optional_newlines(),
                     $self->parse(qr/^(?:;;|esac)$/)); # item body
                $token = $self->peek();
-               last unless defined($token) && $token ne 'esac';
+               last unless defined($token) && $token->[0] ne 'esac';
                push(@tokens,
                     $self->expect(';;'),
                     $self->optional_newlines());
@@ -315,7 +328,7 @@ sub parse_for {
             $self->next_token(), # variable
             $self->optional_newlines());
        my $token = $self->peek();
-       if (defined($token) && $token eq 'in') {
+       if (defined($token) && $token->[0] eq 'in') {
                push(@tokens,
                     $self->expect('in'),
                     $self->optional_newlines());
@@ -339,11 +352,11 @@ sub parse_if {
                     $self->optional_newlines(),
                     $self->parse(qr/^(?:elif|else|fi)$/)); # if/elif body
                my $token = $self->peek();
-               last unless defined($token) && $token eq 'elif';
+               last unless defined($token) && $token->[0] eq 'elif';
                push(@tokens, $self->expect('elif'));
        }
        my $token = $self->peek();
-       if (defined($token) && $token eq 'else') {
+       if (defined($token) && $token->[0] eq 'else') {
                push(@tokens,
                     $self->expect('else'),
                     $self->optional_newlines(),
@@ -380,7 +393,7 @@ sub parse_bash_array_assignment {
        my @tokens = $self->expect('(');
        while (defined(my $token = $self->next_token())) {
                push(@tokens, $token);
-               last if $token eq ')';
+               last if $token->[0] eq ')';
        }
        return @tokens;
 }
@@ -398,29 +411,31 @@ sub parse_cmd {
        my $self = shift @_;
        my $cmd = $self->next_token();
        return () unless defined($cmd);
-       return $cmd if $cmd eq "\n";
+       return $cmd if $cmd->[0] eq "\n";
 
        my $token;
        my @tokens = $cmd;
-       if ($cmd eq '!') {
+       if ($cmd->[0] eq '!') {
                push(@tokens, $self->parse_cmd());
                return @tokens;
-       } elsif (my $f = $compound{$cmd}) {
+       } elsif (my $f = $compound{$cmd->[0]}) {
                push(@tokens, $self->$f());
-       } elsif (defined($token = $self->peek()) && $token eq '(') {
-               if ($cmd !~ /\w=$/) {
+       } elsif (defined($token = $self->peek()) && $token->[0] eq '(') {
+               if ($cmd->[0] !~ /\w=$/) {
                        push(@tokens, $self->parse_func());
                        return @tokens;
                }
-               $tokens[-1] .= join(' ', $self->parse_bash_array_assignment());
+               my @array = $self->parse_bash_array_assignment();
+               $tokens[-1]->[0] .= join(' ', map {$_->[0]} @array);
+               $tokens[-1]->[2] = $array[$#array][2] if @array;
        }
 
        while (defined(my $token = $self->next_token())) {
                $self->untoken($token), last if $self->stop_at($token);
                push(@tokens, $token);
-               last if $token =~ /^(?:[;&\n|]|&&|\|\|)$/;
+               last if $token->[0] =~ /^(?:[;&\n|]|&&|\|\|)$/;
        }
-       push(@tokens, $self->next_token()) if $tokens[-1] ne "\n" && defined($token = $self->peek()) && $token eq "\n";
+       push(@tokens, $self->next_token()) if $tokens[-1]->[0] ne "\n" && defined($token = $self->peek()) && $token->[0] eq "\n";
        return @tokens;
 }
 
@@ -453,11 +468,18 @@ package TestParser;
 
 use base 'ShellParser';
 
+sub new {
+       my $class = shift @_;
+       my $self = $class->SUPER::new(@_);
+       $self->{problems} = [];
+       return $self;
+}
+
 sub find_non_nl {
        my $tokens = shift @_;
        my $n = shift @_;
        $n = $#$tokens if !defined($n);
-       $n-- while $n >= 0 && $$tokens[$n] eq "\n";
+       $n-- while $n >= 0 && $$tokens[$n]->[0] eq "\n";
        return $n;
 }
 
@@ -467,7 +489,7 @@ sub ends_with {
        for my $needle (reverse(@$needles)) {
                return undef if $n < 0;
                $n = find_non_nl($tokens, $n), next if $needle eq "\n";
-               return undef if $$tokens[$n] !~ $needle;
+               return undef if $$tokens[$n]->[0] !~ $needle;
                $n--;
        }
        return 1;
@@ -486,13 +508,13 @@ sub parse_loop_body {
        my $self = shift @_;
        my @tokens = $self->SUPER::parse_loop_body(@_);
        # did loop signal failure via "|| return" or "|| exit"?
-       return @tokens if !@tokens || grep(/^(?:return|exit|\$\?)$/, @tokens);
+       return @tokens if !@tokens || grep {$_->[0] =~ /^(?:return|exit|\$\?)$/} @tokens;
        # did loop upstream of a pipe signal failure via "|| echo 'impossible
        # text'" as the final command in the loop body?
        return @tokens if ends_with(\@tokens, [qr/^\|\|$/, "\n", qr/^echo$/, qr/^.+$/]);
        # flag missing "return/exit" handling explicit failure in loop body
        my $n = find_non_nl(\@tokens);
-       splice(@tokens, $n + 1, 0, '?!LOOP?!');
+       push(@{$self->{problems}}, ['LOOP', $tokens[$n]]);
        return @tokens;
 }
 
@@ -505,8 +527,13 @@ my @safe_endings = (
 
 sub accumulate {
        my ($self, $tokens, $cmd) = @_;
+       my $problems = $self->{problems};
+
+       # no previous command to check for missing "&&"
        goto DONE unless @$tokens;
-       goto DONE if @$cmd == 1 && $$cmd[0] eq "\n";
+
+       # new command is empty line; can't yet check if previous is missing "&&"
+       goto DONE if @$cmd == 1 && $$cmd[0]->[0] eq "\n";
 
        # did previous command end with "&&", "|", "|| return" or similar?
        goto DONE if match_ending($tokens, \@safe_endings);
@@ -514,20 +541,20 @@ sub accumulate {
        # if this command handles "$?" specially, then okay for previous
        # command to be missing "&&"
        for my $token (@$cmd) {
-               goto DONE if $token =~ /\$\?/;
+               goto DONE if $token->[0] =~ /\$\?/;
        }
 
        # if this command is "false", "return 1", or "exit 1" (which signal
        # failure explicitly), then okay for all preceding commands to be
        # missing "&&"
-       if ($$cmd[0] =~ /^(?:false|return|exit)$/) {
-               @$tokens = grep(!/^\?!AMP\?!$/, @$tokens);
+       if ($$cmd[0]->[0] =~ /^(?:false|return|exit)$/) {
+               @$problems = grep {$_->[0] ne 'AMP'} @$problems;
                goto DONE;
        }
 
        # flag missing "&&" at end of previous command
        my $n = find_non_nl($tokens);
-       splice(@$tokens, $n + 1, 0, '?!AMP?!') unless $n < 0;
+       push(@$problems, ['AMP', $tokens->[$n]]) unless $n < 0;
 
 DONE:
        $self->SUPER::accumulate($tokens, $cmd);
@@ -553,7 +580,7 @@ sub new {
 # composition of multiple strings and non-string character runs; for instance,
 # `"test body"` unwraps to `test body`; `word"a b"42'c d'` to `worda b42c d`
 sub unwrap {
-       my $token = @_ ? shift @_ : $_;
+       my $token = (@_ ? shift @_ : $_)->[0];
        # simple case: 'sqstring' or "dqstring"
        return $token if $token =~ s/^'([^']*)'$/$1/;
        return $token if $token =~ s/^"([^"]*)"$/$1/;
@@ -584,13 +611,25 @@ sub check_test {
        $self->{ntests}++;
        my $parser = TestParser->new(\$body);
        my @tokens = $parser->parse();
-       return unless $emit_all || grep(/\?![^?]+\?!/, @tokens);
+       my $problems = $parser->{problems};
+       return unless $emit_all || @$problems;
        my $c = main::fd_colors(1);
-       my $checked = join(' ', @tokens);
-       $checked =~ s/^\n//;
-       $checked =~ s/^ //mg;
-       $checked =~ s/ $//mg;
+       my $lineno = $_[1]->[3];
+       my $start = 0;
+       my $checked = '';
+       for (sort {$a->[1]->[2] <=> $b->[1]->[2]} @$problems) {
+               my ($label, $token) = @$_;
+               my $pos = $token->[2];
+               $checked .= substr($body, $start, $pos - $start) . " ?!$label?! ";
+               $start = $pos;
+       }
+       $checked .= substr($body, $start);
+       $checked =~ s/^/$lineno++ . ' '/mge;
+       $checked =~ s/^\d+ \n//;
+       $checked =~ s/(\s) \?!/$1?!/mg;
+       $checked =~ s/\?! (\s)/?!$1/mg;
        $checked =~ s/(\?![^?]+\?!)/$c->{rev}$c->{red}$1$c->{reset}/mg;
+       $checked =~ s/^\d+/$c->{dim}$&$c->{reset}/mg;
        $checked .= "\n" unless $checked =~ /\n$/;
        push(@{$self->{output}}, "$c->{blue}# chainlint: $title$c->{reset}\n$checked");
 }
@@ -598,9 +637,9 @@ sub check_test {
 sub parse_cmd {
        my $self = shift @_;
        my @tokens = $self->SUPER::parse_cmd();
-       return @tokens unless @tokens && $tokens[0] =~ /^test_expect_(?:success|failure)$/;
+       return @tokens unless @tokens && $tokens[0]->[0] =~ /^test_expect_(?:success|failure)$/;
        my $n = $#tokens;
-       $n-- while $n >= 0 && $tokens[$n] =~ /^(?:[;&\n|]|&&|\|\|)$/;
+       $n-- while $n >= 0 && $tokens[$n]->[0] =~ /^(?:[;&\n|]|&&|\|\|)$/;
        $self->check_test($tokens[1], $tokens[2]) if $n == 2; # title body
        $self->check_test($tokens[2], $tokens[3]) if $n > 2;  # prereq title body
        return @tokens;
@@ -622,25 +661,39 @@ if (eval {require Time::HiRes; Time::HiRes->import(); 1;}) {
 # thread and ignore %ENV changes in subthreads.
 $ENV{TERM} = $ENV{USER_TERM} if $ENV{USER_TERM};
 
-my @NOCOLORS = (bold => '', rev => '', reset => '', blue => '', green => '', red => '');
+my @NOCOLORS = (bold => '', rev => '', dim => '', reset => '', blue => '', green => '', red => '');
 my %COLORS = ();
 sub get_colors {
        return \%COLORS if %COLORS;
-       if (exists($ENV{NO_COLOR}) ||
-           system("tput sgr0 >/dev/null 2>&1") != 0 ||
-           system("tput bold >/dev/null 2>&1") != 0 ||
-           system("tput rev  >/dev/null 2>&1") != 0 ||
-           system("tput setaf 1 >/dev/null 2>&1") != 0) {
+       if (exists($ENV{NO_COLOR})) {
                %COLORS = @NOCOLORS;
                return \%COLORS;
        }
-       %COLORS = (bold  => `tput bold`,
-                  rev   => `tput rev`,
-                  reset => `tput sgr0`,
-                  blue  => `tput setaf 4`,
-                  green => `tput setaf 2`,
-                  red   => `tput setaf 1`);
-       chomp(%COLORS);
+       if ($ENV{TERM} =~ /xterm|xterm-\d+color|xterm-new|xterm-direct|nsterm|nsterm-\d+color|nsterm-direct/) {
+               %COLORS = (bold  => "\e[1m",
+                          rev   => "\e[7m",
+                          dim   => "\e[2m",
+                          reset => "\e[0m",
+                          blue  => "\e[34m",
+                          green => "\e[32m",
+                          red   => "\e[31m");
+               return \%COLORS;
+       }
+       if (system("tput sgr0 >/dev/null 2>&1") == 0 &&
+           system("tput bold >/dev/null 2>&1") == 0 &&
+           system("tput rev  >/dev/null 2>&1") == 0 &&
+           system("tput dim  >/dev/null 2>&1") == 0 &&
+           system("tput setaf 1 >/dev/null 2>&1") == 0) {
+               %COLORS = (bold  => `tput bold`,
+                          rev   => `tput rev`,
+                          dim   => `tput dim`,
+                          reset => `tput sgr0`,
+                          blue  => `tput setaf 4`,
+                          green => `tput setaf 2`,
+                          red   => `tput setaf 1`);
+               return \%COLORS;
+       }
+       %COLORS = @NOCOLORS;
        return \%COLORS;
 }
 
index d10b2eeaf2754f5d6609f438c523ee26c92e0b05..df2beea8887f3504e5fbab25aef96eb3763ded84 100644 (file)
@@ -1,6 +1,8 @@
 (
        {
+               # show a
                echo a &&
+               # show b
                echo b
        }
 )
index 1e4b054bda0faa803ecf12c8f4622ed0b1b29023..641c157b98c0af678b15fb2cc25645eac8650602 100644 (file)
@@ -1,7 +1,10 @@
 (
        case "$x" in
+       # found foo
        x) foo ;;
+       # found other
        *)
+               # treat it as bar
                bar
                ;;
        esac
index 0f87db9ae6891f8536c6eec73b71e5f049ca9667..2192a2870a1ae3d098c78126eb06608f74a32437 100644 (file)
@@ -15,7 +15,8 @@
 ) | wuzzle &&
 (
        bop
-) | fazz       fozz &&
+) | fazz \
+       fozz &&
 (
        bup
 ) |
index f76fde1ffba91d7becf17c0990c39ac25a7083f0..a68f1f9d7c26745169ab4c45b3e6f5f7c5b3c6b3 100644 (file)
@@ -1,4 +1,8 @@
 (
+       # comment 1
        nothing &&
+       # comment 2
        something
+       # comment 3
+       # comment 4
 )
index 75477bb1add492288504244af4bd57ec45dd601c..cd584a435730045608f1ce9162274fb6fa53a2c8 100644 (file)
@@ -1,2 +1,12 @@
-run_sub_test_lib_test_err run-inv-range-start "--run invalid range start" --run="a-5" <<-EOF &&
-check_sub_test_lib_test_err run-inv-range-start <<-EOF_OUT 3 <<-EOF_ERR
+run_sub_test_lib_test_err run-inv-range-start \
+       "--run invalid range start" \
+       --run="a-5" <<-\EOF &&
+test_expect_success "passing test #1" "true"
+test_done
+EOF
+check_sub_test_lib_test_err run-inv-range-start \
+       <<-\EOF_OUT 3<<-EOF_ERR
+> FATAL: Unexpected exit with code 1
+EOF_OUT
+> error: --run: invalid non-numeric in range start: ${SQ}a-5${SQ}
+EOF_ERR
index f42f2d41ba8c68398631c256e2abb9705d32c6ba..e8733c97c645afce31a1253e90a69cfb017e4d7d 100644 (file)
@@ -1,3 +1,4 @@
 git ls-tree $tree path > current &&
-cat > expected <<EOF &&
+cat > expected <<\EOF &&
+EOF
 test_output
index a5810c9bddd8352c74f3213be1fd54d1986a72f4..d65c82129a68b7c3e2088ba9a95971e03a6952ee 100644 (file)
@@ -2,7 +2,9 @@
        for i in a b c
        do
                echo $i ?!AMP?!
-               cat <<-EOF ?!LOOP?!
+               cat <<-\EOF ?!LOOP?!
+               bar
+               EOF
        done ?!AMP?!
        for i in a b c; do
                echo $i &&
index 2af9ced71cc331414ce22e5d4ef3fc1320b3c15d..7d9c2b560701f66eb854b1b31c8b3820347f7eb1 100644 (file)
@@ -1,2 +1,4 @@
 (
-       cat <<-INPUT)
+       cat <<-\INPUT)
+       fizz
+       INPUT
index fb6cf7285d02649a8df406db3e11ca4d59b9eeae..f92a7ce9992420e2e5483ddcd83d3118d5dfd516 100644 (file)
@@ -1,5 +1,11 @@
-cat > expect <<-EOF &&
+cat >expect <<- EOF &&
+header: 43475048 1 $(test_oid oid_version) $NUM_CHUNKS 0
+num_commits: $1
+chunks: oid_fanout oid_lookup commit_metadata generation_data bloom_indexes bloom_data
+EOF
 
-cat > expect <<-EOF ?!AMP?!
+cat >expect << -EOF ?!AMP?!
+this is not indented
+-EOF
 
 cleanup
index f8b3aa73c4f180be48afff988c0f7cece67e45d4..b7364c82c89feba3baf9a149937f1e43bf91ff23 100644 (file)
@@ -1,5 +1,8 @@
 (
-       x=$(bobble <<-END &&
+       x=$(bobble <<-\END &&
+               fossil
+               vegetable
+               END
                wiffle) ?!AMP?!
        echo $x
 )
index be64b26869ada1f4d7d9715cb754c2aa826c6978..6c13bdcbfb5d12500ffc01915b853d0169abf13e 100644 (file)
@@ -1,5 +1,7 @@
 (
-       cat <<-TXT && echo "multi-line
+       cat <<-\TXT && echo "multi-line
        string" ?!AMP?!
+       fizzle
+       TXT
        bap
 )
index 110059ba58420e5924de64edb0ec44346b43cb34..1df3f782821b6a7221767fbdd76ad2c26b5d67c9 100644 (file)
@@ -1,7 +1,25 @@
-boodle wobba        gorgo snoot        wafta snurb <<EOF &&
+boodle wobba \
+       gorgo snoot \
+       wafta snurb <<EOF &&
+quoth the raven,
+nevermore...
+EOF
 
 cat <<-Arbitrary_Tag_42 >foo &&
+snoz
+boz
+woz
+Arbitrary_Tag_42
 
-cat <<zump >boo &&
+cat <<"zump" >boo &&
+snoz
+boz
+woz
+zump
 
-horticulture <<EOF
+horticulture <<\EOF
+gomez
+morticia
+wednesday
+pugsly
+EOF
index 44d86c35976ce1957aa0b4fb90f6b7e31f230d3c..cbaaf857d47a0bf23813933e768b6a9128670737 100644 (file)
@@ -8,7 +8,9 @@
                echo foo
        else
                echo foo &&
-               cat <<-EOF
+               cat <<-\EOF
+               bar
+               EOF
        fi ?!AMP?!
        echo poodle
 ) &&
index ffac8f901857eef401cdcfa6d60734c92a96b416..134d3a14f5c0c1efc51e21fe1fa29b52b5f9a0ab 100644 (file)
@@ -1,4 +1,10 @@
-line 1 line 2 line 3 line 4 &&
+line 1 \
+line 2 \
+line 3 \
+line 4 &&
 (
-       line 5  line 6  line 7  line 8
+       line 5 \
+       line 6 \
+       line 7 \
+       line 8
 )
index dd0dace077f0e093ccda9dc33b3296830e13c8d2..6bad21853006d38834bf1b4ebc2da46e3c6faebc 100644 (file)
@@ -1,6 +1,6 @@
 (
-       foobar &&
-       barfoo ?!AMP?!
+       foobar && # comment 1
+       barfoo ?!AMP?! # wrong position for &&
        flibble "not a # comment"
 ) &&
 
index 0ad23bb35e4fb173eb808f777fef2f2a453c8c1a..24da9e86d596b5d068b149242e213ba0224051d3 100644 (file)
@@ -2,7 +2,7 @@
 do
        printf "Generating blob $i/$blobcount\r" >& 2 &&
        printf "blob\nmark :$i\ndata $blobsize\n" &&
-
+       #test-tool genrandom $i $blobsize &&
        printf "%-${blobsize}s" $i &&
        echo "M 100644 :$i $i" >> commit &&
        i=$(($i+1)) ||
index e3bef63f7548cb0c187ae938280029dd470922bb..29b3832a986af30d7026eef513cf01dc769316a2 100644 (file)
@@ -1,7 +1,30 @@
 cat <<ARBITRARY >foop &&
+naddle
+fub <<EOF
+       nozzle
+       noodle
+EOF
+formp
+ARBITRARY
 
 (
-       cat <<-INPUT_END &&
-       cat <<-EOT ?!AMP?!
+       cat <<-\INPUT_END &&
+       fish are mice
+       but geese go slow
+       data <<EOF
+               perl is lerp
+               and nothing else
+       EOF
+       toink
+       INPUT_END
+
+       cat <<-\EOT ?!AMP?!
+       text goes here
+       data <<EOF
+               data goes here
+       EOF
+       more test here
+       EOT
+
        foobar
 )
index be4b27a305bec54678ae4669a1666405ec06f966..9138cf386d359267143945d13c1882b0feb5c6f4 100644 (file)
@@ -2,6 +2,8 @@
        foo &&
        (
                bar &&
+               # bottles wobble while fiddles gobble
+               # minor numbers of cows (or do they?)
                baz &&
                snaff
        ) ?!AMP?!
index 029d129299a0a5c68d45661071ba3ae144cd5377..52789278d13b7605a63c0c92c09c518990ab316f 100644 (file)
@@ -1,10 +1,30 @@
 (
-       echo wobba             gorgo snoot             wafta snurb <<-EOF &&
+       echo wobba \
+               gorgo snoot \
+               wafta snurb <<-EOF &&
+       quoth the raven,
+       nevermore...
+       EOF
+
        cat <<EOF >bip ?!AMP?!
-       echo <<-EOF >bop
+       fish fly high
+EOF
+
+       echo <<-\EOF >bop
+       gomez
+       morticia
+       wednesday
+       pugsly
+       EOF
 ) &&
 (
-       cat <<-ARBITRARY >bup &&
-       cat <<-ARBITRARY3 >bup3 &&
+       cat <<-\ARBITRARY >bup &&
+       glink
+       FIZZ
+       ARBITRARY
+       cat <<-"ARBITRARY3" >bup3 &&
+       glink
+       FIZZ
+       ARBITRARY3
        meep
 )
index 69167da2f27a3098297191c6cbfc737eacb91e8a..71b3b3bc20ed1d6718f8d0ee6efe35b147557b8c 100644 (file)
@@ -4,12 +4,16 @@ sub2
 sub3
 sub4" &&
        chks_sub=$(cat <<TXT | sed "s,^,sub dir/,"
+$chks
+TXT
 ) &&
        chkms="main-sub1
 main-sub2
 main-sub3
 main-sub4" &&
        chkms_sub=$(cat <<TXT | sed "s,^,sub dir/,"
+$chkms
+TXT
 ) &&
        subfiles=$(git ls-files) &&
        check_equal "$subfiles" "$chkms
index f272aa21fee1950a97a9eeddee9436a11c4e1f3f..1f5eaea0fd59757ea78b7dc0b24ec0971c2faa36 100644 (file)
@@ -2,7 +2,9 @@
        while true
        do
                echo foo ?!AMP?!
-               cat <<-EOF ?!LOOP?!
+               cat <<-\EOF ?!LOOP?!
+               bar
+               EOF
        done ?!AMP?!
        while true; do
                echo foo &&
diff --git a/t/helper/test-cache-tree.c b/t/helper/test-cache-tree.c
new file mode 100644 (file)
index 0000000..93051b2
--- /dev/null
@@ -0,0 +1,64 @@
+#include "test-tool.h"
+#include "cache.h"
+#include "tree.h"
+#include "cache-tree.h"
+#include "parse-options.h"
+
+static char const * const test_cache_tree_usage[] = {
+       N_("test-tool cache-tree <options> (control|prime|update)"),
+       NULL
+};
+
+int cmd__cache_tree(int argc, const char **argv)
+{
+       struct object_id oid;
+       struct tree *tree;
+       int empty = 0;
+       int invalidate_qty = 0;
+       int i;
+
+       struct option options[] = {
+               OPT_BOOL(0, "empty", &empty,
+                        N_("clear the cache tree before each iteration")),
+               OPT_INTEGER_F(0, "invalidate", &invalidate_qty,
+                             N_("number of entries in the cache tree to invalidate (default 0)"),
+                             PARSE_OPT_NONEG),
+               OPT_END()
+       };
+
+       setup_git_directory();
+
+       argc = parse_options(argc, argv, NULL, options, test_cache_tree_usage, 0);
+
+       if (read_cache() < 0)
+               die(_("unable to read index file"));
+
+       oidcpy(&oid, &the_index.cache_tree->oid);
+       tree = parse_tree_indirect(&oid);
+       if (!tree)
+               die(_("not a tree object: %s"), oid_to_hex(&oid));
+
+       if (empty) {
+               /* clear the cache tree & allocate a new one */
+               cache_tree_free(&the_index.cache_tree);
+               the_index.cache_tree = cache_tree();
+       } else if (invalidate_qty) {
+               /* invalidate the specified number of unique paths */
+               float f_interval = (float)the_index.cache_nr / invalidate_qty;
+               int interval = f_interval < 1.0 ? 1 : (int)f_interval;
+               for (i = 0; i < invalidate_qty && i * interval < the_index.cache_nr; i++)
+                       cache_tree_invalidate_path(&the_index, the_index.cache[i * interval]->name);
+       }
+
+       if (argc != 1)
+               usage_with_options(test_cache_tree_usage, options);
+       else if (!strcmp(argv[0], "prime"))
+               prime_cache_tree(the_repository, &the_index, tree);
+       else if (!strcmp(argv[0], "update"))
+               cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR);
+       /* use "control" subcommand to specify no-op */
+       else if (!!strcmp(argv[0], "control"))
+               die(_("Unhandled subcommand '%s'"), argv[0]);
+
+       return 0;
+}
index d860c387c3846d69b7bd63a144ede2f93da60886..71fe5c61455a89eddd6363a87e3c4eb7968f09b6 100644 (file)
@@ -5,3 +5,11 @@ int cmd__sha1(int ac, const char **av)
 {
        return cmd_hash_impl(ac, av, GIT_HASH_SHA1);
 }
+
+int cmd__sha1_is_sha1dc(int argc UNUSED, const char **argv UNUSED)
+{
+#ifdef platform_SHA_IS_SHA1DC
+       return 0;
+#endif
+       return 1;
+}
index b7d117cd557a84eabcdc25df9ae874e0192e3dd7..e060cc6226895d750ced2cab19cedb46f97843b9 100644 (file)
@@ -111,10 +111,94 @@ static int cmd__submodule_resolve_relative_url(int argc, const char **argv)
        return 0;
 }
 
+static int cmd__submodule_config_list(int argc, const char **argv)
+{
+       struct option options[] = {
+               OPT_END()
+       };
+       const char *const usage[] = {
+               "test-tool submodule config-list <key>",
+               NULL
+       };
+       argc = parse_options(argc, argv, "test-tools", options, usage,
+                            PARSE_OPT_KEEP_ARGV0);
+
+       setup_git_directory();
+
+       if (argc == 2)
+               return print_config_from_gitmodules(the_repository, argv[1]);
+       usage_with_options(usage, options);
+}
+
+static int cmd__submodule_config_set(int argc, const char **argv)
+{
+       struct option options[] = {
+               OPT_END()
+       };
+       const char *const usage[] = {
+               "test-tool submodule config-set <key> <value>",
+               NULL
+       };
+       argc = parse_options(argc, argv, "test-tools", options, usage,
+                            PARSE_OPT_KEEP_ARGV0);
+
+       setup_git_directory();
+
+       /* Equivalent to ACTION_SET in builtin/config.c */
+       if (argc == 3) {
+               if (!is_writing_gitmodules_ok())
+                       die("please make sure that the .gitmodules file is in the working tree");
+
+               return config_set_in_gitmodules_file_gently(argv[1], argv[2]);
+       }
+       usage_with_options(usage, options);
+}
+
+static int cmd__submodule_config_unset(int argc, const char **argv)
+{
+       struct option options[] = {
+               OPT_END()
+       };
+       const char *const usage[] = {
+               "test-tool submodule config-unset <key>",
+               NULL
+       };
+
+       setup_git_directory();
+
+       if (argc == 2) {
+               if (!is_writing_gitmodules_ok())
+                       die("please make sure that the .gitmodules file is in the working tree");
+               return config_set_in_gitmodules_file_gently(argv[1], NULL);
+       }
+       usage_with_options(usage, options);
+}
+
+static int cmd__submodule_config_writeable(int argc, const char **argv)
+{
+       struct option options[] = {
+               OPT_END()
+       };
+       const char *const usage[] = {
+               "test-tool submodule config-writeable",
+               NULL
+       };
+       setup_git_directory();
+
+       if (argc == 1)
+               return is_writing_gitmodules_ok() ? 0 : -1;
+
+       usage_with_options(usage, options);
+}
+
 static struct test_cmd cmds[] = {
        { "check-name", cmd__submodule_check_name },
        { "is-active", cmd__submodule_is_active },
        { "resolve-relative-url", cmd__submodule_resolve_relative_url},
+       { "config-list", cmd__submodule_config_list },
+       { "config-set", cmd__submodule_config_set },
+       { "config-unset", cmd__submodule_config_unset },
+       { "config-writeable", cmd__submodule_config_writeable },
 };
 
 int cmd__submodule(int argc, const char **argv)
index 01cda9358df01f50de95a93c8aee795696da377d..7eb1a26a305b89c3d45954a2d41c2f3ad5f82196 100644 (file)
@@ -14,6 +14,7 @@ static struct test_cmd cmds[] = {
        { "bitmap", cmd__bitmap },
        { "bloom", cmd__bloom },
        { "bundle-uri", cmd__bundle_uri },
+       { "cache-tree", cmd__cache_tree },
        { "chmtime", cmd__chmtime },
        { "config", cmd__config },
        { "crontab", cmd__crontab },
@@ -73,6 +74,7 @@ static struct test_cmd cmds[] = {
        { "scrap-cache-tree", cmd__scrap_cache_tree },
        { "serve-v2", cmd__serve_v2 },
        { "sha1", cmd__sha1 },
+       { "sha1-is-sha1dc", cmd__sha1_is_sha1dc },
        { "sha256", cmd__sha256 },
        { "sigchain", cmd__sigchain },
        { "simple-ipc", cmd__simple_ipc },
index ca2948066fd64ec4743e498fffd06829c2cfce2b..da7cd6351a6ea32a222f91229874016226e066de 100644 (file)
@@ -8,6 +8,7 @@ int cmd__advise_if_enabled(int argc, const char **argv);
 int cmd__bitmap(int argc, const char **argv);
 int cmd__bloom(int argc, const char **argv);
 int cmd__bundle_uri(int argc, const char **argv);
+int cmd__cache_tree(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);
@@ -66,6 +67,7 @@ int cmd__run_command(int argc, const char **argv);
 int cmd__scrap_cache_tree(int argc, const char **argv);
 int cmd__serve_v2(int argc, const char **argv);
 int cmd__sha1(int argc, const char **argv);
+int cmd__sha1_is_sha1dc(int argc, const char **argv);
 int cmd__oid_array(int argc, const char **argv);
 int cmd__sha256(int argc, const char **argv);
 int cmd__sigchain(int argc, const char **argv);
index 1f6b9b08d1de626397396f9878812a7a6e6f0ef8..ba9fe36772ac318cab7170671c33e2cf38102786 100644 (file)
@@ -174,6 +174,11 @@ prepare_httpd() {
        fi
 }
 
+enable_http2 () {
+       HTTPD_PARA="$HTTPD_PARA -DHTTP2"
+       test_set_prereq HTTP2
+}
+
 start_httpd() {
        prepare_httpd >&3 2>&4
 
index 706799391bd4047a1c15f8a32f77875cb8dcb44d..0294739a77a24f9c005250cffb0cab86f0b113ff 100644 (file)
@@ -29,6 +29,11 @@ ErrorLog error.log
        LoadModule setenvif_module modules/mod_setenvif.so
 </IfModule>
 
+<IfDefine HTTP2>
+LoadModule http2_module modules/mod_http2.so
+Protocols h2c
+</IfDefine>
+
 <IfVersion < 2.4>
 LockFile accept.lock
 </IfVersion>
@@ -64,12 +69,20 @@ LockFile accept.lock
 <IfModule !mod_access_compat.c>
        LoadModule access_compat_module modules/mod_access_compat.so
 </IfModule>
-<IfModule !mod_mpm_prefork.c>
-       LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
-</IfModule>
 <IfModule !mod_unixd.c>
        LoadModule unixd_module modules/mod_unixd.so
 </IfModule>
+
+<IfDefine HTTP2>
+<IfModule !mod_mpm_event.c>
+       LoadModule mpm_event_module modules/mod_mpm_event.so
+</IfModule>
+</IfDefine>
+<IfDefine !HTTP2>
+<IfModule !mod_mpm_prefork.c>
+       LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
+</IfModule>
+</IfDefine>
 </IfVersion>
 
 PassEnv GIT_VALGRIND
index c481c012d2fc17a7944f308c2d6ef68acb284dee..325566e18ebc7fa7359d079935d6d7f81cd4dc54 100755 (executable)
@@ -49,6 +49,14 @@ test_perf "read-tree br_base br_ballast ($nr_files)" '
        git read-tree -n -m br_base br_ballast
 '
 
+test_perf "read-tree br_ballast_plus_1 ($nr_files)" '
+       # Run read-tree 100 times for clearer performance results & comparisons
+       for i in  $(test_seq 100)
+       do
+               git read-tree -n -m br_ballast_plus_1 || return 1
+       done
+'
+
 test_perf "switch between br_base br_ballast ($nr_files)" '
        git checkout -q br_base &&
        git checkout -q br_ballast
diff --git a/t/perf/p0090-cache-tree.sh b/t/perf/p0090-cache-tree.sh
new file mode 100755 (executable)
index 0000000..a8eabca
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+test_description="Tests performance of cache tree update operations"
+
+. ./perf-lib.sh
+
+test_perf_large_repo
+test_checkout_worktree
+
+count=100
+
+test_expect_success 'setup cache tree' '
+       git write-tree
+'
+
+test_cache_tree () {
+       test_perf "$1, $3" "
+               for i in \$(test_seq $count)
+               do
+                       test-tool cache-tree $4 $2
+               done
+       "
+}
+
+test_cache_tree_update_functions () {
+       test_cache_tree 'no-op' 'control' "$1" "$2"
+       test_cache_tree 'prime_cache_tree' 'prime' "$1" "$2"
+       test_cache_tree 'cache_tree_update' 'update' "$1" "$2"
+}
+
+test_cache_tree_update_functions "clean" ""
+test_cache_tree_update_functions "invalidate 2" "--invalidate 2"
+test_cache_tree_update_functions "invalidate 50" "--invalidate 50"
+test_cache_tree_update_functions "empty" "--empty"
+
+test_done
diff --git a/t/perf/p7102-reset.sh b/t/perf/p7102-reset.sh
new file mode 100755 (executable)
index 0000000..9b039e8
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+test_description='performance of reset'
+. ./perf-lib.sh
+
+test_perf_default_repo
+test_checkout_worktree
+
+test_perf 'reset --hard with change in tree' '
+       base=$(git rev-parse HEAD) &&
+       test_commit --no-tag A &&
+       new=$(git rev-parse HEAD) &&
+
+       for i in $(test_seq 10)
+       do
+               git reset --hard $new &&
+               git reset --hard $base || return $?
+       done
+'
+
+test_done
index 9ad76080aa49d46d03e2bb805d039e6a88d87e5c..53240476896d8e80a969f1b9b448fd01b45fa711 100755 (executable)
@@ -6,9 +6,11 @@ TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 TEST_DATA="$TEST_DIRECTORY/t0013"
 
-if test -z "$DC_SHA1"
+test_lazy_prereq SHA1_IS_SHA1DC 'test-tool sha1-is-sha1dc'
+
+if ! test_have_prereq SHA1_IS_SHA1DC
 then
-       skip_all='skipping sha1 collision tests, DC_SHA1 not set'
+       skip_all='skipping sha1 collision tests, not using sha1collisiondetection'
        test_done
 fi
 
index 7b5423eebdafa4f35b90ebe6194683e59e8bb46c..e2411f6a9bd93bad152360b6997bb9785d55ef18 100755 (executable)
@@ -130,7 +130,8 @@ World
 EOF
 
 test_expect_success 'run_command runs in parallel with more jobs available than tasks' '
-       test-tool run-command run-command-parallel 5 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual &&
+       test-tool run-command run-command-parallel 5 sh -c "printf \"%s\n%s\n\" Hello World" >out 2>actual &&
+       test_must_be_empty out &&
        test_cmp expect actual
 '
 
@@ -141,7 +142,8 @@ test_expect_success 'run_command runs ungrouped in parallel with more jobs avail
 '
 
 test_expect_success 'run_command runs in parallel with as many jobs as tasks' '
-       test-tool run-command run-command-parallel 4 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual &&
+       test-tool run-command run-command-parallel 4 sh -c "printf \"%s\n%s\n\" Hello World" >out 2>actual &&
+       test_must_be_empty out &&
        test_cmp expect actual
 '
 
@@ -152,7 +154,8 @@ test_expect_success 'run_command runs ungrouped in parallel with as many jobs as
 '
 
 test_expect_success 'run_command runs in parallel with more tasks than jobs available' '
-       test-tool run-command run-command-parallel 3 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual &&
+       test-tool run-command run-command-parallel 3 sh -c "printf \"%s\n%s\n\" Hello World" >out 2>actual &&
+       test_must_be_empty out &&
        test_cmp expect actual
 '
 
@@ -172,7 +175,8 @@ asking for a quick stop
 EOF
 
 test_expect_success 'run_command is asked to abort gracefully' '
-       test-tool run-command run-command-abort 3 false 2>actual &&
+       test-tool run-command run-command-abort 3 false >out 2>actual &&
+       test_must_be_empty out &&
        test_cmp expect actual
 '
 
@@ -187,7 +191,8 @@ no further jobs available
 EOF
 
 test_expect_success 'run_command outputs ' '
-       test-tool run-command run-command-no-jobs 3 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual &&
+       test-tool run-command run-command-no-jobs 3 sh -c "printf \"%s\n%s\n\" Hello World" >out 2>actual &&
+       test_must_be_empty out &&
        test_cmp expect actual
 '
 
index 4675e852517688179c2783803ebfa56e730cd24c..c6e0d655630638853b6831350618bc10b15de6e2 100755 (executable)
@@ -8,9 +8,11 @@ test_expect_success 'run based on configured value' '
        git init one &&
        git init two &&
        git init three &&
+       git init ~/four &&
        git -C two commit --allow-empty -m "DID NOT RUN" &&
        git config run.key "$TRASH_DIRECTORY/one" &&
        git config --add run.key "$TRASH_DIRECTORY/three" &&
+       git config --add run.key "~/four" &&
        git for-each-repo --config=run.key commit --allow-empty -m "ran" &&
        git -C one log -1 --pretty=format:%s >message &&
        grep ran message &&
@@ -18,12 +20,16 @@ test_expect_success 'run based on configured value' '
        ! grep ran message &&
        git -C three log -1 --pretty=format:%s >message &&
        grep ran message &&
+       git -C ~/four log -1 --pretty=format:%s >message &&
+       grep ran message &&
        git for-each-repo --config=run.key -- commit --allow-empty -m "ran again" &&
        git -C one log -1 --pretty=format:%s >message &&
        grep again message &&
        git -C two log -1 --pretty=format:%s >message &&
        ! grep again message &&
        git -C three log -1 --pretty=format:%s >message &&
+       grep again message &&
+       git -C ~/four log -1 --pretty=format:%s >message &&
        grep again message
 '
 
index a9953b6a71c360a0c39d058ba61f9ff026ec04bf..da539716359614f6d91cd1db896b3981e1805d6d 100755 (executable)
@@ -19,7 +19,7 @@ test_expect_success 'read-tree in partial clone prefetches in one batch' '
        git -C server config uploadpack.allowfilter 1 &&
        git -C server config uploadpack.allowanysha1inwant 1 &&
        git clone --bare --filter=blob:none "file://$(pwd)/server" client &&
-       GIT_TRACE_PACKET="$(pwd)/trace" git -C client read-tree $TREE &&
+       GIT_TRACE_PACKET="$(pwd)/trace" git -C client read-tree $TREE $TREE &&
 
        # "done" marks the end of negotiation (once per fetch). Expect that
        # only one fetch occurs.
index 43fcb7c0bfc85e3d29455ec37cfbf437e00238a9..2ef3579fa7c23db5055c41e68438d39871bcf6e9 100755 (executable)
@@ -95,7 +95,7 @@ test_expect_success 'git hook run -- out-of-repo runs excluded' '
 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
+       echo Hook ran $1
        EOF
 
        cat >expect <<-\EOF &&
index 7f605f865b334dee36a49b47400fd8d876e699a4..5a169b68d6af4d3db62e88dcdc4fb765ee5232a6 100755 (executable)
@@ -279,6 +279,42 @@ test_expect_success 'git branch -M and -C fail on detached HEAD' '
        test_cmp expect err
 '
 
+test_expect_success 'git branch -d on orphan HEAD (merged)' '
+       test_when_finished git checkout main &&
+       git checkout --orphan orphan &&
+       test_when_finished "rm -rf .git/objects/commit-graph*" &&
+       git commit-graph write --reachable &&
+       git branch --track to-delete main &&
+       git branch -d to-delete
+'
+
+test_expect_success 'git branch -d on orphan HEAD (merged, graph)' '
+       test_when_finished git checkout main &&
+       git checkout --orphan orphan &&
+       git branch --track to-delete main &&
+       git branch -d to-delete
+'
+
+test_expect_success 'git branch -d on orphan HEAD (unmerged)' '
+       test_when_finished git checkout main &&
+       git checkout --orphan orphan &&
+       test_when_finished "git branch -D to-delete" &&
+       git branch to-delete main &&
+       test_must_fail git branch -d to-delete 2>err &&
+       grep "not fully merged" err
+'
+
+test_expect_success 'git branch -d on orphan HEAD (unmerged, graph)' '
+       test_when_finished git checkout main &&
+       git checkout --orphan orphan &&
+       test_when_finished "git branch -D to-delete" &&
+       git branch to-delete main &&
+       test_when_finished "rm -rf .git/objects/commit-graph*" &&
+       git commit-graph write --reachable &&
+       test_must_fail git branch -d to-delete 2>err &&
+       grep "not fully merged" err
+'
+
 test_expect_success 'git branch -v -d t should work' '
        git branch t &&
        git rev-parse --verify refs/heads/t &&
index 4f5abb5ad25e9d39fc85d4d20a4ab1d4905b61e2..462cefd25df3efd51b06023523530b3d97d3d3a9 100755 (executable)
@@ -1964,6 +1964,113 @@ test_expect_success 'respect user edits to update-ref steps' '
        test_cmp_rev HEAD refs/heads/no-conflict-branch
 '
 
+test_expect_success '--update-refs: all update-ref lines removed' '
+       git checkout -b test-refs-not-removed no-conflict-branch &&
+       git branch -f base HEAD~4 &&
+       git branch -f first HEAD~3 &&
+       git branch -f second HEAD~3 &&
+       git branch -f third HEAD~1 &&
+       git branch -f tip &&
+
+       test_commit test-refs-not-removed &&
+       git commit --amend --fixup first &&
+
+       git rev-parse first second third tip no-conflict-branch >expect-oids &&
+
+       (
+               set_cat_todo_editor &&
+               test_must_fail git rebase -i --update-refs base >todo.raw &&
+               sed -e "/^update-ref/d" <todo.raw >todo
+       ) &&
+       (
+               set_replace_editor todo &&
+               git rebase -i --update-refs base
+       ) &&
+
+       # Ensure refs are not deleted and their OIDs have not changed
+       git rev-parse first second third tip no-conflict-branch >actual-oids &&
+       test_cmp expect-oids actual-oids
+'
+
+test_expect_success '--update-refs: all update-ref lines removed, then some re-added' '
+       git checkout -b test-refs-not-removed2 no-conflict-branch &&
+       git branch -f base HEAD~4 &&
+       git branch -f first HEAD~3 &&
+       git branch -f second HEAD~3 &&
+       git branch -f third HEAD~1 &&
+       git branch -f tip &&
+
+       test_commit test-refs-not-removed2 &&
+       git commit --amend --fixup first &&
+
+       git rev-parse first second third >expect-oids &&
+
+       (
+               set_cat_todo_editor &&
+               test_must_fail git rebase -i \
+                       --autosquash --update-refs \
+                       base >todo.raw &&
+               sed -e "/^update-ref/d" <todo.raw >todo
+       ) &&
+
+       # Add a break to the end of the todo so we can edit later
+       echo "break" >>todo &&
+
+       (
+               set_replace_editor todo &&
+               git rebase -i --autosquash --update-refs base &&
+               echo "update-ref refs/heads/tip" >todo &&
+               git rebase --edit-todo &&
+               git rebase --continue
+       ) &&
+
+       # Ensure first/second/third are unchanged, but tip is updated
+       git rev-parse first second third >actual-oids &&
+       test_cmp expect-oids actual-oids &&
+       test_cmp_rev HEAD tip
+'
+
+test_expect_success '--update-refs: --edit-todo with no update-ref lines' '
+       git checkout -b test-refs-not-removed3 no-conflict-branch &&
+       git branch -f base HEAD~4 &&
+       git branch -f first HEAD~3 &&
+       git branch -f second HEAD~3 &&
+       git branch -f third HEAD~1 &&
+       git branch -f tip &&
+
+       test_commit test-refs-not-removed3 &&
+       git commit --amend --fixup first &&
+
+       git rev-parse first second third tip no-conflict-branch >expect-oids &&
+
+       (
+               set_cat_todo_editor &&
+               test_must_fail git rebase -i \
+                       --autosquash --update-refs \
+                       base >todo.raw &&
+               sed -e "/^update-ref/d" <todo.raw >todo
+       ) &&
+
+       # Add a break to the beginning of the todo so we can resume with no
+       # update-ref lines
+       echo "break" >todo.new &&
+       cat todo >>todo.new &&
+
+       (
+               set_replace_editor todo.new &&
+               git rebase -i --autosquash --update-refs base &&
+
+               # Make no changes when editing so update-refs is still empty
+               cat todo >todo.new &&
+               git rebase --edit-todo &&
+               git rebase --continue
+       ) &&
+
+       # Ensure refs are not deleted and their OIDs have not changed
+       git rev-parse first second third tip no-conflict-branch >actual-oids &&
+       test_cmp expect-oids actual-oids
+'
+
 test_expect_success '--update-refs: check failed ref update' '
        git checkout -B update-refs-error no-conflict-branch &&
        git branch -f base HEAD~4 &&
index f351701fec281aa6a88e9ee7185101639c8bbcd6..fa2a06c19f0ff9890733817d066778c7634ab523 100755 (executable)
@@ -138,6 +138,23 @@ test_expect_success '`reset` refuses to overwrite untracked files' '
        git rebase --abort
 '
 
+test_expect_success '`reset` rejects trees' '
+       test_when_finished "test_might_fail git rebase --abort" &&
+       test_must_fail env GIT_SEQUENCE_EDITOR="echo reset A^{tree} >" \
+               git rebase -i B C >out 2>err &&
+       grep "object .* is a tree" err &&
+       test_must_be_empty out
+'
+
+test_expect_success '`reset` only looks for labels under refs/rewritten/' '
+       test_when_finished "test_might_fail git rebase --abort" &&
+       git branch refs/rewritten/my-label A &&
+       test_must_fail env GIT_SEQUENCE_EDITOR="echo reset my-label >" \
+               git rebase -i B C >out 2>err &&
+       grep "could not resolve ${SQ}my-label${SQ}" err &&
+       test_must_be_empty out
+'
+
 test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '
        test_when_finished "test_might_fail git rebase --abort" &&
        git checkout -b conflicting-merge A &&
index 75da8acf8f436f5a50908f8b3a9e3fe194d1d440..b9546ef8e5e5cd3a734228c4d65ffdb59f568684 100755 (executable)
@@ -178,6 +178,7 @@ test_expect_success "submodule.recurse option triggers recursive fetch" '
 '
 
 test_expect_success "fetch --recurse-submodules -j2 has the same output behaviour" '
+       test_when_finished "rm -f trace.out" &&
        add_submodule_commits &&
        (
                cd downstream &&
@@ -705,15 +706,22 @@ test_expect_success "'fetch.recurseSubmodules=on-demand' works also without .git
 
 test_expect_success 'fetching submodules respects parallel settings' '
        git config fetch.recurseSubmodules true &&
+       test_when_finished "rm -f downstream/trace.out" &&
        (
                cd downstream &&
                GIT_TRACE=$(pwd)/trace.out git fetch &&
                grep "1 tasks" trace.out &&
+               >trace.out &&
+
                GIT_TRACE=$(pwd)/trace.out git fetch --jobs 7 &&
                grep "7 tasks" trace.out &&
+               >trace.out &&
+
                git config submodule.fetchJobs 8 &&
                GIT_TRACE=$(pwd)/trace.out git fetch &&
                grep "8 tasks" trace.out &&
+               >trace.out &&
+
                GIT_TRACE=$(pwd)/trace.out git fetch --jobs 9 &&
                grep "9 tasks" trace.out &&
                >trace.out &&
index 64c6c9f59ef690c4acd681000835040ca410df7e..bc0719a4fc929540570c2d3d0e98bdd5febadea2 100755 (executable)
@@ -1,13 +1,19 @@
 #!/bin/sh
 
-test_description='test smart fetching over http via http-backend'
+: ${HTTP_PROTO:=HTTP}
+test_description="test smart fetching over http via http-backend ($HTTP_PROTO)"
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-httpd.sh
+test "$HTTP_PROTO" = "HTTP/2" && enable_http2
 start_httpd
 
+test_expect_success HTTP2 'enable client-side http/2' '
+       git config --global http.version HTTP/2
+'
+
 test_expect_success 'setup repository' '
        git config push.default matching &&
        echo content >file &&
@@ -347,7 +353,10 @@ test_expect_success CMDLINE_LIMIT \
 test_expect_success 'large fetch-pack requests can be sent using chunked encoding' '
        GIT_TRACE_CURL=true git -c http.postbuffer=65536 \
                clone --bare "$HTTPD_URL/smart/repo.git" split.git 2>err &&
-       grep "^=> Send header: Transfer-Encoding: chunked" err
+       {
+               test_have_prereq HTTP2 ||
+               grep "^=> Send header: Transfer-Encoding: chunked" err
+       }
 '
 
 test_expect_success 'test allowreachablesha1inwant' '
diff --git a/t/t5559-http-fetch-smart-http2.sh b/t/t5559-http-fetch-smart-http2.sh
new file mode 100755 (executable)
index 0000000..9eece71
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+HTTP_PROTO=HTTP/2
+. ./t5551-http-fetch-smart.sh
index 83931d482fb23b0b774310a49e8dcd6ad7eac4b9..6dbbe62eb2203c4c12619463e01e438d0bfa93bc 100755 (executable)
@@ -266,6 +266,16 @@ test_expect_success '"git bisect run" simple case' '
        git bisect reset
 '
 
+# We want to make sure no arguments has been eaten
+test_expect_success '"git bisect run" simple case' '
+       git bisect start &&
+       git bisect good $HASH1 &&
+       git bisect bad $HASH4 &&
+       git bisect run printf "%s %s\n" reset --bisect-skip >my_bisect_log.txt &&
+       grep -e "reset --bisect-skip" my_bisect_log.txt &&
+       git bisect reset
+'
+
 # We want to automatically find the commit that
 # added "Ciao" into hello.
 test_expect_success '"git bisect run" with more complex "git bisect start"' '
index a989aafaf57aa8710ff7889318abb8d3c9cadd51..eae6a46ef3d89f2563d5ec69f83dfd2ad6e7829f 100755 (executable)
@@ -579,6 +579,16 @@ test_expect_success 'status should be "modified" after submodule commit' '
        grep "^+$rev2" list
 '
 
+test_expect_success '"submodule --cached" command forms should be identical' '
+       git submodule status --cached >expect &&
+
+       git submodule --cached >actual &&
+       test_cmp expect actual &&
+
+       git submodule --cached status >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'the --cached sha1 should be rev1' '
        git submodule --cached status >list &&
        grep "^+$rev1" list
index 59bd150166793000ed60e70fc9e570519f53f055..8d7b234beb8b09b2f6dbe459aad56315612925b8 100755 (executable)
@@ -154,6 +154,11 @@ test_expect_success 'use "submodule foreach" to checkout 2nd level submodule' '
        )
 '
 
+test_expect_success 'usage: foreach -- --not-an-option' '
+       test_expect_code 1 git submodule foreach -- --not-an-option &&
+       test_expect_code 1 git -C clone2 submodule foreach -- --not-an-option
+'
+
 test_expect_success 'use "foreach --recursive" to checkout all submodules' '
        (
                cd clone2 &&
index c583c4e373ad0de2a0b51080010da995646dff3a..c0167944abdad3f3bd34c4c5fe29f9e7ec1afcf2 100755 (executable)
@@ -137,44 +137,44 @@ test_expect_success 'error in history in fetchrecursesubmodule lets continue' '
        )
 '
 
-test_expect_success 'reading submodules config from the working tree with "submodule--helper config"' '
+test_expect_success 'reading submodules config from the working tree' '
        (cd super &&
                echo "../submodule" >expect &&
-               git submodule--helper config submodule.submodule.url >actual &&
+               test-tool submodule config-list submodule.submodule.url >actual &&
                test_cmp expect actual
        )
 '
 
-test_expect_success 'unsetting submodules config from the working tree with "submodule--helper config --unset"' '
+test_expect_success 'unsetting submodules config from the working tree' '
        (cd super &&
-               git submodule--helper config --unset submodule.submodule.url &&
-               git submodule--helper config submodule.submodule.url >actual &&
+               test-tool submodule config-unset submodule.submodule.url &&
+               test-tool submodule config-list submodule.submodule.url >actual &&
                test_must_be_empty actual
        )
 '
 
 
-test_expect_success 'writing submodules config with "submodule--helper config"' '
+test_expect_success 'writing submodules config' '
        (cd super &&
                echo "new_url" >expect &&
-               git submodule--helper config submodule.submodule.url "new_url" &&
-               git submodule--helper config submodule.submodule.url >actual &&
+               test-tool submodule config-set submodule.submodule.url "new_url" &&
+               test-tool submodule config-list submodule.submodule.url >actual &&
                test_cmp expect actual
        )
 '
 
-test_expect_success 'overwriting unstaged submodules config with "submodule--helper config"' '
+test_expect_success 'overwriting unstaged submodules config' '
        test_when_finished "git -C super checkout .gitmodules" &&
        (cd super &&
                echo "newer_url" >expect &&
-               git submodule--helper config submodule.submodule.url "newer_url" &&
-               git submodule--helper config submodule.submodule.url >actual &&
+               test-tool submodule config-set submodule.submodule.url "newer_url" &&
+               test-tool submodule config-list submodule.submodule.url >actual &&
                test_cmp expect actual
        )
 '
 
 test_expect_success 'writeable .gitmodules when it is in the working tree' '
-       git -C super submodule--helper config --check-writeable
+       test-tool -C super submodule config-writeable
 '
 
 test_expect_success 'writeable .gitmodules when it is nowhere in the repository' '
@@ -183,7 +183,7 @@ test_expect_success 'writeable .gitmodules when it is nowhere in the repository'
        (cd super &&
                git rm .gitmodules &&
                git commit -m "remove .gitmodules from the current branch" &&
-               git submodule--helper config --check-writeable
+               test-tool submodule config-writeable
        )
 '
 
@@ -191,7 +191,7 @@ test_expect_success 'non-writeable .gitmodules when it is in the index but not i
        test_when_finished "git -C super checkout .gitmodules" &&
        (cd super &&
                rm -f .gitmodules &&
-               test_must_fail git submodule--helper config --check-writeable
+               test_must_fail test-tool submodule config-writeable
        )
 '
 
@@ -200,7 +200,7 @@ test_expect_success 'non-writeable .gitmodules when it is in the current branch
        test_when_finished "git -C super reset --hard $ORIG" &&
        (cd super &&
                git rm .gitmodules &&
-               test_must_fail git submodule--helper config --check-writeable
+               test_must_fail test-tool submodule config-writeable
        )
 '
 
@@ -208,11 +208,11 @@ test_expect_success 'reading submodules config from the index when .gitmodules i
        ORIG=$(git -C super rev-parse HEAD) &&
        test_when_finished "git -C super reset --hard $ORIG" &&
        (cd super &&
-               git submodule--helper config submodule.submodule.url "staged_url" &&
+               test-tool submodule config-set submodule.submodule.url "staged_url" &&
                git add .gitmodules &&
                rm -f .gitmodules &&
                echo "staged_url" >expect &&
-               git submodule--helper config submodule.submodule.url >actual &&
+               test-tool submodule config-list submodule.submodule.url >actual &&
                test_cmp expect actual
        )
 '
@@ -223,7 +223,7 @@ test_expect_success 'reading submodules config from the current branch when .git
        (cd super &&
                git rm .gitmodules &&
                echo "../submodule" >expect &&
-               git submodule--helper config submodule.submodule.url >actual &&
+               test-tool submodule config-list submodule.submodule.url >actual &&
                test_cmp expect actual
        )
 '
index d5874200fdcc44c3812f0b702fe4f8c3c2a6dbdc..dde11ecce806c43272bdc59c6eb73a7c6ce241ad 100755 (executable)
@@ -50,12 +50,12 @@ test_expect_success 'sparse checkout setup which hides .gitmodules' '
 
 test_expect_success 'reading gitmodules config file when it is not checked out' '
        echo "../submodule" >expect &&
-       git -C super submodule--helper config submodule.submodule.url >actual &&
+       test-tool -C super submodule config-list submodule.submodule.url >actual &&
        test_cmp expect actual
 '
 
 test_expect_success 'not writing gitmodules config file when it is not checked out' '
-       test_must_fail git -C super submodule--helper config submodule.submodule.url newurl &&
+       test_must_fail test-tool -C super submodule config-set submodule.submodule.url newurl &&
        test_path_is_missing super/.gitmodules
 '
 
diff --git a/t/t7422-submodule-output.sh b/t/t7422-submodule-output.sh
new file mode 100755 (executable)
index 0000000..ab946ec
--- /dev/null
@@ -0,0 +1,170 @@
+#!/bin/sh
+
+test_description='submodule --cached, --quiet etc. output'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-t3100.sh
+
+setup_sub () {
+       local d="$1" &&
+       shift &&
+       git $@ clone . "$d" &&
+       git $@ submodule add ./"$d"
+}
+
+normalize_status () {
+       sed -e 's/-g[0-9a-f]*/-gHASH/'
+}
+
+test_expect_success 'setup' '
+       test_commit A &&
+       test_commit B &&
+       setup_sub S  &&
+       setup_sub S.D &&
+       setup_sub S.C &&
+       setup_sub S.C.D &&
+       setup_sub X &&
+       git add S* &&
+       test_commit C &&
+
+       # recursive in X/
+       git -C X pull &&
+       GIT_ALLOW_PROTOCOL=file git -C X submodule update --init &&
+
+       # dirty
+       for d in S.D X/S.D
+       do
+               echo dirty >"$d"/A.t || return 1
+       done &&
+
+       # commit (for --cached)
+       for d in S.C* X/S.C*
+       do
+               git -C "$d" reset --hard A || return 1
+       done &&
+
+       # dirty
+       for d in S*.D X/S*.D
+       do
+               echo dirty >"$d/C2.t" || return 1
+       done &&
+
+       for ref in A B C
+       do
+               # Not different with SHA-1 and SHA-256, just (ab)using
+               # test_oid_cache as a variable bag to avoid using
+               # $(git rev-parse ...).
+               oid=$(git rev-parse $ref) &&
+               test_oid_cache <<-EOF || return 1
+               $ref sha1:$oid
+               $ref sha256:$oid
+               EOF
+       done
+'
+
+for opts in "" "status"
+do
+       test_expect_success "git submodule $opts" '
+               sed -e "s/^>//" >expect <<-EOF &&
+               > $(test_oid B) S (B)
+               >+$(test_oid A) S.C (A)
+               >+$(test_oid A) S.C.D (A)
+               > $(test_oid B) S.D (B)
+               >+$(test_oid C) X (C)
+               EOF
+               git submodule $opts >actual.raw &&
+               normalize_status <actual.raw >actual &&
+               test_cmp expect actual
+       '
+done
+
+for opts in \
+       "status --recursive"
+do
+       test_expect_success "git submodule $opts" '
+               sed -e "s/^>//" >expect <<-EOF &&
+               > $(test_oid B) S (B)
+               >+$(test_oid A) S.C (A)
+               >+$(test_oid A) S.C.D (A)
+               > $(test_oid B) S.D (B)
+               >+$(test_oid C) X (C)
+               > $(test_oid B) X/S (B)
+               >+$(test_oid A) X/S.C (A)
+               >+$(test_oid A) X/S.C.D (A)
+               > $(test_oid B) X/S.D (B)
+               > $(test_oid B) X/X (B)
+               EOF
+               git submodule $opts >actual.raw &&
+               normalize_status <actual.raw >actual &&
+               test_cmp expect actual
+       '
+done
+
+for opts in \
+       "--quiet" \
+       "--quiet status" \
+       "status --quiet"
+do
+       test_expect_success "git submodule $opts" '
+               git submodule $opts >out &&
+               test_must_be_empty out
+       '
+done
+
+for opts in \
+       "--cached" \
+       "--cached status" \
+       "status --cached"
+do
+       test_expect_success "git submodule $opts" '
+               sed -e "s/^>//" >expect <<-EOF &&
+               > $(test_oid B) S (B)
+               >+$(test_oid B) S.C (B)
+               >+$(test_oid B) S.C.D (B)
+               > $(test_oid B) S.D (B)
+               >+$(test_oid B) X (B)
+               EOF
+               git submodule $opts >actual.raw &&
+               normalize_status <actual.raw >actual &&
+               test_cmp expect actual
+       '
+done
+
+for opts in \
+       "--cached --quiet" \
+       "--cached --quiet status" \
+       "--cached status --quiet" \
+       "--quiet status --cached" \
+       "status --cached --quiet"
+do
+       test_expect_success "git submodule $opts" '
+               git submodule $opts >out &&
+               test_must_be_empty out
+       '
+done
+
+for opts in \
+       "status --cached --recursive" \
+       "--cached status --recursive"
+do
+       test_expect_success "git submodule $opts" '
+               sed -e "s/^>//" >expect <<-EOF &&
+               > $(test_oid B) S (B)
+               >+$(test_oid B) S.C (B)
+               >+$(test_oid B) S.C.D (B)
+               > $(test_oid B) S.D (B)
+               >+$(test_oid B) X (B)
+               > $(test_oid B) X/S (B)
+               >+$(test_oid B) X/S.C (B)
+               >+$(test_oid B) X/S.C.D (B)
+               > $(test_oid B) X/S.D (B)
+               > $(test_oid B) X/X (B)
+               EOF
+               git submodule $opts >actual.raw &&
+               normalize_status <actual.raw >actual &&
+               test_cmp expect actual
+       '
+done
+
+test_done
index 8cc64729adb20640f17de4aa9ba891a9ead7edde..7b957022f1abbd0b01cf043b52ebfd8a76042929 100755 (executable)
@@ -33,7 +33,7 @@ test_expect_success 'setup' '
                git add foo &&
                git commit -m "Add foo"
        ) &&
-       git submodule add git://example.com/submod submod &&
+       git submodule add file:///dev/null submod &&
        git add file1 "spaced name" file1[1-4] subdir/file3 .gitmodules submod &&
        git commit -m "add initial versions" &&
 
@@ -614,7 +614,7 @@ test_expect_success 'submodule in subdirectory' '
                )
        ) &&
        test_when_finished "rm -rf subdir/subdir_module" &&
-       git submodule add git://example.com/subsubmodule subdir/subdir_module &&
+       git submodule add file:///dev/null subdir/subdir_module &&
        git add subdir/subdir_module &&
        git commit -m "add submodule in subdirectory" &&
 
index 5be483bf887cc60a1c967196ccfa7584f363bc6e..c630e0d52da2e0671004ddba47b2d8f5b363eb00 100755 (executable)
@@ -543,4 +543,125 @@ test_expect_success '-n overrides repack.updateServerInfo=true' '
        test_server_info_missing
 '
 
+test_expect_success '--expire-to stores pruned objects (now)' '
+       git init expire-to-now &&
+       (
+               cd expire-to-now &&
+
+               git branch -M main &&
+
+               test_commit base &&
+
+               git checkout -b cruft &&
+               test_commit --no-tag cruft &&
+
+               git rev-list --objects --no-object-names main..cruft >moved.raw &&
+               sort moved.raw >moved.want &&
+
+               git rev-list --all --objects --no-object-names >expect.raw &&
+               sort expect.raw >expect &&
+
+               git checkout main &&
+               git branch -D cruft &&
+               git reflog expire --all --expire=all &&
+
+               git init --bare expired.git &&
+               git repack -d \
+                       --cruft --cruft-expiration="now" \
+                       --expire-to="expired.git/objects/pack/pack" &&
+
+               expired="$(ls expired.git/objects/pack/pack-*.idx)" &&
+               test_path_is_file "${expired%.idx}.mtimes" &&
+
+               # Since the `--cruft-expiration` is "now", the effective
+               # behavior is to move _all_ unreachable objects out to
+               # the location in `--expire-to`.
+               git show-index <$expired >expired.raw &&
+               cut -d" " -f2 expired.raw | sort >expired.objects &&
+               git rev-list --all --objects --no-object-names \
+                       >remaining.objects &&
+
+               # ...in other words, the combined contents of this
+               # repository and expired.git should be the same as the
+               # set of objects we started with.
+               cat expired.objects remaining.objects | sort >actual &&
+               test_cmp expect actual &&
+
+               # The "moved" objects (i.e., those in expired.git)
+               # should be the same as the cruft objects which were
+               # expired in the previous step.
+               test_cmp moved.want expired.objects
+       )
+'
+
+test_expect_success '--expire-to stores pruned objects (5.minutes.ago)' '
+       git init expire-to-5.minutes.ago &&
+       (
+               cd expire-to-5.minutes.ago &&
+
+               git branch -M main &&
+
+               test_commit base &&
+
+               # Create two classes of unreachable objects, one which
+               # is older than 5 minutes (stale), and another which is
+               # newer (recent).
+               for kind in stale recent
+               do
+                       git checkout -b $kind main &&
+                       test_commit --no-tag $kind || return 1
+               done &&
+
+               git rev-list --objects --no-object-names main..stale >in &&
+               stale="$(git pack-objects $objdir/pack/pack <in)" &&
+               mtime="$(test-tool chmtime --get =-600 $objdir/pack/pack-$stale.pack)" &&
+
+               # expect holds the set of objects we expect to find in
+               # this repository after repacking
+               git rev-list --objects --no-object-names recent >expect.raw &&
+               sort expect.raw >expect &&
+
+               # moved.want holds the set of objects we expect to find
+               # in expired.git
+               git rev-list --objects --no-object-names main..stale >out &&
+               sort out >moved.want &&
+
+               git checkout main &&
+               git branch -D stale recent &&
+               git reflog expire --all --expire=all &&
+               git prune-packed &&
+
+               git init --bare expired.git &&
+               git repack -d \
+                       --cruft --cruft-expiration=5.minutes.ago \
+                       --expire-to="expired.git/objects/pack/pack" &&
+
+               # Some of the remaining objects in this repository are
+               # unreachable, so use `cat-file --batch-all-objects`
+               # instead of `rev-list` to get their names
+               git cat-file --batch-all-objects --batch-check="%(objectname)" \
+                       >remaining.objects &&
+               sort remaining.objects >actual &&
+               test_cmp expect actual &&
+
+               (
+                       cd expired.git &&
+
+                       expired="$(ls objects/pack/pack-*.mtimes)" &&
+                       test-tool pack-mtimes $(basename $expired) >out &&
+                       cut -d" " -f1 out | sort >../moved.got &&
+
+                       # Ensure that there are as many objects with the
+                       # expected mtime as were moved to expired.git.
+                       #
+                       # In other words, ensure that the recorded
+                       # mtimes of any moved objects was written
+                       # correctly.
+                       grep " $mtime$" out >matching &&
+                       test_line_count = $(wc -l <../moved.want) matching
+               ) &&
+               test_cmp moved.want moved.got
+       )
+'
+
 test_done
index 96bdd4204560c58ec2fe97f9898e698a2379549f..823331e44a03b2f62e9f1577a84f08863150388d 100755 (executable)
@@ -500,9 +500,28 @@ test_expect_success 'register and unregister' '
        git config --global --get-all maintenance.repo >actual &&
        test_cmp before actual &&
 
+       git config --file ./other --add maintenance.repo /existing1 &&
+       git config --file ./other --add maintenance.repo /existing2 &&
+       git config --file ./other --get-all maintenance.repo >before &&
+
+       git maintenance register --config-file ./other &&
+       test_cmp_config false maintenance.auto &&
+       git config --file ./other --get-all maintenance.repo >between &&
+       cp before expect &&
+       pwd >>expect &&
+       test_cmp expect between &&
+
+       git maintenance unregister --config-file ./other &&
+       git config --file ./other --get-all maintenance.repo >actual &&
+       test_cmp before actual &&
+
        test_must_fail git maintenance unregister 2>err &&
        grep "is not registered" err &&
-       git maintenance unregister --force
+       git maintenance unregister --force &&
+
+       test_must_fail git maintenance unregister --config-file ./other 2>err &&
+       grep "is not registered" err &&
+       git maintenance unregister --config-file ./other --force
 '
 
 test_expect_success !MINGW 'register and unregister with regex metacharacters' '
index be51a8bb7a4e38e2c53c675850265c6433e905ed..25f500cf682a33787e0809bf5e7b513fd9282e4c 100755 (executable)
@@ -166,6 +166,20 @@ test_expect_success 'scalar reconfigure' '
        test true = "$(git -C one/src config core.preloadIndex)"
 '
 
+test_expect_success '`reconfigure -a` removes stale config entries' '
+       git init stale/src &&
+       scalar register stale &&
+       scalar list >scalar.repos &&
+       grep stale scalar.repos &&
+
+       grep -v stale scalar.repos >expect &&
+
+       rm -rf stale &&
+       scalar reconfigure -a &&
+       scalar list >scalar.repos &&
+       test_cmp expect scalar.repos
+'
+
 test_expect_success 'scalar delete without enlistment shows a usage' '
        test_expect_code 129 scalar delete
 '
index bae812156c4fedb8e296e422d4cb6a8af1a2dada..8a762aa0772eb4f7e107da6807092fe0a2be1a61 100644 (file)
@@ -2043,7 +2043,8 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
                if (!ret) {
                        if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0))
                                cache_tree_verify(the_repository, &o->result);
-                       if (!cache_tree_fully_valid(o->result.cache_tree))
+                       if (!o->skip_cache_tree_update &&
+                           !cache_tree_fully_valid(o->result.cache_tree))
                                cache_tree_update(&o->result,
                                                  WRITE_TREE_SILENT |
                                                  WRITE_TREE_REPAIR);
index efb9edfbb2717b4739247ecdd58a104e1d44cfd2..6ab0d74c84dc5ca3004e8b3b160dde98d1805e5d 100644 (file)
@@ -71,7 +71,8 @@ struct unpack_trees_options {
                     quiet,
                     exiting_early,
                     show_all_errors,
-                    dry_run;
+                    dry_run,
+                    skip_cache_tree_update;
        enum unpack_trees_reset_type reset;
        const char *prefix;
        int cache_bottom;