Subject: [PATCH v2 0/9] Sparse index: integrate with 'clean', 'checkout-index', 'update-index'

From: "Victoria Dye via GitGitGadget" <[email-masked]>
Date: Tue, 11 Jan 2022 18:04:57 +0000

This series continues the work to integrate commands with the sparse index,
adding integrations with 'git clean', 'git checkout-index', and 'git
update-index'. These three commands, while useful in their own right, are
updated mainly because they're used in 'git stash'. A future series will
integrate sparse index with 'stash' directly, but its subcommands must be
integrated to avoid the performance cost of each one expanding and
collapsing the index.

The series is broken up into 4 parts:

 * Patches 1-2 are minor fixups to the 'git reset' sparse index integration
   in response to discussion [1] that came after the series was ready for
   merge to 'next'.
 * Patch 3 integrates 'git clean' with the sparse index.
 * Patches 4-6 integrate 'git checkout-index' with the sparse index and
   introduce a new '--ignore-skip-worktree-bits' option.
   * This involves changing the behavior of 'checkout-index' to respect
     'skip-worktree' by default (i.e., it won't check out 'skip-worktree'
     files). The '--ignore-skip-worktree-bits' option can be specified to
     force checkout of 'skip-worktree' files, if desired.
 * Patches 7-9 integrate 'git update-index' with the sparse index.
   * Note that, although this integrates the sparse index with
     '--cacheinfo', sparse directories still cannot be updated using that
     option (see the prior discussion [2] for more details on why)

Changes since V1
================

 * Changed 'checkout-index' to fail by default when given filenames of files
   with 'skip-worktree' enabled
   * These files can still be forcibly checked-out by using the
     '--ignore-skip-worktree-bits' option
   * Added/updated corresponding t1092 tests
 * Updated t1092 'update-index' tests
   * Mentioned where/why 'skip-worktree' files were manually created on-disk
     for testing purposes
   * Provided explanation as to what '--remove' does, and how it relates to
     '--ignore-skip-worktree-entries'; restructured corresponding test
   * Fixed typo 'update-indexe' -> 'update-index'
   * Removed unused 'edit-contents'
   * Changed '--again' test to not use '--remove' to avoid confusion over
     how/why it updates 'skip-worktree' entries
   * Added "set skip-worktree" step to '--cacheinfo' test to illustrate how
     it could be used to add a new outside-of-cone file and remain generally
     compliant with a sparse-checkout definition
   * Added '--cacheinfo' test to "ensure not expanded"
   * Moved t1092 test 'sparse index is not expanded: update-index' to avoid
     merge conflict
 * Updated p2000 test for 'update-index': added file argument
   * Without any file arguments, 'update-index' was effectively a no-op
 * Clarified reasoning behind changing/not changing behavior of update-index
   in sparse-checkouts

Thanks!

 * Victoria

[1]
https://lore.kernel.org/git/CABPp-BG0iDHf268UAnRyA=0y0T69YTc+bLMdxCmSbrL8s=[email-masked]/

[2]
https://lore.kernel.org/git/[email-masked]/

Victoria Dye (9):
  reset: fix validation in sparse index test
  reset: reorder wildcard pathspec conditions
  clean: integrate with sparse index
  checkout-index: expand sparse checkout compatibility tests
  checkout-index: add --ignore-skip-worktree-bits option
  checkout-index: integrate with sparse index
  update-index: add tests for sparse-checkout compatibility
  update-index: integrate with sparse index
  update-index: reduce scope of index expansion in do_reupdate

 Documentation/git-checkout-index.txt     |  10 +-
 builtin/checkout-index.c                 |  41 +++-
 builtin/clean.c                          |   3 +
 builtin/reset.c                          |  12 +-
 builtin/update-index.c                   |  17 +-
 read-cache.c                             |  10 +-
 t/perf/p2000-sparse-operations.sh        |   2 +
 t/t1092-sparse-checkout-compatibility.sh | 282 ++++++++++++++++++++++-
 8 files changed, 360 insertions(+), 17 deletions(-)

base-commit: dcc0cd074f0c639a0df20461a301af6d45bd582e
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1109%2Fvdye%2Fsparse%2Fupdate-index_checkout-index_clean-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1109/vdye/sparse/update-index_checkout-index_clean-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/1109

Range-diff vs v1:

  1:  eefb6ab4c61 =  1:  eefb6ab4c61 reset: fix validation in sparse index test
  2:  0194d894c2f =  2:  0194d894c2f reset: reorder wildcard pathspec conditions
  3:  52aec13d18e =  3:  52aec13d18e clean: integrate with sparse index
  4:  e6a8671f6be !  4:  d964507fdcc checkout-index: expand sparse checkout compatibility tests
     @@ Commit message
      
          Add tests to cover `checkout-index`, with a focus on cases interesting in a
          sparse checkout (e.g., files specified outside sparse checkout definition).
     -    New tests are intended to serve as a baseline for expected behavior and
     -    performance when integrating `checkout-index` with the sparse index.
     +
     +    New tests are intended to serve as a baseline for existing and/or expected
     +    behavior and performance when integrating `checkout-index` with the sparse
     +    index. Note that the test 'checkout-index --all' is marked as
     +    'test_expect_failure', indicating that `update-index --all` will be modified
     +    in a subsequent patch to behave as the test expects.
      
          Signed-off-by: Victoria Dye <[email-masked]>
      
  5:  ec9a751e8dc !  5:  601888606d1 checkout-index: add --ignore-skip-worktree-bits option
     @@ Metadata
       ## Commit message ##
          checkout-index: add --ignore-skip-worktree-bits option
      
     -    Update `checkout-index --all` to no longer refresh files that have the
     -    `skip-worktree` bit set. The newly-added `--ignore-skip-worktree-bits`
     -    option, when used with `--all`, maintains the old behavior and checks out
     -    all files regardless of `skip-worktree`.
     +    Update `checkout-index` to no longer refresh files that have the
     +    `skip-worktree` bit set, exiting with an error if `skip-worktree` filenames
     +    are directly provided to `checkout-index`. The newly-added
     +    `--ignore-skip-worktree-bits` option provides a mechanism to replicate the
     +    old behavior, checking out *all* files specified (even those with
     +    `skip-worktree` enabled).
      
          The ability to toggle whether files should be checked-out based on
          `skip-worktree` already exists in `git checkout` and `git restore` (both of
     -    which have an `--ignore-skip-worktree-bits` option). Adding the option to
     -    `checkout-index` (and changing the corresponding default behavior to respect
     -    the `skip-worktree` bit) is especially helpful for sparse-checkout: it
     -    prevents inadvertent creation of *all* files outside the sparse definition
     -    on disk and eliminates the need to expand a sparse index by default when
     -    using the `--all` option.
     +    which have an `--ignore-skip-worktree-bits` option). The change to, by
     +    default, ignore `skip-worktree` files is especially helpful for
     +    sparse-checkout; it prevents inadvertent creation of files outside the
     +    sparse definition on disk and eliminates the need to expand a sparse index
     +    when using the `--all` option.
      
          Internal usage of `checkout-index` in `git stash` and `git filter-branch` do
          not make explicit use of files with `skip-worktree` enabled, so
          `--ignore-skip-worktree-bits` is not added to them.
      
     +    Helped-by: Elijah Newren <[email-masked]>
          Signed-off-by: Victoria Dye <[email-masked]>
      
       ## Documentation/git-checkout-index.txt ##
     @@ Documentation/git-checkout-index.txt: OPTIONS
       
      +--ignore-skip-worktree-bits::
      +	Check out all files, including those with the skip-worktree bit
     -+	set. Note: may only be used with `--all`; skip-worktree is
     -+	ignored when explicit filenames are specified.
     ++	set.
      +
       --stdin::
       	Instead of taking list of paths from the command line,
     @@ builtin/checkout-index.c
       #include "lockfile.h"
       #include "quote.h"
       #include "cache-tree.h"
     +@@
     + #define CHECKOUT_ALL 4
     + static int nul_term_line;
     + static int checkout_stage; /* default to checkout stage0 */
     ++static int ignore_skip_worktree; /* default to 0 */
     + static int to_tempfile;
     + static char topath[4][TEMPORARY_FILENAME_LENGTH + 1];
     + 
      @@ builtin/checkout-index.c: static int checkout_file(const char *name, const char *prefix)
     - 	return -1;
     - }
     + 	int namelen = strlen(name);
     + 	int pos = cache_name_pos(name, namelen);
     + 	int has_same_name = 0;
     ++	int is_skipped = 1;
     + 	int did_checkout = 0;
     + 	int errs = 0;
       
     --static int checkout_all(const char *prefix, int prefix_length)
     -+static int checkout_all(const char *prefix, int prefix_length, int ignore_skip_worktree)
     - {
     - 	int i, errs = 0;
     - 	struct cache_entry *last_ce = NULL;
     +@@ builtin/checkout-index.c: static int checkout_file(const char *name, const char *prefix)
     + 			break;
     + 		has_same_name = 1;
     + 		pos++;
     ++		if (!ignore_skip_worktree && ce_skip_worktree(ce))
     ++			break;
     ++		is_skipped = 0;
     + 		if (ce_stage(ce) != checkout_stage
     + 		    && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
     + 			continue;
     +@@ builtin/checkout-index.c: static int checkout_file(const char *name, const char *prefix)
     + 		fprintf(stderr, "git checkout-index: %s ", name);
     + 		if (!has_same_name)
     + 			fprintf(stderr, "is not in the cache");
     ++		else if (is_skipped)
     ++			fprintf(stderr, "has skip-worktree enabled; "
     ++					"use '--ignore-skip-worktree-bits' to checkout");
     + 		else if (checkout_stage)
     + 			fprintf(stderr, "does not exist at stage %d",
     + 				checkout_stage);
      @@ builtin/checkout-index.c: static int checkout_all(const char *prefix, int prefix_length)
       	ensure_full_index(&the_index);
       	for (i = 0; i < active_nr ; i++) {
     @@ builtin/checkout-index.c: static int checkout_all(const char *prefix, int prefix
       		if (ce_stage(ce) != checkout_stage
       		    && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
       			continue;
     -@@ builtin/checkout-index.c: int cmd_checkout_index(int argc, const char **argv, const char *prefix)
     - 	int i;
     - 	struct lock_file lock_file = LOCK_INIT;
     - 	int all = 0;
     -+	int ignore_skip_worktree = 0;
     - 	int read_from_stdin = 0;
     - 	int prefix_length;
     - 	int force = 0, quiet = 0, not_new = 0;
      @@ builtin/checkout-index.c: int cmd_checkout_index(int argc, const char **argv, const char *prefix)
       	struct option builtin_checkout_index_options[] = {
       		OPT_BOOL('a', "all", &all,
     @@ builtin/checkout-index.c: int cmd_checkout_index(int argc, const char **argv, co
       		OPT__FORCE(&force, N_("force overwrite of existing files"), 0),
       		OPT__QUIET(&quiet,
       			N_("no warning for existing files and files not in index")),
     -@@ builtin/checkout-index.c: int cmd_checkout_index(int argc, const char **argv, const char *prefix)
     +
     + ## t/t1092-sparse-checkout-compatibility.sh ##
     +@@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'checkout-index inside sparse definition' '
     + test_expect_success 'checkout-index outside sparse definition' '
     + 	init_repos &&
       
     - 		if (all)
     - 			die("git checkout-index: don't mix '--all' and explicit filenames");
     -+		if (ignore_skip_worktree)
     -+			die("git checkout-index: don't mix '--ignore-skip-worktree-bits' and explicit filenames");
     - 		if (read_from_stdin)
     - 			die("git checkout-index: don't mix '--stdin' and explicit filenames");
     - 		p = prefix_path(prefix, prefix_length, arg);
     -@@ builtin/checkout-index.c: int cmd_checkout_index(int argc, const char **argv, const char *prefix)
     - 	}
     +-	# File does not exist on disk yet for sparse checkouts, so checkout-index
     +-	# succeeds without -f
     +-	test_sparse_match git checkout-index -- folder1/a &&
     ++	# Without --ignore-skip-worktree-bits, outside-of-cone files will trigger
     ++	# an error
     ++	test_sparse_match test_must_fail git checkout-index -- folder1/a &&
     ++	test_i18ngrep "folder1/a has skip-worktree enabled" sparse-checkout-err &&
     ++	test_path_is_missing folder1/a &&
     ++
     ++	# With --ignore-skip-worktree-bits, outside-of-cone files are checked out
     ++	test_sparse_match git checkout-index --ignore-skip-worktree-bits -- folder1/a &&
     + 	test_cmp sparse-checkout/folder1/a sparse-index/folder1/a &&
     + 	test_cmp sparse-checkout/folder1/a full-checkout/folder1/a &&
       
     - 	if (all)
     --		err |= checkout_all(prefix, prefix_length);
     -+		err |= checkout_all(prefix, prefix_length, ignore_skip_worktree);
     +@@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'checkout-index outside sparse definition' '
     + 	run_on_sparse mkdir -p folder1 &&
     + 	run_on_all cp ../new-a folder1/a &&
       
     - 	if (pc_workers > 1)
     - 		err |= run_parallel_checkout(&state, pc_workers, pc_threshold,
     -
     - ## t/t1092-sparse-checkout-compatibility.sh ##
     +-	test_all_match test_must_fail git checkout-index -- folder1/a &&
     +-	test_all_match git checkout-index -f -- folder1/a &&
     ++	test_all_match test_must_fail git checkout-index --ignore-skip-worktree-bits -- folder1/a &&
     ++	test_all_match git checkout-index -f --ignore-skip-worktree-bits -- folder1/a &&
     + 	test_cmp sparse-checkout/folder1/a sparse-index/folder1/a &&
     + 	test_cmp sparse-checkout/folder1/a full-checkout/folder1/a
     + '
      @@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'checkout-index with folders' '
       	test_all_match test_must_fail git checkout-index -f -- folder1/
       '
     @@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'checkout-index wi
      -	test_sparse_match test_path_is_missing folder1
      +	test_sparse_match test_path_is_missing folder1 &&
      +
     ++	# --ignore-skip-worktree-bits will cause `skip-worktree` files to be
     ++	# checked out, causing the outside-of-cone `folder1` to exist on-disk
      +	test_all_match git checkout-index --ignore-skip-worktree-bits --all &&
      +	test_all_match test_path_exists folder1
       '
  6:  18c00fc9dd3 !  6:  b4b9086dcdc checkout-index: integrate with sparse index
     @@ builtin/checkout-index.c: static int checkout_file(const char *name, const char
       	int pos = cache_name_pos(name, namelen);
       	int has_same_name = 0;
      +	int is_file = 0;
     + 	int is_skipped = 1;
       	int did_checkout = 0;
       	int errs = 0;
     - 
      @@ builtin/checkout-index.c: static int checkout_file(const char *name, const char *prefix)
       			break;
       		has_same_name = 1;
     @@ builtin/checkout-index.c: static int checkout_file(const char *name, const char
      +		if (S_ISSPARSEDIR(ce->ce_mode))
      +			break;
      +		is_file = 1;
     - 		if (ce_stage(ce) != checkout_stage
     - 		    && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
     - 			continue;
     + 		if (!ignore_skip_worktree && ce_skip_worktree(ce))
     + 			break;
     + 		is_skipped = 0;
      @@ builtin/checkout-index.c: static int checkout_file(const char *name, const char *prefix)
       		fprintf(stderr, "git checkout-index: %s ", name);
       		if (!has_same_name)
       			fprintf(stderr, "is not in the cache");
      +		else if (!is_file)
      +			fprintf(stderr, "is a sparse directory");
     - 		else if (checkout_stage)
     - 			fprintf(stderr, "does not exist at stage %d",
     - 				checkout_stage);
     -@@ builtin/checkout-index.c: static int checkout_all(const char *prefix, int prefix_length, int ignore_skip_w
     + 		else if (is_skipped)
     + 			fprintf(stderr, "has skip-worktree enabled; "
     + 					"use '--ignore-skip-worktree-bits' to checkout");
     +@@ builtin/checkout-index.c: static int checkout_all(const char *prefix, int prefix_length)
       	int i, errs = 0;
       	struct cache_entry *last_ce = NULL;
       
  7:  3b734f89c0f !  7:  ff32952a21c update-index: add tests for sparse-checkout compatibility
     @@ Commit message
          update-index: add tests for sparse-checkout compatibility
      
          Introduce tests for a variety of `git update-index` use cases, including
     -    performance scenarios. Tests for `update-index add/remove` are specifically
     -    focused on how `git stash` uses `git update-index` as a subcommand to
     -    prepare for sparse index integration with `stash` in a future series.
     +    performance scenarios. Tests are intended to exercise `update-index` with
     +    options that change the commands interaction with the index (e.g.,
     +    `--again`) and with files/directories inside and outside a sparse checkout
     +    cone.
     +
     +    Of note is that these tests clearly establish the behavior of `git
     +    update-index --add` with untracked, outside-of-cone files. Unlike `git add`,
     +    which fails with an error when provided with such files, `update-index`
     +    succeeds in adding them to the index. Additionally, the `skip-worktree` flag
     +    is *not* automatically added to the new entry. Although this is pre-existing
     +    behavior, there are a couple of reasons to avoid changing it in favor of
     +    consistency with e.g. `git add`:
     +
     +    * `update-index` is low-level command for modifying the index; while it can
     +      perform operations similar to those of `add`, it traditionally has fewer
     +      "guardrails" preventing a user from doing something they may not want to
     +      do (in this case, adding an outside-of-cone, non-`skip-worktree` file to
     +      the index)
     +    * `update-index` typically only exits with an error code if it is incapable
     +      of performing an operation (e.g., if an internal function call fails);
     +      adding a new file outside the sparse checkout definition is still a valid
     +      operation, albeit an inadvisable one
     +    * `update-index` does not implicitly set flags (e.g., `skip-worktree`) when
     +      creating new index entries with `--add`; if flags need to be updated,
     +      options like `--[no-]skip-worktree` allow a user to intentionally set them
     +
     +    All this to say that, while there are valid reasons to consider changing the
     +    treatment of outside-of-cone files in `update-index`, there are also
     +    sufficient reasons for leaving it as-is.
      
          Co-authored-by: Derrick Stolee <[email-masked]>
          Signed-off-by: Victoria Dye <[email-masked]>
     @@ t/perf/p2000-sparse-operations.sh: test_perf_on_all git diff --cached
       test_perf_on_all git blame $SPARSE_CONE/a
       test_perf_on_all git blame $SPARSE_CONE/f3/a
       test_perf_on_all git checkout-index -f --all
     -+test_perf_on_all git update-index --add --remove
     ++test_perf_on_all git update-index --add --remove $SPARSE_CONE/a
       
       test_done
      
     @@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'reset with wildca
      +	EOF
      +
      +	# Create & modify folder1/a
     ++	# Note that this setup is a manual way of reaching the erroneous
     ++	# condition in which a `skip-worktree` enabled, outside-of-cone file
     ++	# exists on disk. It is used here to ensure `update-index` is stable
     ++	# and behaves predictably if such a condition occurs.
      +	run_on_sparse mkdir -p folder1 &&
      +	run_on_sparse cp ../initial-repo/folder1/a folder1/a &&
      +	run_on_all ../edit-contents folder1/a &&
     @@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'reset with wildca
      +	run_on_sparse mkdir -p folder1 &&
      +	run_on_all ../edit-contents folder1/b &&
      +
     -+	# Similar to `git add`, the untracked out-of-cone file is added to the index
     -+	# identically across sparse and non-sparse checkouts
     ++	# The *untracked* out-of-cone file is added to the index because it does
     ++	# not have a `skip-worktree` bit to signal that it should be ignored
     ++	# (unlike in `git add`, which will fail due to the file being outside
     ++	# the sparse checkout definition).
      +	test_all_match git update-index --add folder1/b &&
      +	test_all_match git status --porcelain=v2
      +'
      +
     ++# NEEDSWORK: `--remove`, unlike the rest of `update-index`, does not ignore
     ++# `skip-worktree` entries by default and will remove them from the index.
     ++# The `--ignore-skip-worktree-entries` flag must be used in conjunction with
     ++# `--remove` to ignore the `skip-worktree` entries and prevent their removal
     ++# from the index.
      +test_expect_success 'update-index --remove outside sparse definition' '
      +	init_repos &&
      +
     -+	# When `--ignore-skip-worktree-entries` is specified, out-of-cone files are
     -+	# not removed from the index if they do not exist on disk
     ++	# When --ignore-skip-worktree-entries is _not_ specified:
     ++	# out-of-cone, not-on-disk files are removed from the index
     ++	test_sparse_match git update-index --remove folder1/a &&
     ++	cat >expect <<-EOF &&
     ++	D	folder1/a
     ++	EOF
     ++	test_sparse_match git diff --cached --name-status &&
     ++	test_cmp expect sparse-checkout-out &&
     ++
     ++	# Reset the state
     ++	test_all_match git reset --hard &&
     ++
     ++	# When --ignore-skip-worktree-entries is specified, out-of-cone
     ++	# (skip-worktree) files are ignored
      +	test_sparse_match git update-index --remove --ignore-skip-worktree-entries folder1/a &&
     -+	test_all_match git status --porcelain=v2 &&
     ++	test_sparse_match git diff --cached --name-status &&
     ++	test_must_be_empty sparse-checkout-out &&
      +
     -+	# When the flag is _not_ specified, out-of-cone, not-on-disk files are
     -+	# removed from the index
     -+	rm full-checkout/folder1/a &&
     -+	test_all_match git update-index --remove folder1/a &&
     -+	test_all_match git status --porcelain=v2 &&
     ++	# Reset the state
     ++	test_all_match git reset --hard &&
      +
     -+	# NOTE: --force-remove supercedes --ignore-skip-worktree-entries, removing
     ++	# --force-remove supercedes --ignore-skip-worktree-entries, removing
      +	# a skip-worktree file from the index (and disk) when both are specified
     -+	test_all_match git update-index --force-remove --ignore-skip-worktree-entries folder1/a &&
     -+	test_all_match git status --porcelain=v2
     ++	# with --remove
     ++	test_sparse_match git update-index --force-remove --ignore-skip-worktree-entries folder1/a &&
     ++	cat >expect <<-EOF &&
     ++	D	folder1/a
     ++	EOF
     ++	test_sparse_match git diff --cached --name-status &&
     ++	test_cmp expect sparse-checkout-out
      +'
      +
      +test_expect_success 'update-index with directories' '
     @@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'reset with wildca
      +	#   file and either triggers an error ("does not exist  and --remove not passed")
      +	#   or is ignored completely (when using --remove)
      +	test_all_match test_must_fail git update-index deep &&
     -+	run_on_all test_must_fail git update-indexe folder1 &&
     ++	run_on_all test_must_fail git update-index folder1 &&
      +	test_must_fail git -C full-checkout update-index --remove folder1 &&
      +	test_sparse_match git update-index --remove folder1 &&
      +	test_all_match git status --porcelain=v2
     @@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'reset with wildca
      +test_expect_success 'update-index --again file outside sparse definition' '
      +	init_repos &&
      +
     -+	write_script edit-contents <<-\EOF &&
     -+	echo text >>$1
     -+	EOF
     -+
      +	test_all_match git checkout -b test-reupdate &&
      +
      +	# Update HEAD without modifying the index to introduce a difference in
     @@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'reset with wildca
      +	test_sparse_match git reset --soft update-folder1 &&
      +
      +	# Because folder1/a differs in the index vs HEAD,
     -+	# `git update-index --remove --again` will effectively perform
     -+	# `git update-index --remove folder1/a` and remove the folder1/a
     -+	test_sparse_match git update-index --remove --again &&
     -+	test_sparse_match git status --porcelain=v2
     ++	# `git update-index --no-skip-worktree --again` will effectively perform
     ++	# `git update-index --no-skip-worktree folder1/a` and remove the skip-worktree
     ++	# flag from folder1/a
     ++	test_sparse_match git update-index --no-skip-worktree --again &&
     ++	test_sparse_match git status --porcelain=v2 &&
     ++
     ++	cat >expect <<-EOF &&
     ++	D	folder1/a
     ++	EOF
     ++	test_sparse_match git diff --name-status &&
     ++	test_cmp expect sparse-checkout-out
      +'
      +
      +test_expect_success 'update-index --cacheinfo' '
     @@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'reset with wildca
      +	# Cannot add sparse directory, even in sparse index case
      +	test_all_match test_must_fail git update-index --add --cacheinfo 040000 $folder2_oid folder2/ &&
      +
     -+	# Sparse match only - because folder1/a is outside the sparse checkout
     -+	# definition (and thus not on-disk), it will appear "deleted" in
     -+	# unstaged changes.
     -+	test_all_match git update-index --add --cacheinfo 100644 $folder1_a_oid folder1/a &&
     -+	test_sparse_match git status --porcelain=v2
     ++	# Sparse match only: the new outside-of-cone entry is added *without* skip-worktree,
     ++	# so `git status` reports it as "deleted" in the worktree
     ++	test_sparse_match git update-index --add --cacheinfo 100644 $folder1_a_oid folder1/a &&
     ++	test_sparse_match git status --porcelain=v2 &&
     ++	cat >expect <<-EOF &&
     ++	MD folder1/a
     ++	EOF
     ++	test_sparse_match git status --short -- folder1/a &&
     ++	test_cmp expect sparse-checkout-out &&
     ++
     ++	# To return folder1/a to "normal" for a sparse checkout (ignored &
     ++	# outside-of-cone), add the skip-worktree flag.
     ++	test_sparse_match git update-index --skip-worktree folder1/a &&
     ++	cat >expect <<-EOF &&
     ++	S folder1/a
     ++	EOF
     ++	test_sparse_match git ls-files -t -- folder1/a &&
     ++	test_cmp expect sparse-checkout-out
      +'
      +
       test_expect_success 'merge, cherry-pick, and rebase' '
  8:  c5b98e36516 !  8:  9ddc51a47d5 update-index: integrate with sparse index
     @@ Metadata
       ## Commit message ##
          update-index: integrate with sparse index
      
     -    Enable usage of the sparse index with `update-index`. Most variations of
     +    Enable use of the sparse index with `update-index`. Most variations of
          `update-index` work without explicitly expanding the index or making any
          other updates in or outside of `update-index.c`.
      
     @@ read-cache.c: static int add_index_entry_with_check(struct index_state *istate,
       		if (!new_only)
      
       ## t/t1092-sparse-checkout-compatibility.sh ##
     -@@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'sparse index is not expanded: blame' '
     - 	done
     +@@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'sparse index is not expanded: diff' '
     + 	ensure_not_expanded diff --cached
       '
       
      +test_expect_success 'sparse index is not expanded: update-index' '
      +	init_repos &&
      +
     ++	deep_a_oid=$(git -C full-checkout rev-parse update-deep:deep/a) &&
     ++	ensure_not_expanded update-index --cacheinfo 100644 $deep_a_oid deep/a &&
     ++
      +	echo "test" >sparse-index/README.md &&
      +	echo "test2" >sparse-index/a &&
      +	rm -f sparse-index/deep/a &&
     @@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'sparse index is n
      +	ensure_not_expanded update-index --remove deep/a
      +'
      +
     - # NEEDSWORK: a sparse-checkout behaves differently from a full checkout
     - # in this scenario, but it shouldn't.
     - test_expect_success 'reset mixed and checkout orphan' '
     + test_expect_success 'sparse index is not expanded: blame' '
     + 	init_repos &&
     + 
  9:  de7fc143562 !  9:  80697e9259e update-index: reduce scope of index expansion in do_reupdate
     @@ Metadata
       ## Commit message ##
          update-index: reduce scope of index expansion in do_reupdate
      
     -    Expand the full index (and redo reupdate operation) only if a sparse
     -    directory in the index differs from HEAD. Only the index entries that differ
     -    between the index and HEAD are updated when performing `git update-index
     -    --again`, so unmodified sparse directories are safely skipped. The index
     -    does need to be expanded when sparse directories contain changes, though,
     -    because `update_one(...)` will not operate on sparse directory index
     -    entries.
     +    Replace unconditional index expansion in 'do_reupdate()' with one scoped to
     +    only where a full index is needed. A full index is only required in
     +    'do_reupdate()' when a sparse directory in the index differs from HEAD; in
     +    that case, the index is expanded and the operation restarted.
      
          Because the index should only be expanded if a sparse directory is modified,
          add a test ensuring the index is not expanded when differences only exist
     @@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'sparse index is n
      +	ensure_not_expanded update-index --add --remove --again
       '
       
     - # NEEDSWORK: a sparse-checkout behaves differently from a full checkout
     + test_expect_success 'sparse index is not expanded: blame' '

-- 
gitgitgadget