]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'js/remove-stale-scalar-repos'
authorJunio C Hamano <gitster@pobox.com>
Wed, 23 Nov 2022 02:22:23 +0000 (11:22 +0900)
committerJunio C Hamano <gitster@pobox.com>
Wed, 23 Nov 2022 02:22:23 +0000 (11:22 +0900)
'scalar reconfigure -a' is taught to automatically remove
scalar.repo entires which no longer exist.

* js/remove-stale-scalar-repos:
  tests(scalar): tighten the stale `scalar.repo` test some
  scalar reconfigure -a: remove stale `scalar.repo` entries

98 files changed:
.github/workflows/main.yml
Documentation/MyFirstContribution.txt
Documentation/RelNotes/2.39.0.txt
Documentation/config/core.txt
Documentation/config/feature.txt
Documentation/git-credential-cache.txt
Documentation/git-credential.txt
Documentation/git-ls-files.txt
Documentation/git-pack-redundant.txt
Documentation/git-repack.txt
Documentation/gitcredentials.txt
Documentation/gitformat-commit-graph.txt
Documentation/glossary-content.txt
Documentation/howto/maintain-git.txt
Documentation/technical/commit-graph.txt
Documentation/technical/parallel-checkout.txt
Documentation/technical/repository-version.txt
Documentation/technical/sparse-checkout.txt [new file with mode: 0644]
INSTALL
Makefile
add-interactive.c
archive-tar.c
bisect.c
builtin/add.c
builtin/am.c
builtin/bisect--helper.c
builtin/branch.c
builtin/clone.c
builtin/difftool.c
builtin/fetch.c
builtin/gc.c
builtin/merge-index.c
builtin/merge.c
builtin/pull.c
builtin/read-tree.c
builtin/remote.c
builtin/repack.c
builtin/reset.c
builtin/rm.c
builtin/submodule--helper.c
ci/lib.sh
compat/mingw.c
contrib/buildsystems/CMakeLists.txt
diff.c
fsmonitor-ipc.c
git-bisect.sh
git-submodule.sh
git.c
ll-merge.c
merge.c
path.c
ref-filter.c
repo-settings.c
repository.h
reset.c
revision.c
revision.h
run-command.c
run-command.h
scalar.c
sequencer.c
sha1dc_git.h
shell.c
sparse-index.c
submodule.c
submodule.h
t/helper/test-cache-tree.c [new file with mode: 0644]
t/helper/test-fake-ssh.c
t/helper/test-sha1.c
t/helper/test-submodule.c
t/helper/test-tool.c
t/helper/test-tool.h
t/helper/test-trace2.c
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/t1022-read-tree-partial-clone.sh
t/t1800-hook.sh
t/t3200-branch.sh
t/t3404-rebase-interactive.sh
t/t5000-tar-tree.sh
t/t5526-fetch-submodules.sh
t/t6030-bisect-porcelain.sh
t/t6300-for-each-ref.sh
t/t6500-gc.sh
t/t7001-mv.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/t7700-repack.sh
t/test-lib-functions.sh
tmp-objdir.h
unpack-trees.c
unpack-trees.h

index bd6f75b8e0fed74880a2608618b7480d4018797d..592f9193a8c812d56c9890422e7b3f762dd03bf9 100644 (file)
@@ -37,14 +37,14 @@ jobs:
           echo "::set-output name=enabled::$enabled"
       - name: skip if the commit or tree was already tested
         id: skip-if-redundant
-        uses: actions/github-script@v3
+        uses: actions/github-script@v6
         if: steps.check-ref.outputs.enabled == 'yes'
         with:
           github-token: ${{secrets.GITHUB_TOKEN}}
           script: |
             try {
               // Figure out workflow ID, commit and tree
-              const { data: run } = await github.actions.getWorkflowRun({
+              const { data: run } = await github.rest.actions.getWorkflowRun({
                 owner: context.repo.owner,
                 repo: context.repo.repo,
                 run_id: context.runId,
@@ -54,7 +54,7 @@ jobs:
               const tree_id = run.head_commit.tree_id;
 
               // See whether there is a successful run for that commit or tree
-              const { data: runs } = await github.actions.listWorkflowRuns({
+              const { data: runs } = await github.rest.actions.listWorkflowRuns({
                 owner: context.repo.owner,
                 repo: context.repo.repo,
                 per_page: 500,
index 1a4be8ee0adde5db48e5f53a8fe79dd17036b270..ccfd0cb5f3e2aadb04bed17b5082ae71fc5fa2e0 100644 (file)
@@ -736,7 +736,7 @@ the {lore}[Git mailing list archive]:
 2022-02-21  1:43     ` John Cai
 2022-02-21  1:50       ` Taylor Blau
 2022-02-23 19:50         ` John Cai
-2022-02-18 20:00   ` // other replies ellided
+2022-02-18 20:00   ` // other replies elided
 2022-02-18 18:40 ` [PATCH 2/3] reflog: call reflog_delete from reflog.c John Cai via GitGitGadget
 2022-02-18 19:15   ` Ã†var Arnfjörð Bjarmason
 2022-02-18 20:26     ` Junio C Hamano
index 7096f07689be18b66c4467d17fe381a10712f503..153bf6d89b83ce08b3cb46805f9426afda30f42a 100644 (file)
@@ -29,6 +29,12 @@ UI, Workflows & Features
    existing bugs in the internal patch-id logic that did not match
    what "git patch-id" produces have been corrected.
 
+ * 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.
 --------------------------------------------------------------
 
@@ -84,6 +90,40 @@ Performance, Internal Implementation, Development Support etc.
  * Make sure generated dependency file is stably sorted to help
    developers debugging their build issues.
 
+ * The glossary entries for "commit-graph file" and "reachability
+   bitmap" have been added.
+
+ * Various tests exercising the transfer.credentialsInUrl
+   configuration are taught to avoid making requests which require
+   resolving localhost to reduce CI-flakiness.
+
+ * A redundant diagnostic message is dropped from test_path_is_missing().
+
+ * Simplify the run-command API.
+
+ * Update the actions/github-script dependency in CI to avoid a
+   deprecation warning.
+
+ * Progress on being able to initialize a rev_info struct with a
+   macro.
+
+ * Add trace2 counters to the region to clear skip worktree bits in a
+   sparse checkout.
+
+ * 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
 -----------------
 
@@ -219,6 +259,18 @@ Fixes since v2.38
    configuration are taught to avoid making requests which require
    resolving localhost to reduce CI-flakiness.
 
+ * The adjust_shared_perm() helper function learned to refrain from
+   setting the "g+s" bit on directories when it is not necessary.
+
+ * "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 37afbaf5a419d17f22e6598607cc13d121a8020e..dfbdaf00b8bc2239e7e106c8b78a2dd90d44ac9b 100644 (file)
@@ -618,7 +618,7 @@ but risks losing recent work in the event of an unclean system shutdown.
 * `loose-object` hardens objects added to the repo in loose-object form.
 * `pack` hardens objects added to the repo in packfile form.
 * `pack-metadata` hardens packfile bitmaps and indexes.
-* `commit-graph` hardens the commit graph file.
+* `commit-graph` hardens the commit-graph file.
 * `index` hardens the index when it is modified.
 * `objects` is an aggregate option that is equivalent to
   `loose-object,pack`.
index cdecd04e5bb7ea05efee6124a669da72ad0c5384..95975e50912c6f423820a5440d6696ac5326dc51 100644 (file)
@@ -14,6 +14,9 @@ feature.experimental::
 +
 * `fetch.negotiationAlgorithm=skipping` may improve fetch negotiation times by
 skipping more commits at a time, reducing the number of round trips.
++
+* `gc.cruftPacks=true` reduces disk space used by unreachable objects during
+garbage collection, preventing loose object explosions.
 
 feature.manyFiles::
        Enable config options that optimize for repos with many files in the
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 d7986419c2507a7032961ba5b37ce6c73e5822f9..440043cdb8e4da5908c4378b2d90e8da3b14c956 100644 (file)
@@ -10,8 +10,8 @@ SYNOPSIS
 --------
 [verse]
 'git ls-files' [-z] [-t] [-v] [-f]
-               [-c|--cached] [-d|--deleted] [-o|--others] [-i|--|ignored]
-               [-s|--stage] [-u|--unmerged] [-k|--|killed] [-m|--modified]
+               [-c|--cached] [-d|--deleted] [-o|--others] [-i|--ignored]
+               [-s|--stage] [-u|--unmerged] [-k|--killed] [-m|--modified]
                [--directory [--no-empty-directory]] [--eol]
                [--deduplicate]
                [-x <pattern>|--exclude=<pattern>]
index dda80a740c879d1a1300caad3478a56ce62cad2a..99ef13839d49cdaa93c1b5a4b523ace18ce454db 100644 (file)
@@ -34,7 +34,7 @@ OPTIONS
 
 --alt-odb::
        Don't require objects present in packs from alternate object
-       directories to be present in local packs.
+       database (odb) directories to be present in local packs.
 
 --verbose::
        Outputs some statistics to stderr. Has a small performance penalty.
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 80517b4eb2cb259d7b99528902e3192db2f4166d..d6665d2f31f6ea2f418c6e01bc2cba47f98f671f 100644 (file)
@@ -17,9 +17,10 @@ DESCRIPTION
 
 Git will sometimes need credentials from the user in order to perform
 operations; for example, it may need to ask for a username and password
-in order to access a remote repository over HTTP. This manual describes
-the mechanisms Git uses to request these credentials, as well as some
-features to avoid inputting these credentials repeatedly.
+in order to access a remote repository over HTTP. Some remotes accept
+a personal access token or OAuth access token as a password. This
+manual describes the mechanisms Git uses to request these credentials,
+as well as some features to avoid inputting these credentials repeatedly.
 
 REQUESTING CREDENTIALS
 ----------------------
@@ -269,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 7324665716d7db7f352354f80c61406629264e95..31cad585e233031d205e5338bed74fcaf125de1f 100644 (file)
@@ -3,7 +3,7 @@ gitformat-commit-graph(5)
 
 NAME
 ----
-gitformat-commit-graph - Git commit graph format
+gitformat-commit-graph - Git commit-graph format
 
 SYNOPSIS
 --------
@@ -14,7 +14,7 @@ $GIT_DIR/objects/info/commit-graphs/*
 DESCRIPTION
 -----------
 
-The Git commit graph stores a list of commit OIDs and some associated
+The Git commit-graph stores a list of commit OIDs and some associated
 metadata, including:
 
 - The generation number of the commit.
@@ -34,7 +34,7 @@ corresponding to the array position within the list of commit OIDs. Due
 to some special constants we use to track parents, we can store at most
 (1 << 30) + (1 << 29) + (1 << 28) - 1 (around 1.8 billion) commits.
 
-== Commit graph files have the following format:
+== Commit-graph files have the following format:
 
 In order to allow extensions that add extra data to the graph, we organize
 the body into "chunks" and provide a binary lookup table at the beginning
index aa2f41f5e700774ef49eb7079c5239448fc61276..5a537268e275e45142640820942004b5cd731dbb 100644 (file)
@@ -20,7 +20,7 @@
 [[def_branch]]branch::
        A "branch" is a line of development.  The most recent
        <<def_commit,commit>> on a branch is referred to as the tip of
-       that branch.  The tip of the branch is referenced by a branch
+       that branch.  The tip of the branch is <<def_ref,referenced>> by a branch
        <<def_head,head>>, which moves forward as additional development
        is done on the branch.  A single Git
        <<def_repository,repository>> can track an arbitrary number of
@@ -75,6 +75,21 @@ state in the Git history, by creating a new commit representing the current
 state of the <<def_index,index>> and advancing <<def_HEAD,HEAD>>
 to point at the new commit.
 
+[[def_commit_graph_general]]commit graph concept, representations and usage::
+       A synonym for the <<def_DAG,DAG>> structure formed by the commits
+       in the object database, <<def_ref,referenced>> by branch tips,
+       using their <<def_chain,chain>> of linked commits.
+       This structure is the definitive commit graph. The
+       graph can be represented in other ways, e.g. the
+       <<def_commit_graph_file,"commit-graph" file>>.
+
+[[def_commit_graph_file]]commit-graph file::
+       The "commit-graph" (normally hyphenated) file is a supplemental
+       representation of the <<def_commit_graph_general,commit graph>>
+       which accelerates commit graph walks. The "commit-graph" file is
+       stored either in the .git/objects/info directory or in the info
+       directory of an alternate object database.
+
 [[def_commit_object]]commit object::
        An <<def_object,object>> which contains the information about a
        particular <<def_revision,revision>>, such as <<def_parent,parents>>, committer,
@@ -262,7 +277,7 @@ This commit is referred to as a "merge commit", or sometimes just a
        identified by its <<def_object_name,object name>>. The objects usually
        live in `$GIT_DIR/objects/`.
 
-[[def_object_identifier]]object identifier::
+[[def_object_identifier]]object identifier (oid)::
        Synonym for <<def_object_name,object name>>.
 
 [[def_object_name]]object name::
@@ -493,6 +508,14 @@ exclude;;
        <<def_tree_object,trees>> to the trees or <<def_blob_object,blobs>>
        that they contain.
 
+[[def_reachability_bitmap]]reachability bitmaps::
+       Reachability bitmaps store information about the
+       <<def_reachable,reachability>> of a selected set of commits in
+       a packfile, or a multi-pack index (MIDX), to speed up object search.
+       The bitmaps are stored in a ".bitmap" file. A repository may have at
+       most one bitmap file in use. The bitmap file may belong to either one
+       pack, or the repository's multi-pack index (if it exists).
+
 [[def_rebase]]rebase::
        To reapply a series of changes from a <<def_branch,branch>> to a
        different base, and reset the <<def_head,head>> of that branch
index a67130debb63dc99cbd860fd7b31fb187b1f9571..215e2edb0f5f47fac8e1cf5dea91c7f8d1881d5b 100644 (file)
@@ -256,7 +256,7 @@ by doing the following:
    merged to 'next', add it at the end of the list.  Then:
 
      $ git checkout -B jch master
-     $ Meta/redo-jch.sh -c1
+     $ sh Meta/redo-jch.sh -c1
 
    to rebuild the 'jch' branch from scratch.  "-c1" tells the script
    to stop merging at the first line that begins with '###'
index 90c9760c230555053db461a8cb3d1a3676729cd7..86fed0de0f77f97031621ffd4ca94c1cbd2b5ba1 100644 (file)
@@ -1,4 +1,4 @@
-Git Commit Graph Design Notes
+Git Commit-Graph Design Notes
 =============================
 
 Git walks the commit graph for many reasons, including:
@@ -17,7 +17,7 @@ There are two main costs here:
 
 The commit-graph file is a supplemental data structure that accelerates
 commit graph walks. If a user downgrades or disables the 'core.commitGraph'
-config setting, then the existing ODB is sufficient. The file is stored
+config setting, then the existing object database is sufficient. The file is stored
 as "commit-graph" either in the .git/objects/info directory or in the info
 directory of an alternate.
 
@@ -95,7 +95,7 @@ with default order), but is not used when the topological order is
 required (such as merge base calculations, "git log --graph").
 
 In practice, we expect some commits to be created recently and not stored
-in the commit graph. We can treat these commits as having "infinite"
+in the commit-graph. We can treat these commits as having "infinite"
 generation number and walk until reaching commits with known generation
 number.
 
@@ -149,7 +149,7 @@ Design Details
   helpful for these clones, anyway. The commit-graph will not be read or
   written when shallow commits are present.
 
-Commit Graphs Chains
+Commit-Graphs Chains
 --------------------
 
 Typically, repos grow with near-constant velocity (commits per day). Over time,
index e790258a1adcdb9d683074a63fd6c941743bb757..47c9b6183cfad0f3f8e60df2003cd60657a92b0d 100644 (file)
@@ -56,7 +56,7 @@ Rejected Multi-Threaded Solution
 
 The most "straightforward" implementation would be to spread the set of
 to-be-updated cache entries across multiple threads. But due to the
-thread-unsafe functions in the ODB code, we would have to use locks to
+thread-unsafe functions in the object database code, we would have to use locks to
 coordinate the parallel operation. An early prototype of this solution
 showed that the multi-threaded checkout would bring performance
 improvements over the sequential code, but there was still too much lock
index 7844ef30ffdefd9158ae49ddd5d03d6a5c4edd4c..8ef664b0b9537ad3c8fc554e68448a69a3b7f439 100644 (file)
@@ -82,9 +82,9 @@ When the config key `extensions.preciousObjects` is set to `true`,
 objects in the repository MUST NOT be deleted (e.g., by `git-prune` or
 `git repack -d`).
 
-==== `partialclone`
+==== `partialClone`
 
-When the config key `extensions.partialclone` is set, it indicates
+When the config key `extensions.partialClone` is set, it indicates
 that the repo was created with a partial clone (or later performed
 a partial fetch) and that the remote may have omitted sending
 certain unwanted objects.  Such a remote is called a "promisor remote"
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..54ede18bbb5fd8885fdfee321255093db8a3bbf6 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
@@ -1826,7 +1891,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 +3053,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)\' >>$@+
index f071b2a1b4f2ee8bf1959706d1778c3697d81ba7..ecc5ae1b2499c56beae72ec4756011b4b8296134 100644 (file)
@@ -997,18 +997,17 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,
        count = list_and_choose(s, files, opts);
        opts->flags = 0;
        if (count > 0) {
-               struct strvec args = STRVEC_INIT;
+               struct child_process cmd = CHILD_PROCESS_INIT;
 
-               strvec_pushl(&args, "git", "diff", "-p", "--cached",
+               strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached",
                             oid_to_hex(!is_initial ? &oid :
                                        s->r->hash_algo->empty_tree),
                             "--", NULL);
                for (i = 0; i < files->items.nr; i++)
                        if (files->selected[i])
-                               strvec_push(&args,
+                               strvec_push(&cmd.args,
                                            files->items.items[i].string);
-               res = run_command_v_opt(args.v, 0);
-               strvec_clear(&args);
+               res = run_command(&cmd);
        }
 
        putchar('\n');
index 3e4822b68409b85d36436bd42da862991274cd81..f8fad2946ef97324756b78363fb90d329f504209 100644 (file)
@@ -498,6 +498,7 @@ static int write_tar_filter_archive(const struct archiver *ar,
        strvec_push(&filter.args, cmd.buf);
        filter.use_shell = 1;
        filter.in = -1;
+       filter.silent_exec_failure = 1;
 
        if (start_command(&filter) < 0)
                die_errno(_("unable to start '%s' filter"), cmd.buf);
index fd581b85a72cc6d1f9b447893f9a3b345a8ce9da..ec7487e6836c7ec5819f0b22cc7b08bce0e59fa0 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -22,8 +22,6 @@ static struct oid_array skipped_revs;
 
 static struct object_id *current_bad_oid;
 
-static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL};
-
 static const char *term_bad;
 static const char *term_good;
 
@@ -729,20 +727,22 @@ static int is_expected_rev(const struct object_id *oid)
 enum bisect_error bisect_checkout(const struct object_id *bisect_rev,
                                  int no_checkout)
 {
-       char bisect_rev_hex[GIT_MAX_HEXSZ + 1];
        struct commit *commit;
        struct pretty_print_context pp = {0};
        struct strbuf commit_msg = STRBUF_INIT;
 
-       oid_to_hex_r(bisect_rev_hex, bisect_rev);
        update_ref(NULL, "BISECT_EXPECTED_REV", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
 
-       argv_checkout[2] = bisect_rev_hex;
        if (no_checkout) {
                update_ref(NULL, "BISECT_HEAD", bisect_rev, NULL, 0,
                           UPDATE_REFS_DIE_ON_ERR);
        } else {
-               if (run_command_v_opt(argv_checkout, RUN_GIT_CMD))
+               struct child_process cmd = CHILD_PROCESS_INIT;
+
+               cmd.git_cmd = 1;
+               strvec_pushl(&cmd.args, "checkout", "-q",
+                            oid_to_hex(bisect_rev), "--", NULL);
+               if (run_command(&cmd))
                        /*
                         * Errors in `run_command()` itself, signaled by res < 0,
                         * and errors in the child process, signaled by res > 0
index f84372964c8c486601941c927d6e39bb8da42084..626c71ec6aac6ff92ca2cb168ad6e4b6846cebd3 100644 (file)
@@ -240,8 +240,8 @@ static int refresh(int verbose, const struct pathspec *pathspec)
 int run_add_interactive(const char *revision, const char *patch_mode,
                        const struct pathspec *pathspec)
 {
-       int status, i;
-       struct strvec argv = STRVEC_INIT;
+       int i;
+       struct child_process cmd = CHILD_PROCESS_INIT;
        int use_builtin_add_i =
                git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
 
@@ -272,19 +272,18 @@ int run_add_interactive(const char *revision, const char *patch_mode,
                return !!run_add_p(the_repository, mode, revision, pathspec);
        }
 
-       strvec_push(&argv, "add--interactive");
+       strvec_push(&cmd.args, "add--interactive");
        if (patch_mode)
-               strvec_push(&argv, patch_mode);
+               strvec_push(&cmd.args, patch_mode);
        if (revision)
-               strvec_push(&argv, revision);
-       strvec_push(&argv, "--");
+               strvec_push(&cmd.args, revision);
+       strvec_push(&cmd.args, "--");
        for (i = 0; i < pathspec->nr; i++)
                /* pass original pathspec, to be re-parsed */
-               strvec_push(&argv, pathspec->items[i].original);
+               strvec_push(&cmd.args, pathspec->items[i].original);
 
-       status = run_command_v_opt(argv.v, RUN_GIT_CMD);
-       strvec_clear(&argv);
-       return status;
+       cmd.git_cmd = 1;
+       return run_command(&cmd);
 }
 
 int interactive_add(const char **argv, const char *prefix, int patch)
index 39fea24833078be76d76d974e5b601715ed51aea..20aea0d2487b2dfe851d44f753e7de7439698223 100644 (file)
@@ -2187,14 +2187,12 @@ static int show_patch(struct am_state *state, enum show_patch_type sub_mode)
        int len;
 
        if (!is_null_oid(&state->orig_commit)) {
-               const char *av[4] = { "show", NULL, "--", NULL };
-               char *new_oid_str;
-               int ret;
+               struct child_process cmd = CHILD_PROCESS_INIT;
 
-               av[1] = new_oid_str = xstrdup(oid_to_hex(&state->orig_commit));
-               ret = run_command_v_opt(av, RUN_GIT_CMD);
-               free(new_oid_str);
-               return ret;
+               strvec_pushl(&cmd.args, "show", oid_to_hex(&state->orig_commit),
+                            "--", NULL);
+               cmd.git_cmd = 1;
+               return run_command(&cmd);
        }
 
        switch (sub_mode) {
index 28ef7ec2a48856b1c13f1cdd31546985148752f0..6e41cbdb2d3785cadb5ab901586585daee8718e0 100644 (file)
@@ -220,18 +220,17 @@ static int bisect_reset(const char *commit)
        }
 
        if (!ref_exists("BISECT_HEAD")) {
-               struct strvec argv = STRVEC_INIT;
+               struct child_process cmd = CHILD_PROCESS_INIT;
 
-               strvec_pushl(&argv, "checkout", branch.buf, "--", NULL);
-               if (run_command_v_opt(argv.v, RUN_GIT_CMD)) {
+               cmd.git_cmd = 1;
+               strvec_pushl(&cmd.args, "checkout", branch.buf, "--", NULL);
+               if (run_command(&cmd)) {
                        error(_("could not check out original"
                                " HEAD '%s'. Try 'git bisect"
                                " reset <commit>'."), branch.buf);
                        strbuf_release(&branch);
-                       strvec_clear(&argv);
                        return -1;
                }
-               strvec_clear(&argv);
        }
 
        strbuf_release(&branch);
@@ -765,10 +764,12 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, const char **a
                strbuf_read_file(&start_head, git_path_bisect_start(), 0);
                strbuf_trim(&start_head);
                if (!no_checkout) {
-                       const char *argv[] = { "checkout", start_head.buf,
-                                              "--", NULL };
+                       struct child_process cmd = CHILD_PROCESS_INIT;
 
-                       if (run_command_v_opt(argv, RUN_GIT_CMD)) {
+                       cmd.git_cmd = 1;
+                       strvec_pushl(&cmd.args, "checkout", start_head.buf,
+                                    "--", NULL);
+                       if (run_command(&cmd)) {
                                res = error(_("checking out '%s' failed."
                                                 " Try 'git bisect start "
                                                 "<valid-branch>'."),
@@ -1098,40 +1099,38 @@ static enum bisect_error bisect_skip(struct bisect_terms *terms, const char **ar
 
 static int bisect_visualize(struct bisect_terms *terms, const char **argv, int argc)
 {
-       struct strvec args = STRVEC_INIT;
-       int flags = RUN_COMMAND_NO_STDIN, res = 0;
+       struct child_process cmd = CHILD_PROCESS_INIT;
        struct strbuf sb = STRBUF_INIT;
 
        if (bisect_next_check(terms, NULL) != 0)
                return BISECT_FAILED;
 
+       cmd.no_stdin = 1;
        if (!argc) {
                if ((getenv("DISPLAY") || getenv("SESSIONNAME") || getenv("MSYSTEM") ||
                     getenv("SECURITYSESSIONID")) && exists_in_PATH("gitk")) {
-                       strvec_push(&args, "gitk");
+                       strvec_push(&cmd.args, "gitk");
                } else {
-                       strvec_push(&args, "log");
-                       flags |= RUN_GIT_CMD;
+                       strvec_push(&cmd.args, "log");
+                       cmd.git_cmd = 1;
                }
        } else {
                if (argv[0][0] == '-') {
-                       strvec_push(&args, "log");
-                       flags |= RUN_GIT_CMD;
+                       strvec_push(&cmd.args, "log");
+                       cmd.git_cmd = 1;
                } else if (strcmp(argv[0], "tig") && !starts_with(argv[0], "git"))
-                       flags |= RUN_GIT_CMD;
+                       cmd.git_cmd = 1;
 
-               strvec_pushv(&args, argv);
+               strvec_pushv(&cmd.args, argv);
        }
 
-       strvec_pushl(&args, "--bisect", "--", NULL);
+       strvec_pushl(&cmd.args, "--bisect", "--", NULL);
 
        strbuf_read_file(&sb, git_path_bisect_names(), 0);
-       sq_dequote_to_strvec(sb.buf, &args);
+       sq_dequote_to_strvec(sb.buf, &cmd.args);
        strbuf_release(&sb);
 
-       res = run_command_v_opt(args.v, flags);
-       strvec_clear(&args);
-       return res;
+       return run_command(&cmd);
 }
 
 static int get_first_good(const char *refname UNUSED,
@@ -1142,8 +1141,17 @@ static int get_first_good(const char *refname UNUSED,
        return 1;
 }
 
-static int verify_good(const struct bisect_terms *terms,
-                      const char **quoted_argv)
+static int do_bisect_run(const char *command)
+{
+       struct child_process cmd = CHILD_PROCESS_INIT;
+
+       printf(_("running %s\n"), command);
+       cmd.use_shell = 1;
+       strvec_push(&cmd.args, command);
+       return run_command(&cmd);
+}
+
+static int verify_good(const struct bisect_terms *terms, const char *command)
 {
        int rc;
        enum bisect_error res;
@@ -1163,8 +1171,7 @@ static int verify_good(const struct bisect_terms *terms,
        if (res != BISECT_OK)
                return -1;
 
-       printf(_("running %s\n"), quoted_argv[0]);
-       rc = run_command_v_opt(quoted_argv, RUN_USING_SHELL);
+       rc = do_bisect_run(command);
 
        res = bisect_checkout(&current_rev, no_checkout);
        if (res != BISECT_OK)
@@ -1177,7 +1184,6 @@ static int bisect_run(struct bisect_terms *terms, const char **argv, int argc)
 {
        int res = BISECT_OK;
        struct strbuf command = STRBUF_INIT;
-       struct strvec run_args = STRVEC_INIT;
        const char *new_state;
        int temporary_stdout_fd, saved_stdout;
        int is_first_run = 1;
@@ -1192,11 +1198,8 @@ static int bisect_run(struct bisect_terms *terms, const char **argv, int argc)
                return BISECT_FAILED;
        }
 
-       strvec_push(&run_args, command.buf);
-
        while (1) {
-               printf(_("running %s\n"), command.buf);
-               res = run_command_v_opt(run_args.v, RUN_USING_SHELL);
+               res = do_bisect_run(command.buf);
 
                /*
                 * Exit code 126 and 127 can either come from the shell
@@ -1206,7 +1209,7 @@ static int bisect_run(struct bisect_terms *terms, const char **argv, int argc)
                 * missing or non-executable script.
                 */
                if (is_first_run && (res == 126 || res == 127)) {
-                       int rc = verify_good(terms, run_args.v);
+                       int rc = verify_good(terms, command.buf);
                        is_first_run = 0;
                        if (rc < 0) {
                                error(_("unable to verify '%s' on good"
@@ -1273,119 +1276,147 @@ static int bisect_run(struct bisect_terms *terms, const char **argv, int argc)
        }
 
        strbuf_release(&command);
-       strvec_clear(&run_args);
+       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 547d6464b3c04b83cb1376d325eeefb79aca079c..0e4348686b6efb13bc1aa8847f8012a416e2080c 100644 (file)
@@ -653,9 +653,9 @@ static void update_head(const struct ref *our, const struct ref *remote,
 
 static int git_sparse_checkout_init(const char *repo)
 {
-       struct strvec argv = STRVEC_INIT;
+       struct child_process cmd = CHILD_PROCESS_INIT;
        int result = 0;
-       strvec_pushl(&argv, "-C", repo, "sparse-checkout", "set", NULL);
+       strvec_pushl(&cmd.args, "-C", repo, "sparse-checkout", "set", NULL);
 
        /*
         * We must apply the setting in the current process
@@ -663,12 +663,12 @@ static int git_sparse_checkout_init(const char *repo)
         */
        core_apply_sparse_checkout = 1;
 
-       if (run_command_v_opt(argv.v, RUN_GIT_CMD)) {
+       cmd.git_cmd = 1;
+       if (run_command(&cmd)) {
                error(_("failed to initialize sparse-checkout"));
                result = 1;
        }
 
-       strvec_clear(&argv);
        return result;
 }
 
@@ -733,37 +733,38 @@ static int checkout(int submodule_progress, int filter_submodules)
                           oid_to_hex(&oid), "1", NULL);
 
        if (!err && (option_recurse_submodules.nr > 0)) {
-               struct strvec args = STRVEC_INIT;
-               strvec_pushl(&args, "submodule", "update", "--require-init", "--recursive", NULL);
+               struct child_process cmd = CHILD_PROCESS_INIT;
+               strvec_pushl(&cmd.args, "submodule", "update", "--require-init",
+                            "--recursive", NULL);
 
                if (option_shallow_submodules == 1)
-                       strvec_push(&args, "--depth=1");
+                       strvec_push(&cmd.args, "--depth=1");
 
                if (max_jobs != -1)
-                       strvec_pushf(&args, "--jobs=%d", max_jobs);
+                       strvec_pushf(&cmd.args, "--jobs=%d", max_jobs);
 
                if (submodule_progress)
-                       strvec_push(&args, "--progress");
+                       strvec_push(&cmd.args, "--progress");
 
                if (option_verbosity < 0)
-                       strvec_push(&args, "--quiet");
+                       strvec_push(&cmd.args, "--quiet");
 
                if (option_remote_submodules) {
-                       strvec_push(&args, "--remote");
-                       strvec_push(&args, "--no-fetch");
+                       strvec_push(&cmd.args, "--remote");
+                       strvec_push(&cmd.args, "--no-fetch");
                }
 
                if (filter_submodules && filter_options.choice)
-                       strvec_pushf(&args, "--filter=%s",
+                       strvec_pushf(&cmd.args, "--filter=%s",
                                     expand_list_objects_filter_spec(&filter_options));
 
                if (option_single_branch >= 0)
-                       strvec_push(&args, option_single_branch ?
+                       strvec_push(&cmd.args, option_single_branch ?
                                               "--single-branch" :
                                               "--no-single-branch");
 
-               err = run_command_v_opt(args.v, RUN_GIT_CMD);
-               strvec_clear(&args);
+               cmd.git_cmd = 1;
+               err = run_command(&cmd);
        }
 
        return err;
@@ -864,11 +865,15 @@ static void write_refspec_config(const char *src_ref_prefix,
 
 static void dissociate_from_references(void)
 {
-       static const char* argv[] = { "repack", "-a", "-d", NULL };
        char *alternates = git_pathdup("objects/info/alternates");
 
        if (!access(alternates, F_OK)) {
-               if (run_command_v_opt(argv, RUN_GIT_CMD|RUN_COMMAND_NO_STDIN))
+               struct child_process cmd = CHILD_PROCESS_INIT;
+
+               cmd.git_cmd = 1;
+               cmd.no_stdin = 1;
+               strvec_pushl(&cmd.args, "repack", "-a", "-d", NULL);
+               if (run_command(&cmd))
                        die(_("cannot repack to clean up"));
                if (unlink(alternates) && errno != ENOENT)
                        die_errno(_("cannot unlink temporary alternates file"));
index 4b10ad1a36908fe8067daa1e66a000c8c8e85718..d7f08c8a7fa201750465d37a3b6b8fbb7b91ac6a 100644 (file)
@@ -44,8 +44,11 @@ static int difftool_config(const char *var, const char *value, void *cb)
 
 static int print_tool_help(void)
 {
-       const char *argv[] = { "mergetool", "--tool-help=diff", NULL };
-       return run_command_v_opt(argv, RUN_GIT_CMD);
+       struct child_process cmd = CHILD_PROCESS_INIT;
+
+       cmd.git_cmd = 1;
+       strvec_pushl(&cmd.args, "mergetool", "--tool-help=diff", NULL);
+       return run_command(&cmd);
 }
 
 static int parse_index_info(char *p, int *mode1, int *mode2,
@@ -360,8 +363,8 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
        struct pair_entry *entry;
        struct index_state wtindex;
        struct checkout lstate, rstate;
-       int flags = RUN_GIT_CMD, err = 0;
-       const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL };
+       int err = 0;
+       struct child_process cmd = CHILD_PROCESS_INIT;
        struct hashmap wt_modified, tmp_modified;
        int indices_loaded = 0;
 
@@ -563,16 +566,17 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
        }
 
        strbuf_setlen(&ldir, ldir_len);
-       helper_argv[1] = ldir.buf;
        strbuf_setlen(&rdir, rdir_len);
-       helper_argv[2] = rdir.buf;
 
        if (extcmd) {
-               helper_argv[0] = extcmd;
-               flags = 0;
-       } else
+               strvec_push(&cmd.args, extcmd);
+       } else {
+               strvec_push(&cmd.args, "difftool--helper");
+               cmd.git_cmd = 1;
                setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
-       ret = run_command_v_opt(helper_argv, flags);
+       }
+       strvec_pushl(&cmd.args, ldir.buf, rdir.buf, NULL);
+       ret = run_command(&cmd);
 
        /* TODO: audit for interaction with sparse-index. */
        ensure_full_index(&wtindex);
index b06e454cbddaf1fb3cf8211acab71e90a526448b..7378cafeec9fd6ea3edc521646ab7d8f66bcc02a 100644 (file)
@@ -1972,14 +1972,17 @@ static int fetch_multiple(struct string_list *list, int max_children)
        } else
                for (i = 0; i < list->nr; i++) {
                        const char *name = list->items[i].string;
-                       strvec_push(&argv, name);
+                       struct child_process cmd = CHILD_PROCESS_INIT;
+
+                       strvec_pushv(&cmd.args, argv.v);
+                       strvec_push(&cmd.args, name);
                        if (verbosity >= 0)
                                printf(_("Fetching %s\n"), name);
-                       if (run_command_v_opt(argv.v, RUN_GIT_CMD)) {
+                       cmd.git_cmd = 1;
+                       if (run_command(&cmd)) {
                                error(_("could not fetch %s"), name);
                                result = 1;
                        }
-                       strvec_pop(&argv);
                }
 
        strvec_clear(&argv);
index 24ea85c7afdd706efcad37642d9023a7f20bd43f..6b08dcf3c56482f3ed794071c5f2ea780e141cdb 100644 (file)
@@ -42,7 +42,7 @@ static const char * const builtin_gc_usage[] = {
 
 static int pack_refs = 1;
 static int prune_reflogs = 1;
-static int cruft_packs = 0;
+static int cruft_packs = -1;
 static int aggressive_depth = 50;
 static int aggressive_window = 250;
 static int gc_auto_threshold = 6700;
@@ -167,9 +167,11 @@ static void gc_config(void)
 struct maintenance_run_opts;
 static int maintenance_task_pack_refs(MAYBE_UNUSED struct maintenance_run_opts *opts)
 {
-       const char *argv[] = { "pack-refs", "--all", "--prune", NULL };
+       struct child_process cmd = CHILD_PROCESS_INIT;
 
-       return run_command_v_opt(argv, RUN_GIT_CMD);
+       cmd.git_cmd = 1;
+       strvec_pushl(&cmd.args, "pack-refs", "--all", "--prune", NULL);
+       return run_command(&cmd);
 }
 
 static int too_many_loose_objects(void)
@@ -535,8 +537,14 @@ static void gc_before_repack(void)
        if (pack_refs && maintenance_task_pack_refs(NULL))
                die(FAILED_RUN, "pack-refs");
 
-       if (prune_reflogs && run_command_v_opt(reflog.v, RUN_GIT_CMD))
-               die(FAILED_RUN, reflog.v[0]);
+       if (prune_reflogs) {
+               struct child_process cmd = CHILD_PROCESS_INIT;
+
+               cmd.git_cmd = 1;
+               strvec_pushv(&cmd.args, reflog.v);
+               if (run_command(&cmd))
+                       die(FAILED_RUN, reflog.v[0]);
+       }
 }
 
 int cmd_gc(int argc, const char **argv, const char *prefix)
@@ -550,6 +558,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
        int daemonized = 0;
        int keep_largest_pack = -1;
        timestamp_t dummy;
+       struct child_process rerere_cmd = CHILD_PROCESS_INIT;
 
        struct option builtin_gc_options[] = {
                OPT__QUIET(&quiet, N_("suppress progress reporting")),
@@ -593,6 +602,10 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
        if (prune_expire && parse_expiry_date(prune_expire, &dummy))
                die(_("failed to parse prune expiry value %s"), prune_expire);
 
+       prepare_repo_settings(the_repository);
+       if (cruft_packs < 0)
+               cruft_packs = the_repository->settings.gc_cruft_packs;
+
        if (aggressive) {
                strvec_push(&repack, "-f");
                if (aggressive_depth > 0)
@@ -671,11 +684,17 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
        gc_before_repack();
 
        if (!repository_format_precious_objects) {
-               if (run_command_v_opt(repack.v,
-                                     RUN_GIT_CMD | RUN_CLOSE_OBJECT_STORE))
+               struct child_process repack_cmd = CHILD_PROCESS_INIT;
+
+               repack_cmd.git_cmd = 1;
+               repack_cmd.close_object_store = 1;
+               strvec_pushv(&repack_cmd.args, repack.v);
+               if (run_command(&repack_cmd))
                        die(FAILED_RUN, repack.v[0]);
 
                if (prune_expire) {
+                       struct child_process prune_cmd = CHILD_PROCESS_INIT;
+
                        /* run `git prune` even if using cruft packs */
                        strvec_push(&prune, prune_expire);
                        if (quiet)
@@ -683,18 +702,26 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                        if (has_promisor_remote())
                                strvec_push(&prune,
                                            "--exclude-promisor-objects");
-                       if (run_command_v_opt(prune.v, RUN_GIT_CMD))
+                       prune_cmd.git_cmd = 1;
+                       strvec_pushv(&prune_cmd.args, prune.v);
+                       if (run_command(&prune_cmd))
                                die(FAILED_RUN, prune.v[0]);
                }
        }
 
        if (prune_worktrees_expire) {
+               struct child_process prune_worktrees_cmd = CHILD_PROCESS_INIT;
+
                strvec_push(&prune_worktrees, prune_worktrees_expire);
-               if (run_command_v_opt(prune_worktrees.v, RUN_GIT_CMD))
+               prune_worktrees_cmd.git_cmd = 1;
+               strvec_pushv(&prune_worktrees_cmd.args, prune_worktrees.v);
+               if (run_command(&prune_worktrees_cmd))
                        die(FAILED_RUN, prune_worktrees.v[0]);
        }
 
-       if (run_command_v_opt(rerere.v, RUN_GIT_CMD))
+       rerere_cmd.git_cmd = 1;
+       strvec_pushv(&rerere_cmd.args, rerere.v);
+       if (run_command(&rerere_cmd))
                die(FAILED_RUN, rerere.v[0]);
 
        report_garbage = report_pack_garbage;
@@ -704,7 +731,6 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                clean_pack_garbage();
        }
 
-       prepare_repo_settings(the_repository);
        if (the_repository->settings.gc_write_commit_graph == 1)
                write_commit_graph_reachable(the_repository->objects->odb,
                                             !quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0,
@@ -1910,20 +1936,16 @@ static char *schtasks_task_name(const char *frequency)
 static int schtasks_remove_task(enum schedule_priority schedule)
 {
        const char *cmd = "schtasks";
-       int result;
-       struct strvec args = STRVEC_INIT;
+       struct child_process child = CHILD_PROCESS_INIT;
        const char *frequency = get_frequency(schedule);
        char *name = schtasks_task_name(frequency);
 
        get_schedule_cmd(&cmd, NULL);
-       strvec_split(&args, cmd);
-       strvec_pushl(&args, "/delete", "/tn", name, "/f", NULL);
-
-       result = run_command_v_opt(args.v, 0);
-
-       strvec_clear(&args);
+       strvec_split(&child.args, cmd);
+       strvec_pushl(&child.args, "/delete", "/tn", name, "/f", NULL);
        free(name);
-       return result;
+
+       return run_command(&child);
 }
 
 static int schtasks_remove_tasks(void)
index c0383fe9df9a3eb2e34f8c5869f996ebc81fce2e..012f52bd007ce769b2b067cff72b8d8259060dbe 100644 (file)
@@ -12,6 +12,7 @@ static int merge_entry(int pos, const char *path)
        const char *arguments[] = { pgm, "", "", "", path, "", "", "", NULL };
        char hexbuf[4][GIT_MAX_HEXSZ + 1];
        char ownbuf[4][60];
+       struct child_process cmd = CHILD_PROCESS_INIT;
 
        if (pos >= active_nr)
                die("git merge-index: %s not in the cache", path);
@@ -31,7 +32,8 @@ static int merge_entry(int pos, const char *path)
        if (!found)
                die("git merge-index: %s not in the cache", path);
 
-       if (run_command_v_opt(arguments, 0)) {
+       strvec_pushv(&cmd.args, arguments);
+       if (run_command(&cmd)) {
                if (one_shot)
                        err++;
                else {
index 5900b81729d8da757d04613781d41f2e7621ebcd..b3f75f55c8a82db92f31b7f32a503eb70357e9c8 100644 (file)
@@ -345,60 +345,49 @@ out:
        return rc;
 }
 
-static void read_empty(const struct object_id *oid, int verbose)
+static void read_empty(const struct object_id *oid)
 {
-       int i = 0;
-       const char *args[7];
-
-       args[i++] = "read-tree";
-       if (verbose)
-               args[i++] = "-v";
-       args[i++] = "-m";
-       args[i++] = "-u";
-       args[i++] = empty_tree_oid_hex();
-       args[i++] = oid_to_hex(oid);
-       args[i] = NULL;
+       struct child_process cmd = CHILD_PROCESS_INIT;
+
+       strvec_pushl(&cmd.args, "read-tree", "-m", "-u", empty_tree_oid_hex(),
+                    oid_to_hex(oid), NULL);
+       cmd.git_cmd = 1;
 
-       if (run_command_v_opt(args, RUN_GIT_CMD))
+       if (run_command(&cmd))
                die(_("read-tree failed"));
 }
 
-static void reset_hard(const struct object_id *oid, int verbose)
+static void reset_hard(const struct object_id *oid)
 {
-       int i = 0;
-       const char *args[6];
-
-       args[i++] = "read-tree";
-       if (verbose)
-               args[i++] = "-v";
-       args[i++] = "--reset";
-       args[i++] = "-u";
-       args[i++] = oid_to_hex(oid);
-       args[i] = NULL;
+       struct child_process cmd = CHILD_PROCESS_INIT;
+
+       strvec_pushl(&cmd.args, "read-tree", "-v", "--reset", "-u",
+                    oid_to_hex(oid), NULL);
+       cmd.git_cmd = 1;
 
-       if (run_command_v_opt(args, RUN_GIT_CMD))
+       if (run_command(&cmd))
                die(_("read-tree failed"));
 }
 
 static void restore_state(const struct object_id *head,
                          const struct object_id *stash)
 {
-       struct strvec args = STRVEC_INIT;
+       struct child_process cmd = CHILD_PROCESS_INIT;
 
-       reset_hard(head, 1);
+       reset_hard(head);
 
        if (is_null_oid(stash))
                goto refresh_cache;
 
-       strvec_pushl(&args, "stash", "apply", "--index", "--quiet", NULL);
-       strvec_push(&args, oid_to_hex(stash));
+       strvec_pushl(&cmd.args, "stash", "apply", "--index", "--quiet", NULL);
+       strvec_push(&cmd.args, oid_to_hex(stash));
 
        /*
         * It is OK to ignore error here, for example when there was
         * nothing to restore.
         */
-       run_command_v_opt(args.v, RUN_GIT_CMD);
-       strvec_clear(&args);
+       cmd.git_cmd = 1;
+       run_command(&cmd);
 
 refresh_cache:
        if (discard_cache() < 0 || read_cache() < 0)
@@ -1470,7 +1459,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                                               check_trust_level);
 
                remote_head_oid = &remoteheads->item->object.oid;
-               read_empty(remote_head_oid, 0);
+               read_empty(remote_head_oid);
                update_ref("initial pull", "HEAD", remote_head_oid, NULL, 0,
                           UPDATE_REFS_DIE_ON_ERR);
                goto done;
index 403a24d7ca670f9a5c56ebfb6d8429b5e5817b54..b21edd767aaf31d36cf938451812e1dd45584985 100644 (file)
@@ -515,76 +515,75 @@ static void parse_repo_refspecs(int argc, const char **argv, const char **repo,
  */
 static int run_fetch(const char *repo, const char **refspecs)
 {
-       struct strvec args = STRVEC_INIT;
-       int ret;
+       struct child_process cmd = CHILD_PROCESS_INIT;
 
-       strvec_pushl(&args, "fetch", "--update-head-ok", NULL);
+       strvec_pushl(&cmd.args, "fetch", "--update-head-ok", NULL);
 
        /* Shared options */
-       argv_push_verbosity(&args);
+       argv_push_verbosity(&cmd.args);
        if (opt_progress)
-               strvec_push(&args, opt_progress);
+               strvec_push(&cmd.args, opt_progress);
 
        /* Options passed to git-fetch */
        if (opt_all)
-               strvec_push(&args, opt_all);
+               strvec_push(&cmd.args, opt_all);
        if (opt_append)
-               strvec_push(&args, opt_append);
+               strvec_push(&cmd.args, opt_append);
        if (opt_upload_pack)
-               strvec_push(&args, opt_upload_pack);
-       argv_push_force(&args);
+               strvec_push(&cmd.args, opt_upload_pack);
+       argv_push_force(&cmd.args);
        if (opt_tags)
-               strvec_push(&args, opt_tags);
+               strvec_push(&cmd.args, opt_tags);
        if (opt_prune)
-               strvec_push(&args, opt_prune);
+               strvec_push(&cmd.args, opt_prune);
        if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT)
                switch (recurse_submodules_cli) {
                case RECURSE_SUBMODULES_ON:
-                       strvec_push(&args, "--recurse-submodules=on");
+                       strvec_push(&cmd.args, "--recurse-submodules=on");
                        break;
                case RECURSE_SUBMODULES_OFF:
-                       strvec_push(&args, "--recurse-submodules=no");
+                       strvec_push(&cmd.args, "--recurse-submodules=no");
                        break;
                case RECURSE_SUBMODULES_ON_DEMAND:
-                       strvec_push(&args, "--recurse-submodules=on-demand");
+                       strvec_push(&cmd.args, "--recurse-submodules=on-demand");
                        break;
                default:
                        BUG("submodule recursion option not understood");
                }
        if (max_children)
-               strvec_push(&args, max_children);
+               strvec_push(&cmd.args, max_children);
        if (opt_dry_run)
-               strvec_push(&args, "--dry-run");
+               strvec_push(&cmd.args, "--dry-run");
        if (opt_keep)
-               strvec_push(&args, opt_keep);
+               strvec_push(&cmd.args, opt_keep);
        if (opt_depth)
-               strvec_push(&args, opt_depth);
+               strvec_push(&cmd.args, opt_depth);
        if (opt_unshallow)
-               strvec_push(&args, opt_unshallow);
+               strvec_push(&cmd.args, opt_unshallow);
        if (opt_update_shallow)
-               strvec_push(&args, opt_update_shallow);
+               strvec_push(&cmd.args, opt_update_shallow);
        if (opt_refmap)
-               strvec_push(&args, opt_refmap);
+               strvec_push(&cmd.args, opt_refmap);
        if (opt_ipv4)
-               strvec_push(&args, opt_ipv4);
+               strvec_push(&cmd.args, opt_ipv4);
        if (opt_ipv6)
-               strvec_push(&args, opt_ipv6);
+               strvec_push(&cmd.args, opt_ipv6);
        if (opt_show_forced_updates > 0)
-               strvec_push(&args, "--show-forced-updates");
+               strvec_push(&cmd.args, "--show-forced-updates");
        else if (opt_show_forced_updates == 0)
-               strvec_push(&args, "--no-show-forced-updates");
+               strvec_push(&cmd.args, "--no-show-forced-updates");
        if (set_upstream)
-               strvec_push(&args, set_upstream);
-       strvec_pushv(&args, opt_fetch.v);
+               strvec_push(&cmd.args, set_upstream);
+       strvec_pushv(&cmd.args, opt_fetch.v);
 
        if (repo) {
-               strvec_push(&args, repo);
-               strvec_pushv(&args, refspecs);
+               strvec_push(&cmd.args, repo);
+               strvec_pushv(&cmd.args, refspecs);
        } else if (*refspecs)
                BUG("refspecs without repo?");
-       ret = run_command_v_opt(args.v, RUN_GIT_CMD | RUN_CLOSE_OBJECT_STORE);
-       strvec_clear(&args);
-       return ret;
+       cmd.git_cmd = 1;
+       cmd.close_object_store = 1;
+       return run_command(&cmd);
 }
 
 /**
@@ -653,52 +652,50 @@ static int update_submodules(void)
  */
 static int run_merge(void)
 {
-       int ret;
-       struct strvec args = STRVEC_INIT;
+       struct child_process cmd = CHILD_PROCESS_INIT;
 
-       strvec_pushl(&args, "merge", NULL);
+       strvec_pushl(&cmd.args, "merge", NULL);
 
        /* Shared options */
-       argv_push_verbosity(&args);
+       argv_push_verbosity(&cmd.args);
        if (opt_progress)
-               strvec_push(&args, opt_progress);
+               strvec_push(&cmd.args, opt_progress);
 
        /* Options passed to git-merge */
        if (opt_diffstat)
-               strvec_push(&args, opt_diffstat);
+               strvec_push(&cmd.args, opt_diffstat);
        if (opt_log)
-               strvec_push(&args, opt_log);
+               strvec_push(&cmd.args, opt_log);
        if (opt_signoff)
-               strvec_push(&args, opt_signoff);
+               strvec_push(&cmd.args, opt_signoff);
        if (opt_squash)
-               strvec_push(&args, opt_squash);
+               strvec_push(&cmd.args, opt_squash);
        if (opt_commit)
-               strvec_push(&args, opt_commit);
+               strvec_push(&cmd.args, opt_commit);
        if (opt_edit)
-               strvec_push(&args, opt_edit);
+               strvec_push(&cmd.args, opt_edit);
        if (cleanup_arg)
-               strvec_pushf(&args, "--cleanup=%s", cleanup_arg);
+               strvec_pushf(&cmd.args, "--cleanup=%s", cleanup_arg);
        if (opt_ff)
-               strvec_push(&args, opt_ff);
+               strvec_push(&cmd.args, opt_ff);
        if (opt_verify)
-               strvec_push(&args, opt_verify);
+               strvec_push(&cmd.args, opt_verify);
        if (opt_verify_signatures)
-               strvec_push(&args, opt_verify_signatures);
-       strvec_pushv(&args, opt_strategies.v);
-       strvec_pushv(&args, opt_strategy_opts.v);
+               strvec_push(&cmd.args, opt_verify_signatures);
+       strvec_pushv(&cmd.args, opt_strategies.v);
+       strvec_pushv(&cmd.args, opt_strategy_opts.v);
        if (opt_gpg_sign)
-               strvec_push(&args, opt_gpg_sign);
+               strvec_push(&cmd.args, opt_gpg_sign);
        if (opt_autostash == 0)
-               strvec_push(&args, "--no-autostash");
+               strvec_push(&cmd.args, "--no-autostash");
        else if (opt_autostash == 1)
-               strvec_push(&args, "--autostash");
+               strvec_push(&cmd.args, "--autostash");
        if (opt_allow_unrelated_histories > 0)
-               strvec_push(&args, "--allow-unrelated-histories");
+               strvec_push(&cmd.args, "--allow-unrelated-histories");
 
-       strvec_push(&args, "FETCH_HEAD");
-       ret = run_command_v_opt(args.v, RUN_GIT_CMD);
-       strvec_clear(&args);
-       return ret;
+       strvec_push(&cmd.args, "FETCH_HEAD");
+       cmd.git_cmd = 1;
+       return run_command(&cmd);
 }
 
 /**
@@ -879,43 +876,41 @@ static int get_rebase_newbase_and_upstream(struct object_id *newbase,
 static int run_rebase(const struct object_id *newbase,
                const struct object_id *upstream)
 {
-       int ret;
-       struct strvec args = STRVEC_INIT;
+       struct child_process cmd = CHILD_PROCESS_INIT;
 
-       strvec_push(&args, "rebase");
+       strvec_push(&cmd.args, "rebase");
 
        /* Shared options */
-       argv_push_verbosity(&args);
+       argv_push_verbosity(&cmd.args);
 
        /* Options passed to git-rebase */
        if (opt_rebase == REBASE_MERGES)
-               strvec_push(&args, "--rebase-merges");
+               strvec_push(&cmd.args, "--rebase-merges");
        else if (opt_rebase == REBASE_INTERACTIVE)
-               strvec_push(&args, "--interactive");
+               strvec_push(&cmd.args, "--interactive");
        if (opt_diffstat)
-               strvec_push(&args, opt_diffstat);
-       strvec_pushv(&args, opt_strategies.v);
-       strvec_pushv(&args, opt_strategy_opts.v);
+               strvec_push(&cmd.args, opt_diffstat);
+       strvec_pushv(&cmd.args, opt_strategies.v);
+       strvec_pushv(&cmd.args, opt_strategy_opts.v);
        if (opt_gpg_sign)
-               strvec_push(&args, opt_gpg_sign);
+               strvec_push(&cmd.args, opt_gpg_sign);
        if (opt_signoff)
-               strvec_push(&args, opt_signoff);
+               strvec_push(&cmd.args, opt_signoff);
        if (opt_autostash == 0)
-               strvec_push(&args, "--no-autostash");
+               strvec_push(&cmd.args, "--no-autostash");
        else if (opt_autostash == 1)
-               strvec_push(&args, "--autostash");
+               strvec_push(&cmd.args, "--autostash");
        if (opt_verify_signatures &&
            !strcmp(opt_verify_signatures, "--verify-signatures"))
                warning(_("ignoring --verify-signatures for rebase"));
 
-       strvec_push(&args, "--onto");
-       strvec_push(&args, oid_to_hex(newbase));
+       strvec_push(&cmd.args, "--onto");
+       strvec_push(&cmd.args, oid_to_hex(newbase));
 
-       strvec_push(&args, oid_to_hex(upstream));
+       strvec_push(&cmd.args, oid_to_hex(upstream));
 
-       ret = run_command_v_opt(args.v, RUN_GIT_CMD);
-       strvec_clear(&args);
-       return ret;
+       cmd.git_cmd = 1;
+       return run_command(&cmd);
 }
 
 static int get_can_ff(struct object_id *orig_head,
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 93285fc06e6b462b215b4947206cbdc04fa082fb..729f6f3643add1819942facaae5175555f9c167b 100644 (file)
@@ -92,13 +92,15 @@ static int verbose;
 
 static int fetch_remote(const char *name)
 {
-       const char *argv[] = { "fetch", name, NULL, NULL };
-       if (verbose) {
-               argv[1] = "-v";
-               argv[2] = name;
-       }
+       struct child_process cmd = CHILD_PROCESS_INIT;
+
+       strvec_push(&cmd.args, "fetch");
+       if (verbose)
+               strvec_push(&cmd.args, "-v");
+       strvec_push(&cmd.args, name);
+       cmd.git_cmd = 1;
        printf_ln(_("Updating %s"), name);
-       if (run_command_v_opt(argv, RUN_GIT_CMD))
+       if (run_command(&cmd))
                return error(_("Could not fetch %s"), name);
        return 0;
 }
@@ -1508,37 +1510,35 @@ static int update(int argc, const char **argv, const char *prefix)
                         N_("prune remotes after fetching")),
                OPT_END()
        };
-       struct strvec fetch_argv = STRVEC_INIT;
+       struct child_process cmd = CHILD_PROCESS_INIT;
        int default_defined = 0;
-       int retval;
 
        argc = parse_options(argc, argv, prefix, options,
                             builtin_remote_update_usage,
                             PARSE_OPT_KEEP_ARGV0);
 
-       strvec_push(&fetch_argv, "fetch");
+       strvec_push(&cmd.args, "fetch");
 
        if (prune != -1)
-               strvec_push(&fetch_argv, prune ? "--prune" : "--no-prune");
+               strvec_push(&cmd.args, prune ? "--prune" : "--no-prune");
        if (verbose)
-               strvec_push(&fetch_argv, "-v");
-       strvec_push(&fetch_argv, "--multiple");
+               strvec_push(&cmd.args, "-v");
+       strvec_push(&cmd.args, "--multiple");
        if (argc < 2)
-               strvec_push(&fetch_argv, "default");
+               strvec_push(&cmd.args, "default");
        for (i = 1; i < argc; i++)
-               strvec_push(&fetch_argv, argv[i]);
+               strvec_push(&cmd.args, argv[i]);
 
-       if (strcmp(fetch_argv.v[fetch_argv.nr-1], "default") == 0) {
+       if (strcmp(cmd.args.v[cmd.args.nr-1], "default") == 0) {
                git_config(get_remote_default, &default_defined);
                if (!default_defined) {
-                       strvec_pop(&fetch_argv);
-                       strvec_push(&fetch_argv, "--all");
+                       strvec_pop(&cmd.args);
+                       strvec_push(&cmd.args, "--all");
                }
        }
 
-       retval = run_command_v_opt(fetch_argv.v, RUN_GIT_CMD);
-       strvec_clear(&fetch_argv);
-       return retval;
+       cmd.git_cmd = 1;
+       return run_command(&cmd);
 }
 
 static int remove_all_fetch_refspecs(const char *key)
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 901375d58415a3ae21f03a15e7b78da6f8b08efa..d614f156df1db31248e3a1de51273ffefdd671ff 100644 (file)
@@ -196,16 +196,19 @@ static int read_yes_no_answer(void)
 static int ask_yes_no_if_possible(const char *format, ...)
 {
        char question[4096];
-       const char *retry_hook[] = { NULL, NULL, NULL };
+       const char *retry_hook;
        va_list args;
 
        va_start(args, format);
        vsnprintf(question, sizeof(question), format, args);
        va_end(args);
 
-       if ((retry_hook[0] = mingw_getenv("GIT_ASK_YESNO"))) {
-               retry_hook[1] = question;
-               return !run_command_v_opt(retry_hook, 0);
+       retry_hook = mingw_getenv("GIT_ASK_YESNO");
+       if (retry_hook) {
+               struct child_process cmd = CHILD_PROCESS_INIT;
+
+               strvec_pushl(&cmd.args, retry_hook, question, NULL);
+               return !run_command(&cmd);
        }
 
        if (!isatty(_fileno(stdin)) || !isatty(_fileno(stderr)))
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")
diff --git a/diff.c b/diff.c
index 35e46dd9684cd3462899f70ea57bd24946051723..9f9a92ec9d29701e601362a4b3f2d8d2ed35d394 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -4301,35 +4301,34 @@ static void run_external_diff(const char *pgm,
                              const char *xfrm_msg,
                              struct diff_options *o)
 {
-       struct strvec argv = STRVEC_INIT;
-       struct strvec env = STRVEC_INIT;
+       struct child_process cmd = CHILD_PROCESS_INIT;
        struct diff_queue_struct *q = &diff_queued_diff;
 
-       strvec_push(&argv, pgm);
-       strvec_push(&argv, name);
+       strvec_push(&cmd.args, pgm);
+       strvec_push(&cmd.args, name);
 
        if (one && two) {
-               add_external_diff_name(o->repo, &argv, name, one);
+               add_external_diff_name(o->repo, &cmd.args, name, one);
                if (!other)
-                       add_external_diff_name(o->repo, &argv, name, two);
+                       add_external_diff_name(o->repo, &cmd.args, name, two);
                else {
-                       add_external_diff_name(o->repo, &argv, other, two);
-                       strvec_push(&argv, other);
-                       strvec_push(&argv, xfrm_msg);
+                       add_external_diff_name(o->repo, &cmd.args, other, two);
+                       strvec_push(&cmd.args, other);
+                       strvec_push(&cmd.args, xfrm_msg);
                }
        }
 
-       strvec_pushf(&env, "GIT_DIFF_PATH_COUNTER=%d", ++o->diff_path_counter);
-       strvec_pushf(&env, "GIT_DIFF_PATH_TOTAL=%d", q->nr);
+       strvec_pushf(&cmd.env, "GIT_DIFF_PATH_COUNTER=%d",
+                    ++o->diff_path_counter);
+       strvec_pushf(&cmd.env, "GIT_DIFF_PATH_TOTAL=%d", q->nr);
 
        diff_free_filespec_data(one);
        diff_free_filespec_data(two);
-       if (run_command_v_opt_cd_env(argv.v, RUN_USING_SHELL, NULL, env.v))
+       cmd.use_shell = 1;
+       if (run_command(&cmd))
                die(_("external diff died, stopping at %s"), name);
 
        remove_tempfile();
-       strvec_clear(&argv);
-       strvec_clear(&env);
 }
 
 static int similarity_index(struct diff_filepair *p)
index c0f42301c849c7d697816a0e56577283dba32949..19d772f0f3ae3737d70e69837ff0d9aa3021da62 100644 (file)
@@ -54,10 +54,14 @@ enum ipc_active_state fsmonitor_ipc__get_state(void)
 
 static int spawn_daemon(void)
 {
-       const char *args[] = { "fsmonitor--daemon", "start", NULL };
+       struct child_process cmd = CHILD_PROCESS_INIT;
 
-       return run_command_v_opt_tr2(args, RUN_COMMAND_NO_STDIN | RUN_GIT_CMD,
-                                   "fsmonitor");
+       cmd.git_cmd = 1;
+       cmd.no_stdin = 1;
+       cmd.trace2_child_class = "fsmonitor";
+       strvec_pushl(&cmd.args, "fsmonitor--daemon", "start", NULL);
+
+       return run_command(&cmd);
 }
 
 int fsmonitor_ipc__send_query(const char *since_token,
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 ee7758dcb0e40e660c41001cd3c05f64ffcfb54c..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 },
@@ -787,7 +787,7 @@ static int run_argv(int *argcp, const char ***argv)
                if (!done_alias)
                        handle_builtin(*argcp, *argv);
                else if (get_builtin(**argv)) {
-                       struct strvec args = STRVEC_INIT;
+                       struct child_process cmd = CHILD_PROCESS_INIT;
                        int i;
 
                        /*
@@ -804,18 +804,21 @@ static int run_argv(int *argcp, const char ***argv)
 
                        commit_pager_choice();
 
-                       strvec_push(&args, "git");
+                       strvec_push(&cmd.args, "git");
                        for (i = 0; i < *argcp; i++)
-                               strvec_push(&args, (*argv)[i]);
+                               strvec_push(&cmd.args, (*argv)[i]);
 
-                       trace_argv_printf(args.v, "trace: exec:");
+                       trace_argv_printf(cmd.args.v, "trace: exec:");
 
                        /*
                         * if we fail because the command is not found, it is
                         * OK to return. Otherwise, we just pass along the status code.
                         */
-                       i = run_command_v_opt_tr2(args.v, RUN_SILENT_EXEC_FAILURE |
-                                                 RUN_CLEAN_ON_EXIT | RUN_WAIT_AFTER_CLEAN, "git_alias");
+                       cmd.silent_exec_failure = 1;
+                       cmd.clean_on_exit = 1;
+                       cmd.wait_after_clean = 1;
+                       cmd.trace2_child_class = "git_alias";
+                       i = run_command(&cmd);
                        if (i >= 0 || errno != ENOENT)
                                exit(i);
                        die("could not execute builtin %s", **argv);
index a8e2db933612ce56646bce347e038fc498788b95..22a603e8af402d11fff97172d3f3ded3a9849cd4 100644 (file)
@@ -193,7 +193,7 @@ static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
        struct strbuf cmd = STRBUF_INIT;
        struct strbuf_expand_dict_entry dict[6];
        struct strbuf path_sq = STRBUF_INIT;
-       const char *args[] = { NULL, NULL };
+       struct child_process child = CHILD_PROCESS_INIT;
        int status, fd, i;
        struct stat st;
        enum ll_merge_result ret;
@@ -219,8 +219,9 @@ static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
 
        strbuf_expand(&cmd, fn->cmdline, strbuf_expand_dict_cb, &dict);
 
-       args[0] = cmd.buf;
-       status = run_command_v_opt(args, RUN_USING_SHELL);
+       child.use_shell = 1;
+       strvec_push(&child.args, cmd.buf);
+       status = run_command(&child);
        fd = open(temp[1], O_RDONLY);
        if (fd < 0)
                goto bad;
diff --git a/merge.c b/merge.c
index 2382ff66d351cce20848b6f2ff056258adb2d4d7..445b4f19aa8618b5641f3f007e8cb42d9d39b57c 100644 (file)
--- a/merge.c
+++ b/merge.c
@@ -19,22 +19,22 @@ int try_merge_command(struct repository *r,
                      const char **xopts, struct commit_list *common,
                      const char *head_arg, struct commit_list *remotes)
 {
-       struct strvec args = STRVEC_INIT;
+       struct child_process cmd = CHILD_PROCESS_INIT;
        int i, ret;
        struct commit_list *j;
 
-       strvec_pushf(&args, "merge-%s", strategy);
+       strvec_pushf(&cmd.args, "merge-%s", strategy);
        for (i = 0; i < xopts_nr; i++)
-               strvec_pushf(&args, "--%s", xopts[i]);
+               strvec_pushf(&cmd.args, "--%s", xopts[i]);
        for (j = common; j; j = j->next)
-               strvec_push(&args, merge_argument(j->item));
-       strvec_push(&args, "--");
-       strvec_push(&args, head_arg);
+               strvec_push(&cmd.args, merge_argument(j->item));
+       strvec_push(&cmd.args, "--");
+       strvec_push(&cmd.args, head_arg);
        for (j = remotes; j; j = j->next)
-               strvec_push(&args, merge_argument(j->item));
+               strvec_push(&cmd.args, merge_argument(j->item));
 
-       ret = run_command_v_opt(args.v, RUN_GIT_CMD);
-       strvec_clear(&args);
+       cmd.git_cmd = 1;
+       ret = run_command(&cmd);
 
        discard_index(r->index);
        if (repo_read_index(r) < 0)
diff --git a/path.c b/path.c
index a3cfcd8a6e95b3d018c55529d0e532a9d48da0ef..492e17ad12106233ddde63b724992f388693be10 100644 (file)
--- a/path.c
+++ b/path.c
@@ -901,7 +901,13 @@ int adjust_shared_perm(const char *path)
        if (S_ISDIR(old_mode)) {
                /* Copy read bits to execute bits */
                new_mode |= (new_mode & 0444) >> 2;
-               new_mode |= FORCE_DIR_SET_GID;
+
+               /*
+                * g+s matters only if any extra access is granted
+                * based on group membership.
+                */
+               if (FORCE_DIR_SET_GID && (new_mode & 060))
+                       new_mode |= FORCE_DIR_SET_GID;
        }
 
        if (((old_mode ^ new_mode) & ~S_IFMT) &&
index 914908fac524e788cb362511deb0592e4948f62e..9dc2cd14519d07fb976a3007c1d8d47fa9a9a549 100644 (file)
@@ -1375,12 +1375,12 @@ static void find_subpos(const char *buf,
        /* subject is first non-empty line */
        *sub = buf;
        /* subject goes to first empty line before signature begins */
-       if ((eol = strstr(*sub, "\n\n"))) {
+       if ((eol = strstr(*sub, "\n\n")) ||
+           (eol = strstr(*sub, "\r\n\r\n"))) {
                eol = eol < sigstart ? eol : sigstart;
-       /* check if message uses CRLF */
-       } else if (! (eol = strstr(*sub, "\r\n\r\n"))) {
+       } else {
                /* treat whole message as subject */
-               eol = strrchr(*sub, '\0');
+               eol = sigstart;
        }
        buf = eol;
        *sublen = buf - *sub;
index e8b58151bc4a01d0a90ba49d89ddf52d3d780995..3021921c53d22a29ff4a677fc0f569f4818e8244 100644 (file)
@@ -43,6 +43,7 @@ void prepare_repo_settings(struct repository *r)
        /* Defaults modified by feature.* */
        if (experimental) {
                r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;
+               r->settings.gc_cruft_packs = 1;
        }
        if (manyfiles) {
                r->settings.index_version = 4;
index 24316ac944edcd5827de279d14fef7dea9334c74..6c461c5b9def017947c90bd84140ae48556e252a 100644 (file)
@@ -34,6 +34,7 @@ struct repo_settings {
        int commit_graph_generation_version;
        int commit_graph_read_changed_paths;
        int gc_write_commit_graph;
+       int gc_cruft_packs;
        int fetch_write_commit_graph;
        int command_requires_full_index;
        int sparse_index;
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 0760e78936e4f147fc3170d4a7ba75b7e6e88ab5..c6b399658345aafc3f182cdb76a868c97d8b0c62 100644 (file)
@@ -1865,30 +1865,15 @@ void repo_init_revisions(struct repository *r,
                         struct rev_info *revs,
                         const char *prefix)
 {
-       memset(revs, 0, sizeof(*revs));
+       struct rev_info blank = REV_INFO_INIT;
+       memcpy(revs, &blank, sizeof(*revs));
 
        revs->repo = r;
-       revs->abbrev = DEFAULT_ABBREV;
-       revs->simplify_history = 1;
        revs->pruning.repo = r;
-       revs->pruning.flags.recursive = 1;
-       revs->pruning.flags.quick = 1;
        revs->pruning.add_remove = file_add_remove;
        revs->pruning.change = file_change;
        revs->pruning.change_fn_data = revs;
-       revs->sort_order = REV_SORT_IN_GRAPH_ORDER;
-       revs->dense = 1;
        revs->prefix = prefix;
-       revs->max_age = -1;
-       revs->max_age_as_filter = -1;
-       revs->min_age = -1;
-       revs->skip_count = -1;
-       revs->max_count = -1;
-       revs->max_parents = -1;
-       revs->expand_tabs_in_log = -1;
-
-       revs->commit_format = CMIT_FMT_DEFAULT;
-       revs->expand_tabs_in_log_default = 8;
 
        grep_init(&revs->grep_filter, revs->repo);
        revs->grep_filter.status_only = 1;
index afe1b77985faf5caeea507dad6639441013587c2..8493a3f3b939eb40e1e73b4a0a9ee5728c1aafe7 100644 (file)
@@ -357,7 +357,23 @@ struct rev_info {
  * called before release_revisions() the "struct rev_info" can be left
  * uninitialized.
  */
-#define REV_INFO_INIT { 0 }
+#define REV_INFO_INIT { \
+       .abbrev = DEFAULT_ABBREV, \
+       .simplify_history = 1, \
+       .pruning.flags.recursive = 1, \
+       .pruning.flags.quick = 1, \
+       .sort_order = REV_SORT_IN_GRAPH_ORDER, \
+       .dense = 1, \
+       .max_age = -1, \
+       .max_age_as_filter = -1, \
+       .min_age = -1, \
+       .skip_count = -1, \
+       .max_count = -1, \
+       .max_parents = -1, \
+       .expand_tabs_in_log = -1, \
+       .commit_format = CMIT_FMT_DEFAULT, \
+       .expand_tabs_in_log_default = 8, \
+}
 
 /**
  * Initialize a rev_info structure with default values. The third parameter may
index c772acd74319698c57db3a6f3399f2922840af13..48b9ba6d6f077e5729f3efc4c1a88ab2be508c36 100644 (file)
@@ -1004,41 +1004,6 @@ int run_command(struct child_process *cmd)
        return finish_command(cmd);
 }
 
-int run_command_v_opt(const char **argv, int opt)
-{
-       return run_command_v_opt_cd_env(argv, opt, NULL, NULL);
-}
-
-int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class)
-{
-       return run_command_v_opt_cd_env_tr2(argv, opt, NULL, NULL, tr2_class);
-}
-
-int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env)
-{
-       return run_command_v_opt_cd_env_tr2(argv, opt, dir, env, NULL);
-}
-
-int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,
-                                const char *const *env, const char *tr2_class)
-{
-       struct child_process cmd = CHILD_PROCESS_INIT;
-       strvec_pushv(&cmd.args, argv);
-       cmd.no_stdin = opt & RUN_COMMAND_NO_STDIN ? 1 : 0;
-       cmd.git_cmd = opt & RUN_GIT_CMD ? 1 : 0;
-       cmd.stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;
-       cmd.silent_exec_failure = opt & RUN_SILENT_EXEC_FAILURE ? 1 : 0;
-       cmd.use_shell = opt & RUN_USING_SHELL ? 1 : 0;
-       cmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;
-       cmd.wait_after_clean = opt & RUN_WAIT_AFTER_CLEAN ? 1 : 0;
-       cmd.close_object_store = opt & RUN_CLOSE_OBJECT_STORE ? 1 : 0;
-       cmd.dir = dir;
-       if (env)
-               strvec_pushv(&cmd.env, (const char **)env);
-       cmd.trace2_child_class = tr2_class;
-       return run_command(&cmd);
-}
-
 #ifndef NO_PTHREADS
 static pthread_t main_thread;
 static int main_thread_set;
index e3e1ea01ad9dde63eaa87d0f102955c36000d2f0..072db56a4dff15996889bded735dd02f2bc52e73 100644 (file)
@@ -150,9 +150,7 @@ struct child_process {
 }
 
 /**
- * The functions: child_process_init, start_command, finish_command,
- * run_command, run_command_v_opt, run_command_v_opt_cd_env, child_process_clear
- * do the following:
+ * The functions: start_command, finish_command, run_command do the following:
  *
  * - If a system call failed, errno is set and -1 is returned. A diagnostic
  *   is printed.
@@ -224,36 +222,6 @@ int run_command(struct child_process *);
  */
 int run_auto_maintenance(int quiet);
 
-#define RUN_COMMAND_NO_STDIN           (1<<0)
-#define RUN_GIT_CMD                    (1<<1)
-#define RUN_COMMAND_STDOUT_TO_STDERR   (1<<2)
-#define RUN_SILENT_EXEC_FAILURE                (1<<3)
-#define RUN_USING_SHELL                        (1<<4)
-#define RUN_CLEAN_ON_EXIT              (1<<5)
-#define RUN_WAIT_AFTER_CLEAN           (1<<6)
-#define RUN_CLOSE_OBJECT_STORE         (1<<7)
-
-/**
- * Convenience functions that encapsulate a sequence of
- * start_command() followed by finish_command(). The argument argv
- * specifies the program and its arguments. The argument opt is zero
- * or more of the flags `RUN_COMMAND_NO_STDIN`, `RUN_GIT_CMD`,
- * `RUN_COMMAND_STDOUT_TO_STDERR`, or `RUN_SILENT_EXEC_FAILURE`
- * that correspond to the members .no_stdin, .git_cmd,
- * .stdout_to_stderr, .silent_exec_failure of `struct child_process`.
- * The argument dir corresponds the member .dir. The argument env
- * corresponds to the member .env.
- */
-int run_command_v_opt(const char **argv, int opt);
-int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class);
-/*
- * env (the environment) is to be formatted like environ: "VAR=VALUE".
- * To unset an environment variable use just "VAR".
- */
-int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env);
-int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,
-                                const char *const *env, const char *tr2_class);
-
 /**
  * Execute the given command, sending "in" to its stdin, and capturing its
  * stdout and stderr in the "out" and "err" strbufs. Any of the three may
index 7f4bdb6c153500c11801dac5cb3b237dc846f038..6c52243cdf1f68bb3716891bfabbdf85fe26fcd4 100644 (file)
--- a/scalar.c
+++ b/scalar.c
@@ -69,21 +69,18 @@ static void setup_enlistment_directory(int argc, const char **argv,
 
 static int run_git(const char *arg, ...)
 {
-       struct strvec argv = STRVEC_INIT;
+       struct child_process cmd = CHILD_PROCESS_INIT;
        va_list args;
        const char *p;
-       int res;
 
        va_start(args, arg);
-       strvec_push(&argv, arg);
+       strvec_push(&cmd.args, arg);
        while ((p = va_arg(args, const char *)))
-               strvec_push(&argv, p);
+               strvec_push(&cmd.args, p);
        va_end(args);
 
-       res = run_command_v_opt(argv.v, RUN_GIT_CMD);
-
-       strvec_clear(&argv);
-       return res;
+       cmd.git_cmd = 1;
+       return run_command(&cmd);
 }
 
 struct scalar_config {
index e658df7e8ff66df9edc1e7d909f4688f5eea48c1..a46f3ad2e33761c310c2d01e0dd68069559044bd 100644 (file)
@@ -3183,18 +3183,15 @@ static int rollback_is_safe(void)
 
 static int reset_merge(const struct object_id *oid)
 {
-       int ret;
-       struct strvec argv = STRVEC_INIT;
+       struct child_process cmd = CHILD_PROCESS_INIT;
 
-       strvec_pushl(&argv, "reset", "--merge", NULL);
+       cmd.git_cmd = 1;
+       strvec_pushl(&cmd.args, "reset", "--merge", NULL);
 
        if (!is_null_oid(oid))
-               strvec_push(&argv, oid_to_hex(oid));
+               strvec_push(&cmd.args, oid_to_hex(oid));
 
-       ret = run_command_v_opt(argv.v, RUN_GIT_CMD);
-       strvec_clear(&argv);
-
-       return ret;
+       return run_command(&cmd);
 }
 
 static int rollback_single_pick(struct repository *r)
@@ -3558,12 +3555,13 @@ static int error_failed_squash(struct repository *r,
 
 static int do_exec(struct repository *r, const char *command_line)
 {
-       const char *child_argv[] = { NULL, NULL };
+       struct child_process cmd = CHILD_PROCESS_INIT;
        int dirty, status;
 
        fprintf(stderr, _("Executing: %s\n"), command_line);
-       child_argv[0] = command_line;
-       status = run_command_v_opt(child_argv, RUN_USING_SHELL);
+       cmd.use_shell = 1;
+       strvec_push(&cmd.args, command_line);
+       status = run_command(&cmd);
 
        /* force re-reading of the cache */
        if (discard_index(r->index) < 0 || repo_read_index(r) < 0)
@@ -3750,6 +3748,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)) {
@@ -4130,11 +4129,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);
@@ -4867,14 +4869,14 @@ cleanup_head_ref:
 
 static int continue_single_pick(struct repository *r, struct replay_opts *opts)
 {
-       struct strvec argv = STRVEC_INIT;
-       int ret;
+       struct child_process cmd = CHILD_PROCESS_INIT;
 
        if (!refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") &&
            !refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD"))
                return error(_("no cherry-pick or revert in progress"));
 
-       strvec_push(&argv, "commit");
+       cmd.git_cmd = 1;
+       strvec_push(&cmd.args, "commit");
 
        /*
         * continue_single_pick() handles the case of recovering from a
@@ -4887,11 +4889,9 @@ static int continue_single_pick(struct repository *r, struct replay_opts *opts)
                 * Include --cleanup=strip as well because we don't want the
                 * "# Conflicts:" messages.
                 */
-               strvec_pushl(&argv, "--no-edit", "--cleanup=strip", NULL);
+               strvec_pushl(&cmd.args, "--no-edit", "--cleanup=strip", NULL);
 
-       ret = run_command_v_opt(argv.v, RUN_GIT_CMD);
-       strvec_clear(&argv);
-       return ret;
+       return run_command(&cmd);
 }
 
 static int commit_staged_changes(struct repository *r,
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
diff --git a/shell.c b/shell.c
index 7ff4109db7058b5677be2fa8775f699b54e4d4f1..af0d7c734f8347082ab094cf1fc8898cbf26b64d 100644 (file)
--- a/shell.c
+++ b/shell.c
@@ -52,21 +52,24 @@ static void cd_to_homedir(void)
 static void run_shell(void)
 {
        int done = 0;
-       static const char *help_argv[] = { HELP_COMMAND, NULL };
+       struct child_process help_cmd = CHILD_PROCESS_INIT;
 
        if (!access(NOLOGIN_COMMAND, F_OK)) {
                /* Interactive login disabled. */
-               const char *argv[] = { NOLOGIN_COMMAND, NULL };
+               struct child_process nologin_cmd = CHILD_PROCESS_INIT;
                int status;
 
-               status = run_command_v_opt(argv, 0);
+               strvec_push(&nologin_cmd.args, NOLOGIN_COMMAND);
+               status = run_command(&nologin_cmd);
                if (status < 0)
                        exit(127);
                exit(status);
        }
 
        /* Print help if enabled */
-       run_command_v_opt(help_argv, RUN_SILENT_EXEC_FAILURE);
+       help_cmd.silent_exec_failure = 1;
+       strvec_push(&help_cmd.args, HELP_COMMAND);
+       run_command(&help_cmd);
 
        do {
                const char *prog;
@@ -125,9 +128,13 @@ static void run_shell(void)
                           !strcmp(prog, "exit") || !strcmp(prog, "bye")) {
                        done = 1;
                } else if (is_valid_cmd_name(prog)) {
+                       struct child_process cmd = CHILD_PROCESS_INIT;
+
                        full_cmd = make_cmd(prog);
                        argv[0] = full_cmd;
-                       code = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE);
+                       cmd.silent_exec_failure = 1;
+                       strvec_pushv(&cmd.args, argv);
+                       code = run_command(&cmd);
                        if (code == -1 && errno == ENOENT) {
                                fprintf(stderr, "unrecognized command '%s'\n", prog);
                        }
index e4a54ce19433dd0cdd553996762af642ace0d9b8..8c269dab803fa9a06a0e31ff42b6a20ec1983dbc 100644 (file)
@@ -493,24 +493,42 @@ void clear_skip_worktree_from_present_files(struct index_state *istate)
        int dir_found = 1;
 
        int i;
+       int path_count[2] = {0, 0};
+       int restarted = 0;
 
        if (!core_apply_sparse_checkout ||
            sparse_expect_files_outside_of_patterns)
                return;
 
+       trace2_region_enter("index", "clear_skip_worktree_from_present_files",
+                           istate->repo);
 restart:
        for (i = 0; i < istate->cache_nr; i++) {
                struct cache_entry *ce = istate->cache[i];
 
-               if (ce_skip_worktree(ce) &&
-                   path_found(ce->name, &last_dirname, &dir_len, &dir_found)) {
-                       if (S_ISSPARSEDIR(ce->ce_mode)) {
-                               ensure_full_index(istate);
-                               goto restart;
+               if (ce_skip_worktree(ce)) {
+                       path_count[restarted]++;
+                       if (path_found(ce->name, &last_dirname, &dir_len, &dir_found)) {
+                               if (S_ISSPARSEDIR(ce->ce_mode)) {
+                                       if (restarted)
+                                               BUG("ensure-full-index did not fully flatten?");
+                                       ensure_full_index(istate);
+                                       restarted = 1;
+                                       goto restart;
+                               }
+                               ce->ce_flags &= ~CE_SKIP_WORKTREE;
                        }
-                       ce->ce_flags &= ~CE_SKIP_WORKTREE;
                }
        }
+
+       if (path_count[0])
+               trace2_data_intmax("index", istate->repo,
+                                  "sparse_path_count", path_count[0]);
+       if (restarted)
+               trace2_data_intmax("index", istate->repo,
+                                  "sparse_path_count_full", path_count[1]);
+       trace2_region_leave("index", "clear_skip_worktree_from_present_files",
+                           istate->repo);
 }
 
 /*
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
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 12beee99ad2f4e70e804b21895522d6d362929d2..2e576bcc11e9a62dc01206d46e01574ad47193d4 100644 (file)
@@ -8,7 +8,7 @@ int cmd_main(int argc, const char **argv)
        struct strbuf buf = STRBUF_INIT;
        FILE *f;
        int i;
-       const char *child_argv[] = { NULL, NULL };
+       struct child_process cmd = CHILD_PROCESS_INIT;
 
        /* First, print all parameters into $TRASH_DIRECTORY/ssh-output */
        if (!trash_directory)
@@ -25,6 +25,7 @@ int cmd_main(int argc, const char **argv)
        /* Now, evaluate the *last* parameter */
        if (argc < 2)
                return 0;
-       child_argv[0] = argv[argc - 1];
-       return run_command_v_opt(child_argv, RUN_USING_SHELL);
+       cmd.use_shell = 1;
+       strvec_push(&cmd.args, argv[argc - 1]);
+       return run_command(&cmd);
 }
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 1b092c60714f977f52f23ed62414c560630cd848..f374c21ec32ecc049bace6821d5630e103b8937c 100644 (file)
@@ -132,6 +132,7 @@ static int ut_003error(int argc, const char **argv)
  */
 static int ut_004child(int argc, const char **argv)
 {
+       struct child_process cmd = CHILD_PROCESS_INIT;
        int result;
 
        /*
@@ -141,7 +142,8 @@ static int ut_004child(int argc, const char **argv)
        if (!argc)
                return 0;
 
-       result = run_command_v_opt(argv, 0);
+       strvec_pushv(&cmd.args, argv);
+       result = run_command(&cmd);
        exit(result);
 }
 
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 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 eaa0b22ece4ff3ce7d79faed2edf8ab829c97051..d473048138466322e505f4b3b249e89f19101b0b 100755 (executable)
@@ -342,6 +342,13 @@ test_expect_success 'only enabled filters are available remotely' '
        test_cmp_bin remote.bar config.bar
 '
 
+test_expect_success 'invalid filter is reported only once' '
+       test_must_fail git -c tar.invalid.command= archive --format=invalid \
+               HEAD >out 2>err &&
+       test_must_be_empty out &&
+       test_line_count = 1 err
+'
+
 test_expect_success 'git archive --format=tgz' '
        git archive --format=tgz HEAD >j.tgz
 '
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 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 dcaab7265f5c75a8be444db3e2d1766732192f8d..fa38b874416220d3d3c39ee48a99ad53b94dc2df 100755 (executable)
@@ -1406,4 +1406,44 @@ test_expect_success 'for-each-ref reports broken tags' '
                refs/tags/broken-tag-*
 '
 
+test_expect_success 'set up tag with signature and no blank lines' '
+       git tag -F - fake-sig-no-blanks <<-\EOF
+       this is the subject
+       -----BEGIN PGP SIGNATURE-----
+       not a real signature, but we just care about the
+       subject/body parsing. It is important here that
+       there are no blank lines in the signature.
+       -----END PGP SIGNATURE-----
+       EOF
+'
+
+test_atom refs/tags/fake-sig-no-blanks contents:subject 'this is the subject'
+test_atom refs/tags/fake-sig-no-blanks contents:body ''
+test_atom refs/tags/fake-sig-no-blanks contents:signature "$sig"
+
+test_expect_success 'set up tag with CRLF signature' '
+       append_cr <<-\EOF |
+       this is the subject
+       -----BEGIN PGP SIGNATURE-----
+
+       not a real signature, but we just care about
+       the subject/body parsing. It is important here
+       that there is a blank line separating this
+       from the signature header.
+       -----END PGP SIGNATURE-----
+       EOF
+       git tag -F - --cleanup=verbatim fake-sig-crlf
+'
+
+test_atom refs/tags/fake-sig-crlf contents:subject 'this is the subject'
+test_atom refs/tags/fake-sig-crlf contents:body ''
+
+# CRLF is retained in the signature, so we have to pass our expected value
+# through append_cr. But test_atom requires a shell string, which means command
+# substitution, and the shell will strip trailing newlines from the output of
+# the substitution. Hack around it by adding and then removing a dummy line.
+sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)"
+sig_crlf=${sig_crlf%dummy}
+test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf"
+
 test_done
index cd6c53360d206bef95cac3af0188c5eb3052e76c..d9acb639512ae6bdf30c8bdd4a0b4d498541dd84 100755 (executable)
@@ -202,6 +202,102 @@ test_expect_success 'one of gc.reflogExpire{Unreachable,}=never does not skip "e
        grep -E "^trace: (built-in|exec|run_command): git reflog expire --" trace.out
 '
 
+prepare_cruft_history () {
+       test_commit base &&
+
+       test_commit --no-tag foo &&
+       test_commit --no-tag bar &&
+       git reset HEAD^^
+}
+
+assert_cruft_packs () {
+       find .git/objects/pack -name "*.mtimes" >mtimes &&
+       sed -e 's/\.mtimes$/\.pack/g' mtimes >packs &&
+
+       test_file_not_empty packs &&
+       while read pack
+       do
+               test_path_is_file "$pack" || return 1
+       done <packs
+}
+
+assert_no_cruft_packs () {
+       find .git/objects/pack -name "*.mtimes" >mtimes &&
+       test_must_be_empty mtimes
+}
+
+test_expect_success 'gc --cruft generates a cruft pack' '
+       test_when_finished "rm -fr crufts" &&
+       git init crufts &&
+       (
+               cd crufts &&
+
+               prepare_cruft_history &&
+               git gc --cruft &&
+               assert_cruft_packs
+       )
+'
+
+test_expect_success 'gc.cruftPacks=true generates a cruft pack' '
+       test_when_finished "rm -fr crufts" &&
+       git init crufts &&
+       (
+               cd crufts &&
+
+               prepare_cruft_history &&
+               git -c gc.cruftPacks=true gc &&
+               assert_cruft_packs
+       )
+'
+
+test_expect_success 'feature.experimental=true generates a cruft pack' '
+       git init crufts &&
+       test_when_finished "rm -fr crufts" &&
+       (
+               cd crufts &&
+
+               prepare_cruft_history &&
+               git -c feature.experimental=true gc &&
+               assert_cruft_packs
+       )
+'
+
+test_expect_success 'feature.experimental=false allows explicit cruft packs' '
+       git init crufts &&
+       test_when_finished "rm -fr crufts" &&
+       (
+               cd crufts &&
+
+               prepare_cruft_history &&
+               git -c gc.cruftPacks=true -c feature.experimental=false gc &&
+               assert_cruft_packs
+       )
+'
+
+test_expect_success 'feature.experimental=true can be overridden' '
+       git init crufts &&
+       test_when_finished "rm -fr crufts" &&
+       (
+               cd crufts &&
+
+               prepare_cruft_history &&
+               git -c feature.expiremental=true -c gc.cruftPacks=false gc &&
+               assert_no_cruft_packs
+       )
+'
+
+test_expect_success 'feature.experimental=false avoids cruft packs by default' '
+       git init crufts &&
+       test_when_finished "rm -fr crufts" &&
+       (
+               cd crufts &&
+
+               prepare_cruft_history &&
+               git -c feature.experimental=false gc &&
+               assert_no_cruft_packs
+       )
+'
+
 run_and_wait_for_auto_gc () {
        # We read stdout from gc for the side effect of waiting until the
        # background gc process exits, closing its fd 9.  Furthermore, the
index 8c37bceb336344f9b10878faf9cabf933dcb8ea2..d72cef88264ec3dff713b12253df7a6dd5a4724e 100755 (executable)
@@ -60,8 +60,8 @@ test_expect_success 'checking the commit' '
 
 test_expect_success 'mv --dry-run does not move file' '
        git mv -n path0/COPYING MOVED &&
-       test -f path0/COPYING &&
-       test ! -f MOVED
+       test_path_is_file path0/COPYING &&
+       test_path_is_missing MOVED
 '
 
 test_expect_success 'checking -k on non-existing file' '
@@ -71,25 +71,25 @@ test_expect_success 'checking -k on non-existing file' '
 test_expect_success 'checking -k on untracked file' '
        >untracked1 &&
        git mv -k untracked1 path0 &&
-       test -f untracked1 &&
-       test ! -f path0/untracked1
+       test_path_is_file untracked1 &&
+       test_path_is_missing path0/untracked1
 '
 
 test_expect_success 'checking -k on multiple untracked files' '
        >untracked2 &&
        git mv -k untracked1 untracked2 path0 &&
-       test -f untracked1 &&
-       test -f untracked2 &&
-       test ! -f path0/untracked1 &&
-       test ! -f path0/untracked2
+       test_path_is_file untracked1 &&
+       test_path_is_file untracked2 &&
+       test_path_is_missing path0/untracked1 &&
+       test_path_is_missing path0/untracked2
 '
 
 test_expect_success 'checking -f on untracked file with existing target' '
        >path0/untracked1 &&
        test_must_fail git mv -f untracked1 path0 &&
-       test ! -f .git/index.lock &&
-       test -f untracked1 &&
-       test -f path0/untracked1
+       test_path_is_missing .git/index.lock &&
+       test_path_is_file untracked1 &&
+       test_path_is_file path0/untracked1
 '
 
 # clean up the mess in case bad things happen
@@ -215,8 +215,8 @@ test_expect_success 'absolute pathname' '
                git add sub/file &&
 
                git mv sub "$(pwd)/in" &&
-               ! test -d sub &&
-               test -d in &&
+               test_path_is_missing sub &&
+               test_path_is_dir in &&
                git ls-files --error-unmatch in/file
        )
 '
@@ -234,8 +234,8 @@ test_expect_success 'absolute pathname outside should fail' '
                git add sub/file &&
 
                test_must_fail git mv sub "$out/out" &&
-               test -d sub &&
-               ! test -d ../in &&
+               test_path_is_dir sub &&
+               test_path_is_missing ../in &&
                git ls-files --error-unmatch sub/file
        )
 '
@@ -295,8 +295,8 @@ test_expect_success 'git mv should overwrite symlink to a file' '
        git add moved &&
        test_must_fail git mv moved symlink &&
        git mv -f moved symlink &&
-       ! test -e moved &&
-       test -f symlink &&
+       test_path_is_missing moved &&
+       test_path_is_file symlink &&
        test "$(cat symlink)" = 1 &&
        git update-index --refresh &&
        git diff-files --quiet
@@ -312,13 +312,13 @@ test_expect_success 'git mv should overwrite file with a symlink' '
        git add moved &&
        test_must_fail git mv symlink moved &&
        git mv -f symlink moved &&
-       ! test -e symlink &&
+       test_path_is_missing symlink &&
        git update-index --refresh &&
        git diff-files --quiet
 '
 
 test_expect_success SYMLINKS 'check moved symlink' '
-       test -h moved
+       test_path_is_symlink moved
 '
 
 rm -f moved symlink
@@ -352,7 +352,7 @@ test_expect_success 'git mv moves a submodule with a .git directory and no .gitm
        ) &&
        mkdir mod &&
        git mv sub mod/sub &&
-       ! test -e sub &&
+       test_path_is_missing sub &&
        test "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" &&
        git -C mod/sub status &&
        git update-index --refresh &&
@@ -372,7 +372,7 @@ test_expect_success 'git mv moves a submodule with a .git directory and .gitmodu
        ) &&
        mkdir mod &&
        git mv sub mod/sub &&
-       ! test -e sub &&
+       test_path_is_missing sub &&
        test "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" &&
        git -C mod/sub status &&
        echo mod/sub >expected &&
@@ -389,7 +389,7 @@ test_expect_success 'git mv moves a submodule with gitfile' '
        entry="$(git ls-files --stage sub | cut -f 1)" &&
        mkdir mod &&
        git -C mod mv ../sub/ . &&
-       ! test -e sub &&
+       test_path_is_missing sub &&
        test "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" &&
        git -C mod/sub status &&
        echo mod/sub >expected &&
@@ -408,7 +408,7 @@ test_expect_success 'mv does not complain when no .gitmodules file is found' '
        mkdir mod &&
        git mv sub mod/sub 2>actual.err &&
        test_must_be_empty actual.err &&
-       ! test -e sub &&
+       test_path_is_missing sub &&
        test "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" &&
        git -C mod/sub status &&
        git update-index --refresh &&
@@ -423,13 +423,13 @@ test_expect_success 'mv will error out on a modified .gitmodules file unless sta
        entry="$(git ls-files --stage sub | cut -f 1)" &&
        mkdir mod &&
        test_must_fail git mv sub mod/sub 2>actual.err &&
-       test -s actual.err &&
-       test -e sub &&
+       test_file_not_empty actual.err &&
+       test_path_exists sub &&
        git diff-files --quiet -- sub &&
        git add .gitmodules &&
        git mv sub mod/sub 2>actual.err &&
        test_must_be_empty actual.err &&
-       ! test -e sub &&
+       test_path_is_missing sub &&
        test "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" &&
        git -C mod/sub status &&
        git update-index --refresh &&
@@ -447,7 +447,7 @@ test_expect_success 'mv issues a warning when section is not found in .gitmodule
        mkdir mod &&
        git mv sub mod/sub 2>actual.err &&
        test_cmp expect.err actual.err &&
-       ! test -e sub &&
+       test_path_is_missing sub &&
        test "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" &&
        git -C mod/sub status &&
        git update-index --refresh &&
@@ -460,7 +460,7 @@ test_expect_success 'mv --dry-run does not touch the submodule or .gitmodules' '
        git submodule update &&
        mkdir mod &&
        git mv -n sub mod/sub 2>actual.err &&
-       test -f sub/.git &&
+       test_path_is_file sub/.git &&
        git diff-index --exit-code HEAD &&
        git update-index --refresh &&
        git diff-files --quiet -- sub .gitmodules
@@ -474,10 +474,10 @@ test_expect_success 'checking out a commit before submodule moved needs manual u
        git status -s sub2 >actual &&
        echo "?? sub2/" >expected &&
        test_cmp expected actual &&
-       ! test -f sub/.git &&
-       test -f sub2/.git &&
+       test_path_is_missing sub/.git &&
+       test_path_is_file sub2/.git &&
        git submodule update &&
-       test -f sub/.git &&
+       test_path_is_file sub/.git &&
        rm -rf sub2 &&
        git diff-index --exit-code HEAD &&
        git update-index --refresh &&
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 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 29d914a12ba170c30ba64c627a508a04dac239d4..796093a7b32f983a558d8ef6ea2f406c8edd70da 100644 (file)
@@ -921,10 +921,6 @@ test_path_is_missing () {
        then
                echo "Path exists:"
                ls -ld "$1"
-               if test $# -ge 1
-               then
-                       echo "$*"
-               fi
                false
        fi
 }
index 76efc7edee5be4a9197a242f526c74b05f49802a..237d96b66050c82340af3c18b531bd5c877c15ab 100644 (file)
  *
  * Example:
  *
+ *     struct child_process child = CHILD_PROCESS_INIT;
  *     struct tmp_objdir *t = tmp_objdir_create("incoming");
- *     if (!run_command_v_opt_cd_env(cmd, 0, NULL, tmp_objdir_env(t)) &&
- *         !tmp_objdir_migrate(t))
+ *     strvec_push(&child.args, cmd);
+ *     strvec_pushv(&child.env, tmp_objdir_env(t));
+ *     if (!run_command(&child)) && !tmp_objdir_migrate(t))
  *             printf("success!\n");
  *     else
  *             die("failed...tmp_objdir will clean up for us");
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;