Skip to content

features/sparse-checkout-2.24.0 #214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

derrickstolee
Copy link

@derrickstolee derrickstolee commented Oct 23, 2019

This is a tentative replay of the work in features/sparse-checkout-2.23.0 on top of tentative/vfs-2.24.0.

I had to update our integration point with partial clone since the "multiple promisor remote" feature completely messed up where we were integrating before.

The range-diff shows a lot of dropped merges, dropped old versions of the sparse-checkout feature (in favor of the latest version of ds/sparse-cone), and dropped fixup! commits (which have been squashed).

Range-diff:

 1:  9430147ca0 <  -:  ---------- list-objects-filter: encapsulate filter components
 2:  7a7c7f4a6d <  -:  ---------- list-objects-filter: put omits set in filter struct
 3:  842b00516a <  -:  ---------- list-objects-filter-options: always supply *errbuf
 4:  e987df5fe6 <  -:  ---------- list-objects-filter: implement composite filters
 5:  f56f764279 <  -:  ---------- list-objects-filter-options: move error check up
 6:  cf9ceb5a12 <  -:  ---------- list-objects-filter-options: make filter_spec a string_list
 7:  c2694952e3 <  -:  ---------- strbuf: give URL-encoding API a char predicate fn
 8:  489fc9ee71 <  -:  ---------- list-objects-filter-options: allow mult. --filter
 9:  5a133e8a7f <  -:  ---------- list-objects-filter-options: clean up use of ALLOC_GROW
10:  90d21f9ebf <  -:  ---------- list-objects-filter-options: make parser void
11:  ab2e063cd1 <  -:  ---------- sparse-checkout: create builtin with 'list' subcommand
12:  35700b34c6 <  -:  ---------- sparse-checkout: create 'init' subcommand
13:  4e8e6c314e <  -:  ---------- clone: add --sparse mode
14:  8913c25d23 <  -:  ---------- sparse-checkout: 'add' subcommand
15:  c08d037ac5 <  -:  ---------- sparse-checkout: create 'disable' subcommand
16:  4aee073949 <  -:  ---------- trace2:experiment: clear_ce_flags_1
17:  6c1b7fbdd5 <  -:  ---------- sparse-checkout: add 'cone' mode
18:  79a042c19c <  -:  ---------- sparse-checkout: use hashmaps for cone patterns
19:  19ad979d90 <  -:  ---------- sparse-checkout: init and add in cone mode
20:  40e22538b7 <  -:  ---------- checkout: fix leftover global constant
21:  3025dbde76 <  -:  ---------- unpack-trees: hash less in cone mode
22:  ab8db61390 <  -:  ---------- treewide: rename 'struct exclude' to 'struct path_pattern'
23:  caa3d55444 <  -:  ---------- treewide: rename 'struct exclude_list' to 'struct pattern_list'
24:  4ff89ee52c <  -:  ---------- treewide: rename 'EXCL_FLAG_' to 'PATTERN_FLAG_'
25:  65edd96aec <  -:  ---------- treewide: rename 'exclude' methods to 'pattern'
26:  468ce99b77 <  -:  ---------- unpack-trees: rename 'is_excluded_from_list()'
27:  d90e0a2f53 <  -:  ---------- fixup! DEPRECATION: warn about 'git checkout -b'
28:  193c35218f <  -:  ---------- fixup! Revert "switch: no worktree status unless real branch switch happens"
29:  043dd4781c <  -:  ---------- fixup! Fix reset when using the sparse-checkout feature.
30:  dbaf3de88e !  1:  77844cabe6 sparse-checkout: create builtin with 'list' subcommand
    @@ Commit message
         builtin will be the preferred mechanism for manipulating the
         sparse-checkout file and syncing the working directory.
     
    -    The `$GIT_DIR/info/sparse-checkout` file defines the skip-
    -    worktree reference bitmap. When Git updates the working
    -    directory, it updates the skip-worktree bits in the index
    -    based on this file and removes or restores files in the
    -    working copy to match.
    -
         The documentation provided is adapted from the "git read-tree"
         documentation with a few edits for clarity in the new context.
         Extra sections are added to hint toward a future change to
    @@ Commit message
     
         Helped-by: Elijah Newren <[email protected]>
         Signed-off-by: Derrick Stolee <[email protected]>
    +    Signed-off-by: Junio C Hamano <[email protected]>
     
      ## .gitignore ##
     @@
    @@ Documentation/git-read-tree.txt: support.
      ## Documentation/git-sparse-checkout.txt (new) ##
     @@
     +git-sparse-checkout(1)
    -+=======================
    ++======================
     +
     +NAME
     +----
    @@ Documentation/git-sparse-checkout.txt (new)
     +Initialize and modify the sparse-checkout configuration, which reduces
     +the checkout to a set of directories given by a list of prefixes.
     +
    ++THIS COMMAND IS EXPERIMENTAL. ITS BEHAVIOR, AND THE BEHAVIOR OF OTHER
    ++COMMANDS IN THE PRESENCE OF SPARSE-CHECKOUTS, WILL LIKELY CHANGE IN
    ++THE FUTURE.
    ++
     +
     +COMMANDS
     +--------
    @@ Documentation/git-sparse-checkout.txt (new)
     +
     +
     +SPARSE CHECKOUT
    -+----------------
    ++---------------
     +
     +"Sparse checkout" allows populating the working directory sparsely.
     +It uses the skip-worktree bit (see linkgit:git-update-index[1]) to tell
    @@ Documentation/git-sparse-checkout.txt (new)
     +
     +The `$GIT_DIR/info/sparse-checkout` file is used to define the
     +skip-worktree reference bitmap. When Git updates the working
    -+directory, it resets the skip-worktree bit in the index based on this
    -+file. If an entry
    -+matches a pattern in this file, skip-worktree will not be set on
    -+that entry. Otherwise, skip-worktree will be set.
    -+
    -+Then it compares the new skip-worktree value with the previous one. If
    -+skip-worktree turns from set to unset, it will add the corresponding
    -+file back. If it turns from unset to set, that file will be removed.
    ++directory, it updates the skip-worktree bits in the index based
    ++on this file. The files matching the patterns in the file will
    ++appear in the working directory, and the rest will not.
     +
     +## FULL PATTERN SET
     +
    @@ Documentation/git-sparse-checkout.txt (new)
     +----------------
     +
     +Then you can disable sparse checkout. Sparse checkout support in 'git
    -+read-tree' and similar commands is disabled by default. You need to
    ++checkout' and similar commands is disabled by default. You need to
     +set `core.sparseCheckout` to `true` in order to have sparse checkout
     +support.
     +
    @@ builtin/sparse-checkout.c (new)
     +#include "strbuf.h"
     +
     +static char const * const builtin_sparse_checkout_usage[] = {
    -+	N_("git sparse-checkout [list]"),
    ++	N_("git sparse-checkout list"),
     +	NULL
     +};
     +
    @@ builtin/sparse-checkout.c (new)
     +			   builtin_sparse_checkout_options);
     +}
     
    + ## command-list.txt ##
    +@@ command-list.txt: git-show-index                          plumbinginterrogators
    + git-show-ref                            plumbinginterrogators
    + git-sh-i18n                             purehelpers
    + git-sh-setup                            purehelpers
    ++git-sparse-checkout                     mainporcelain           worktree
    + git-stash                               mainporcelain
    + git-stage                                                               complete
    + git-status                              mainporcelain           info
    +
      ## git.c ##
     @@ git.c: static struct cmd_struct commands[] = {
      	{ "show-branch", cmd_show_branch, RUN_SETUP },
    @@ t/t1091-sparse-checkout-builtin.sh (new)
     +
     +test_expect_success 'git sparse-checkout list (empty)' '
     +	git -C repo sparse-checkout list >list 2>err &&
    -+	test_line_count = 0 list &&
    ++	test_must_be_empty list &&
     +	test_i18ngrep "this worktree is not sparse (sparse-checkout file may not exist)" err
     +'
     +
    @@ t/t1091-sparse-checkout-builtin.sh (new)
     +		**/a
     +		!*bin*
     +	EOF
    ++	cp repo/.git/info/sparse-checkout expect &&
     +	git -C repo sparse-checkout list >list &&
    -+	cat >expect <<-EOF &&
    -+		/folder1/*
    -+		/deep/
    -+		**/a
    -+		!*bin*
    -+	EOF
     +	test_cmp expect list
     +'
     +
     +test_done
    -+
31:  412211f5dd !  2:  7e12ce4c40 sparse-checkout: create 'init' subcommand
    @@ Commit message
         an initial set of patterns to the sparse-checkout file, and update
         their working directory.
     
    -    Using 'git read-tree' to clear directories does not work cleanly
    -    on Windows, so manually delete directories that are tracked by Git
    -    before running read-tree.
    +    Make sure to use the `extensions.worktreeConfig` setting and write
    +    the sparse checkout config to the worktree-specific config file.
    +    This avoids confusing interactions with other worktrees.
     
    -    The use of running another process for 'git read-tree' is likely
    -    suboptimal, but that can be improved in a later change, if valuable.
    +    The use of running another process for 'git read-tree' is sub-
    +    optimal. This will be removed in a later change.
     
         Signed-off-by: Derrick Stolee <[email protected]>
    +    Signed-off-by: Junio C Hamano <[email protected]>
     
      ## Documentation/git-sparse-checkout.txt ##
     @@ Documentation/git-sparse-checkout.txt: COMMANDS
    @@ Documentation/git-sparse-checkout.txt: COMMANDS
     +	no other directories, then will remove all directories tracked
     +	by Git. Add patterns to the sparse-checkout file to
     +	repopulate the working directory.
    +++
    ++To avoid interfering with other worktrees, it first enables the
    ++`extensions.worktreeConfig` setting and makes sure to set the
    ++`core.sparseCheckout` setting in the worktree-specific config file.
      
      SPARSE CHECKOUT
    - ----------------
    + ---------------
     
      ## builtin/sparse-checkout.c ##
     @@
      #include "strbuf.h"
      
      static char const * const builtin_sparse_checkout_usage[] = {
    --	N_("git sparse-checkout [list]"),
    -+	N_("git sparse-checkout [init|list]"),
    +-	N_("git sparse-checkout list"),
    ++	N_("git sparse-checkout (init|list)"),
      	NULL
      };
      
    @@ builtin/sparse-checkout.c: static int sparse_checkout_list(int argc, const char
     +	return result;
     +}
     +
    -+static int sc_enable_config(void)
    ++enum sparse_checkout_mode {
    ++	MODE_NO_PATTERNS = 0,
    ++	MODE_ALL_PATTERNS = 1,
    ++};
    ++
    ++static int sc_set_config(enum sparse_checkout_mode mode)
     +{
     +	struct argv_array argv = ARGV_ARRAY_INIT;
     +
    @@ builtin/sparse-checkout.c: static int sparse_checkout_list(int argc, const char
     +		return 1;
     +	}
     +
    -+	argv_array_pushl(&argv, "config", "--worktree", "core.sparseCheckout", "true", NULL);
    ++	argv_array_pushl(&argv, "config", "--worktree", "core.sparseCheckout", NULL);
    ++
    ++	if (mode)
    ++		argv_array_pushl(&argv, "true", NULL);
    ++	else
    ++		argv_array_pushl(&argv, "false", NULL);
     +
     +	if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
     +		error(_("failed to enable core.sparseCheckout"));
    @@ builtin/sparse-checkout.c: static int sparse_checkout_list(int argc, const char
     +	FILE *fp;
     +	int res;
     +
    -+	if (sc_enable_config())
    ++	if (sc_set_config(MODE_ALL_PATTERNS))
     +		return 1;
     +
     +	memset(&pl, 0, sizeof(pl));
    @@ builtin/sparse-checkout.c: static int sparse_checkout_list(int argc, const char
     +	}
     +
     +	/* initial mode: all blobs at root */
    -+	fp = fopen(sparse_filename, "w");
    ++	fp = xfopen(sparse_filename, "w");
     +	free(sparse_filename);
     +	fprintf(fp, "/*\n!/*/\n");
     +	fclose(fp);
    @@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'git sparse-checkout lis
     +'
     +
      test_done
    - 
32:  fef41b794a !  3:  2e885711e4 clone: add --sparse mode
    @@ Commit message
         point.
     
         During the 'git sparse-checkout init' call, we must first look
    -    to see if HEAD is valid, or else we will fail while trying to
    -    update the working directory. The first checkout will actually
    -    update the working directory correctly.
    +    to see if HEAD is valid, since 'git clone' does not have a valid
    +    HEAD at the point where it initializes the sparse-checkout. The
    +    following checkout within the clone command will create the HEAD
    +    ref and update the working directory correctly.
     
         Signed-off-by: Derrick Stolee <[email protected]>
    +    Signed-off-by: Junio C Hamano <[email protected]>
     
      ## Documentation/git-clone.txt ##
     @@ Documentation/git-clone.txt: SYNOPSIS
    @@ builtin/sparse-checkout.c: static int sparse_checkout_init(int argc, const char
      	int res;
     +	struct object_id oid;
      
    - 	if (sc_enable_config())
    + 	if (sc_set_config(MODE_ALL_PATTERNS))
      		return 1;
     @@ builtin/sparse-checkout.c: static int sparse_checkout_init(int argc, const char **argv)
      	fprintf(fp, "/*\n!/*/\n");
    @@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'init with existing spar
     +'
     +
      test_done
    - 
33:  9a78f9ea0f !  4:  c6b249a6e1 sparse-checkout: 'set' subcommand
    @@ Commit message
         extracted from cmd_sparse_checkout() to make it easier to implement
         'add' and/or 'remove' subcommands in the future.
     
    +    If the core.sparseCheckout config setting is disabled, then enable
    +    the config setting in the worktree config. If we set the config
    +    this way and the sparse-checkout fails, then re-disable the config
    +    setting.
    +
         Signed-off-by: Derrick Stolee <[email protected]>
    +    Signed-off-by: Junio C Hamano <[email protected]>
     
      ## Documentation/git-sparse-checkout.txt ##
    -@@ Documentation/git-sparse-checkout.txt: COMMANDS
    - 	by Git. Add patterns to the sparse-checkout file to
    - 	repopulate the working directory.
    +@@ Documentation/git-sparse-checkout.txt: To avoid interfering with other worktrees, it first enables the
    + `extensions.worktreeConfig` setting and makes sure to set the
    + `core.sparseCheckout` setting in the worktree-specific config file.
      
     +'set'::
     +	Write a set of patterns to the sparse-checkout file, as given as
     +	a list of arguments following the 'set' subcommand. Update the
    -+	working directory to match the new patterns.
    ++	working directory to match the new patterns. Enable the
    ++	core.sparseCheckout config setting if it is not already enabled.
     +
      SPARSE CHECKOUT
    - ----------------
    + ---------------
      
     
      ## builtin/sparse-checkout.c ##
    @@ builtin/sparse-checkout.c
      #include "strbuf.h"
      
      static char const * const builtin_sparse_checkout_usage[] = {
    --	N_("git sparse-checkout [init|list]"),
    -+	N_("git sparse-checkout [init|list|set] <options>"),
    +-	N_("git sparse-checkout (init|list)"),
    ++	N_("git sparse-checkout (init|list|set) <options>"),
      	NULL
      };
      
    @@ builtin/sparse-checkout.c: static int sparse_checkout_init(int argc, const char
     +	fclose(fp);
     +	free(sparse_filename);
     +
    -+	clear_pattern_list(pl);
     +	return update_working_directory();
     +}
     +
     +static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
     +{
    ++	static const char *empty_base = "";
     +	int i;
     +	struct pattern_list pl;
    ++	int result;
    ++	int set_config = 0;
     +	memset(&pl, 0, sizeof(pl));
     +
     +	for (i = 1; i < argc; i++)
    -+		add_pattern(argv[i], NULL, 0, &pl, 0);
    ++		add_pattern(argv[i], empty_base, 0, &pl, 0);
    ++
    ++	if (!core_apply_sparse_checkout) {
    ++		sc_set_config(MODE_ALL_PATTERNS);
    ++		core_apply_sparse_checkout = 1;
    ++		set_config = 1;
    ++	}
    ++
    ++	result = write_patterns_and_update(&pl);
    ++
    ++	if (result && set_config)
    ++		sc_set_config(MODE_NO_PATTERNS);
     +
    -+	return write_patterns_and_update(&pl);
    ++	clear_pattern_list(&pl);
    ++	return result;
     +}
     +
      int cmd_sparse_checkout(int argc, const char **argv, const char *prefix)
    @@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'clone --sparse' '
      	test_cmp expect dir
      '
      
    ++test_expect_success 'set enables config' '
    ++	git init empty-config &&
    ++	(
    ++		cd empty-config &&
    ++		test_commit test file &&
    ++		test_path_is_missing .git/config.worktree &&
    ++		test_must_fail git sparse-checkout set nothing &&
    ++		test_i18ngrep "sparseCheckout = false" .git/config.worktree &&
    ++		git sparse-checkout set "/*" &&
    ++		test_i18ngrep "sparseCheckout = true" .git/config.worktree
    ++	)
    ++'
    ++
     +test_expect_success 'set sparse-checkout using builtin' '
     +	git -C repo sparse-checkout set "/*" "!/*/" "*folder*" &&
     +	cat >expect <<-EOF &&
    @@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'clone --sparse' '
     +'
     +
      test_done
    - 
34:  21a0165be7 !  5:  ead918f126 sparse-checkout: add '--stdin' option to set subcommand
    @@ Commit message
         Add a '--stdin' option to instead read patterns over standard in.
     
         Signed-off-by: Derrick Stolee <[email protected]>
    +    Signed-off-by: Junio C Hamano <[email protected]>
     
      ## builtin/sparse-checkout.c ##
     @@ builtin/sparse-checkout.c: static int write_patterns_and_update(struct pattern_list *pl)
    @@ builtin/sparse-checkout.c: static int write_patterns_and_update(struct pattern_l
     +
      static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
      {
    - 	int i;
    + 	static const char *empty_base = "";
    +@@ builtin/sparse-checkout.c: static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
      	struct pattern_list pl;
    + 	int result;
    + 	int set_config = 0;
     +
     +	static struct option builtin_sparse_checkout_set_options[] = {
     +		OPT_BOOL(0, "stdin", &set_opts.use_stdin,
    @@ builtin/sparse-checkout.c: static int write_patterns_and_update(struct pattern_l
      	memset(&pl, 0, sizeof(pl));
      
     -	for (i = 1; i < argc; i++)
    --		add_pattern(argv[i], NULL, 0, &pl, 0);
    +-		add_pattern(argv[i], empty_base, 0, &pl, 0);
     +	argc = parse_options(argc, argv, prefix,
     +			     builtin_sparse_checkout_set_options,
     +			     builtin_sparse_checkout_set_usage,
    @@ builtin/sparse-checkout.c: static int write_patterns_and_update(struct pattern_l
     +		while (!strbuf_getline(&line, stdin)) {
     +			size_t len;
     +			char *buf = strbuf_detach(&line, &len);
    -+			add_pattern(buf, buf, len, &pl, 0);
    ++			add_pattern(buf, empty_base, 0, &pl, 0);
     +		}
     +	} else {
     +		for (i = 0; i < argc; i++)
    -+			add_pattern(argv[i], argv[i], strlen(argv[i]), &pl, 0);
    ++			add_pattern(argv[i], empty_base, 0, &pl, 0);
     +	}
      
    - 	return write_patterns_and_update(&pl);
    - }
    + 	if (!core_apply_sparse_checkout) {
    + 		sc_set_config(MODE_ALL_PATTERNS);
     
      ## t/t1091-sparse-checkout-builtin.sh ##
     @@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'set sparse-checkout using builtin' '
    @@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'set sparse-checkout usi
     +'
     +
      test_done
    - 
35:  b62b76013f !  6:  043e1a3d9a sparse-checkout: create 'disable' subcommand
    @@ Commit message
         steps for the user.
     
         Signed-off-by: Derrick Stolee <[email protected]>
    +    Signed-off-by: Junio C Hamano <[email protected]>
     
      ## Documentation/git-sparse-checkout.txt ##
    -@@ Documentation/git-sparse-checkout.txt: COMMANDS
    - 	a list of arguments following the 'set' subcommand. Update the
    - 	working directory to match the new patterns.
    +@@ Documentation/git-sparse-checkout.txt: To avoid interfering with other worktrees, it first enables the
    + 	working directory to match the new patterns. Enable the
    + 	core.sparseCheckout config setting if it is not already enabled.
      
     +'disable'::
     +	Remove the sparse-checkout file, set `core.sparseCheckout` to
     +	`false`, and restore the working directory to include all files.
     +
      SPARSE CHECKOUT
    - ----------------
    + ---------------
      
    -@@ Documentation/git-sparse-checkout.txt: Then it compares the new skip-worktree value with the previous one. If
    - skip-worktree turns from set to unset, it will add the corresponding
    - file back. If it turns from unset to set, that file will be removed.
    +@@ Documentation/git-sparse-checkout.txt: directory, it updates the skip-worktree bits in the index based
    + on this file. The files matching the patterns in the file will
    + appear in the working directory, and the rest will not.
      
    ++To enable the sparse-checkout feature, run `git sparse-checkout init` to
    ++initialize a simple sparse-checkout file and enable the `core.sparseCheckout`
    ++config setting. Then, run `git sparse-checkout set` to modify the patterns in
    ++the sparse-checkout file.
    ++
     +To repopulate the working directory with all files, use the
     +`git sparse-checkout disable` command.
    -+
    -+Sparse checkout support in 'git checkout' and similar commands is
    -+disabled by default. You need to set `core.sparseCheckout` to `true`
    -+in order to have sparse checkout support.
     +
      ## FULL PATTERN SET
      
    @@ Documentation/git-sparse-checkout.txt: using negative patterns. For example, to
     -----------------
     -
     -Then you can disable sparse checkout. Sparse checkout support in 'git
    --read-tree' and similar commands is disabled by default. You need to
    +-checkout' and similar commands is disabled by default. You need to
     -set `core.sparseCheckout` to `true` in order to have sparse checkout
     -support.
      
    @@ builtin/sparse-checkout.c
      #include "strbuf.h"
      
      static char const * const builtin_sparse_checkout_usage[] = {
    --	N_("git sparse-checkout [init|list|set] <options>"),
    -+	N_("git sparse-checkout [init|list|set|disable] <options>"),
    +-	N_("git sparse-checkout (init|list|set) <options>"),
    ++	N_("git sparse-checkout (init|list|set|disable) <options>"),
      	NULL
      };
      
    -@@ builtin/sparse-checkout.c: static int update_working_directory(void)
    - 	return result;
    - }
    - 
    --static int sc_enable_config(void)
    -+static int sc_set_config(int mode)
    - {
    - 	struct argv_array argv = ARGV_ARRAY_INIT;
    - 
    -@@ builtin/sparse-checkout.c: static int sc_enable_config(void)
    - 		return 1;
    - 	}
    - 
    --	argv_array_pushl(&argv, "config", "--worktree", "core.sparseCheckout", "true", NULL);
    -+	argv_array_pushl(&argv, "config", "--worktree", "core.sparseCheckout", NULL);
    -+
    -+	if (mode)
    -+		argv_array_pushl(&argv, "true", NULL);
    -+	else
    -+		argv_array_pushl(&argv, "false", NULL);
    - 
    - 	if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
    - 		error(_("failed to enable core.sparseCheckout"));
    -@@ builtin/sparse-checkout.c: static int sparse_checkout_init(int argc, const char **argv)
    - 	int res;
    - 	struct object_id oid;
    - 
    --	if (sc_enable_config())
    -+	if (sc_set_config(1))
    - 		return 1;
    - 
    - 	memset(&pl, 0, sizeof(pl));
     @@ builtin/sparse-checkout.c: static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
    - 	return write_patterns_and_update(&pl);
    + 	return result;
      }
      
     +static int sparse_checkout_disable(int argc, const char **argv)
    @@ builtin/sparse-checkout.c: static int sparse_checkout_set(int argc, const char *
     +	char *sparse_filename;
     +	FILE *fp;
     +
    -+	if (sc_set_config(1))
    ++	if (sc_set_config(MODE_ALL_PATTERNS))
     +		die(_("failed to change config"));
     +
     +	sparse_filename = get_sparse_checkout_filename();
    -+	fp = fopen(sparse_filename, "w");
    ++	fp = xfopen(sparse_filename, "w");
     +	fprintf(fp, "/*\n");
     +	fclose(fp);
     +
    @@ builtin/sparse-checkout.c: static int sparse_checkout_set(int argc, const char *
     +	unlink(sparse_filename);
     +	free(sparse_filename);
     +
    -+	return sc_set_config(0);
    ++	return sc_set_config(MODE_NO_PATTERNS);
     +}
     +
      int cmd_sparse_checkout(int argc, const char **argv, const char *prefix)
    @@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'set sparse-checkout usi
     +'
     +
      test_done
    - 
36:  25642f8df2 !  7:  d97f70b862 trace2: add region in clear_ce_flags
    @@ Commit message
     
         Signed-off-by: Jeff Hostetler <[email protected]>
         Signed-off-by: Derrick Stolee <[email protected]>
    +    Signed-off-by: Junio C Hamano <[email protected]>
     
      ## unpack-trees.c ##
     @@ unpack-trees.c: static int clear_ce_flags(struct index_state *istate,
37:  84511255d1 !  8:  95a6be78f3 sparse-checkout: add 'cone' mode
    @@ Commit message
         indicate that we expect the sparse-checkout file to contain a
         more limited set of patterns. This is a separate config setting
         from core.sparseCheckout to avoid breaking older clients by
    -    introcuding a tri-state option.
    +    introducing a tri-state option.
     
         The config option does nothing right now, but will be expanded
         upon in a later commit.
     
         Signed-off-by: Derrick Stolee <[email protected]>
    +    Signed-off-by: Junio C Hamano <[email protected]>
     
      ## Documentation/config/core.txt ##
     @@ Documentation/config/core.txt: core.multiPackIndex::
    @@ Documentation/config/core.txt: core.multiPackIndex::
      core.sparseCheckout::
     -	Enable "sparse checkout" feature. See section "Sparse checkout" in
     -	linkgit:git-read-tree[1] for more information.
    -+	Enable "sparse checkout" feature. If "false", then sparse-checkout
    -+	is disabled. If "true", then sparse-checkout is enabled with the full
    -+	.gitignore pattern set. If "cone", then sparse-checkout is enabled with
    -+	a restricted pattern set. See linkgit:git-sparse-checkout[1] for more
    -+	information.
    ++	Enable "sparse checkout" feature. See linkgit:git-sparse-checkout[1]
    ++	for more information.
    ++
    ++core.sparseCheckoutCone::
    ++	Enables the "cone mode" of the sparse checkout feature. When the
    ++	sparse-checkout file contains a limited set of patterns, then this
    ++	mode provides significant performance advantages. See
    ++	linkgit:git-sparse-checkout[1] for more information.
      
      core.abbrev::
      	Set the length object names are abbreviated to.  If
38:  95a3285bc6 !  9:  fa1e8e3adb sparse-checkout: use hashmaps for cone patterns
    @@ Commit message
         Running 'git read-tree -mu HEAD' on this file had the following
         performance:
     
    -            core.sparseCheckout=false: 0.21 s (0.00 s)
    -             core.sparseCheckout=true: 3.75 s (3.50 s)
    -             core.sparseCheckout=cone: 0.23 s (0.01 s)
    +        core.sparseCheckout=false: 0.21 s (0.00 s)
    +         core.sparseCheckout=true: 3.75 s (3.50 s)
    +     core.sparseCheckoutCone=true: 0.23 s (0.01 s)
     
         The times in parentheses above correspond to the time spent
         in the first clear_ce_flags() call, according to the trace2
    @@ Commit message
         While this example is contrived, it demonstrates how these
         patterns can slow the sparse-checkout feature.
     
    +    Helped-by: Eric Wong <[email protected]>
    +    Helped-by: Johannes Schindelin <[email protected]>
         Signed-off-by: Derrick Stolee <[email protected]>
    +    Signed-off-by: Junio C Hamano <[email protected]>
     
      ## dir.c ##
     @@ dir.c: void parse_path_pattern(const char **pattern,
    @@ dir.c: void parse_path_pattern(const char **pattern,
      }
      
     +static int pl_hashmap_cmp(const void *unused_cmp_data,
    -+			  const void *a, const void *b, const void *key)
    ++			  const struct hashmap_entry *a,
    ++			  const struct hashmap_entry *b,
    ++			  const void *key)
     +{
    -+	const struct pattern_entry *ee1 = (const struct pattern_entry *)a;
    -+	const struct pattern_entry *ee2 = (const struct pattern_entry *)b;
    ++	const struct pattern_entry *ee1 =
    ++			container_of(a, struct pattern_entry, ent);
    ++	const struct pattern_entry *ee2 =
    ++			container_of(b, struct pattern_entry, ent);
     +
     +	size_t min_len = ee1->patternlen <= ee2->patternlen
     +			 ? ee1->patternlen
    @@ dir.c: void parse_path_pattern(const char **pattern,
     +	if (!pl->use_cone_patterns)
     +		return;
     +
    -+	if (!strcmp(given->pattern, "/*"))
    ++	if (given->flags & PATTERN_FLAG_NEGATIVE &&
    ++	    given->flags & PATTERN_FLAG_MUSTBEDIR &&
    ++	    !strcmp(given->pattern, "/*")) {
    ++		pl->full_cone = 0;
     +		return;
    ++	}
    ++
    ++	if (!given->flags && !strcmp(given->pattern, "/*")) {
    ++		pl->full_cone = 1;
    ++		return;
    ++	}
     +
     +	if (given->patternlen > 2 &&
     +	    !strcmp(given->pattern + given->patternlen - 2, "/*")) {
    @@ dir.c: void parse_path_pattern(const char **pattern,
     +		translated = xmalloc(sizeof(struct pattern_entry));
     +		translated->pattern = truncated;
     +		translated->patternlen = given->patternlen - 2;
    -+		hashmap_entry_init(translated,
    ++		hashmap_entry_init(&translated->ent,
     +				   memhash(translated->pattern, translated->patternlen));
     +
    -+		if (!hashmap_get(&pl->recursive_hashmap, translated, NULL)) {
    ++		if (!hashmap_get_entry(&pl->recursive_hashmap,
    ++				       translated, ent, NULL)) {
     +			/* We did not see the "parent" included */
     +			warning(_("unrecognized negative pattern: '%s'"),
     +				given->pattern);
    @@ dir.c: void parse_path_pattern(const char **pattern,
     +			goto clear_hashmaps;
     +		}
     +
    -+		hashmap_add(&pl->parent_hashmap, translated);
    -+		hashmap_remove(&pl->recursive_hashmap, translated, &data);
    ++		hashmap_add(&pl->parent_hashmap, &translated->ent);
    ++		hashmap_remove(&pl->recursive_hashmap, &translated->ent, &data);
     +		free(data);
     +		return;
     +	}
    @@ dir.c: void parse_path_pattern(const char **pattern,
     +
     +	translated->pattern = xstrdup(given->pattern);
     +	translated->patternlen = given->patternlen;
    -+	hashmap_entry_init(translated,
    ++	hashmap_entry_init(&translated->ent,
     +			   memhash(translated->pattern, translated->patternlen));
     +
    -+	hashmap_add(&pl->recursive_hashmap, translated);
    ++	hashmap_add(&pl->recursive_hashmap, &translated->ent);
     +
    -+	if (hashmap_get(&pl->parent_hashmap, translated, NULL)) {
    ++	if (hashmap_get_entry(&pl->parent_hashmap, translated, ent, NULL)) {
     +		/* we already included this at the parent level */
     +		warning(_("your sparse-checkout file may have issues: pattern '%s' is repeated"),
     +			given->pattern);
    -+		hashmap_remove(&pl->parent_hashmap, translated, &data);
    ++		hashmap_remove(&pl->parent_hashmap, &translated->ent, &data);
     +		free(data);
     +		free(translated);
     +	}
    @@ dir.c: void parse_path_pattern(const char **pattern,
     +
     +clear_hashmaps:
     +	warning(_("disabling cone pattern matching"));
    -+	hashmap_free(&pl->parent_hashmap, 1);
    -+	hashmap_free(&pl->recursive_hashmap, 1);
    ++	hashmap_free_entries(&pl->parent_hashmap, struct pattern_entry, ent);
    ++	hashmap_free_entries(&pl->recursive_hashmap, struct pattern_entry, ent);
     +	pl->use_cone_patterns = 0;
     +}
     +
    @@ dir.c: void parse_path_pattern(const char **pattern,
     +	/* Check straight mapping */
     +	p.pattern = pattern->buf;
     +	p.patternlen = pattern->len;
    -+	hashmap_entry_init(&p, memhash(p.pattern, p.patternlen));
    -+	return !!hashmap_get(map, &p, NULL);
    ++	hashmap_entry_init(&p.ent, memhash(p.pattern, p.patternlen));
    ++	return !!hashmap_get_entry(map, &p, ent, NULL);
    ++}
    ++
    ++int hashmap_contains_parent(struct hashmap *map,
    ++			    const char *path,
    ++			    struct strbuf *buffer)
    ++{
    ++	char *slash_pos;
    ++
    ++	strbuf_setlen(buffer, 0);
    ++
    ++	if (path[0] != '/')
    ++		strbuf_addch(buffer, '/');
    ++
    ++	strbuf_addstr(buffer, path);
    ++
    ++	slash_pos = strrchr(buffer->buf, '/');
    ++
    ++	while (slash_pos > buffer->buf) {
    ++		strbuf_setlen(buffer, slash_pos - buffer->buf);
    ++
    ++		if (hashmap_contains_path(map, buffer))
    ++			return 1;
    ++
    ++		slash_pos = strrchr(buffer->buf, '/');
    ++	}
    ++
    ++	return 0;
     +}
     +
      void add_pattern(const char *string, const char *base,
    @@ dir.c: static int add_patterns_from_buffer(char *buf, size_t size,
      	int i, lineno = 1;
      	char *entry;
      
    -+	pl->use_cone_patterns = core_sparse_checkout_cone;
     +	hashmap_init(&pl->recursive_hashmap, pl_hashmap_cmp, NULL, 0);
     +	hashmap_init(&pl->parent_hashmap, pl_hashmap_cmp, NULL, 0);
     +
    @@ dir.c: enum pattern_match_result path_matches_pattern_list(
     +		}
     +
     +		return UNDECIDED;
    - 	}
    - 
    --	return UNDECIDED;
    ++	}
    ++
    ++	if (pl->full_cone)
    ++		return MATCHED;
    ++
     +	strbuf_addch(&parent_pathname, '/');
     +	strbuf_add(&parent_pathname, pathname, pathlen);
     +
     +	if (hashmap_contains_path(&pl->recursive_hashmap,
    -+					&parent_pathname)) {
    ++				  &parent_pathname)) {
     +		result = MATCHED;
     +		goto done;
     +	}
    @@ dir.c: enum pattern_match_result path_matches_pattern_list(
     +		/* include every file in root */
     +		result = MATCHED;
     +		goto done;
    -+	}
    -+
    + 	}
    + 
    +-	return UNDECIDED;
     +	strbuf_setlen(&parent_pathname, slash_pos - parent_pathname.buf);
     +
     +	if (hashmap_contains_path(&pl->parent_hashmap, &parent_pathname)) {
    @@ dir.c: enum pattern_match_result path_matches_pattern_list(
     +		goto done;
     +	}
     +
    -+	while (parent_pathname.len) {
    -+		if (hashmap_contains_path(&pl->recursive_hashmap,
    -+					  &parent_pathname)) {
    -+			result = UNDECIDED;
    -+			goto done;
    -+		}
    -+
    -+		slash_pos = strrchr(parent_pathname.buf, '/');
    -+		if (slash_pos == parent_pathname.buf)
    -+			break;
    -+
    -+		strbuf_setlen(&parent_pathname, slash_pos - parent_pathname.buf);
    -+	}
    ++	if (hashmap_contains_parent(&pl->recursive_hashmap,
    ++				    pathname,
    ++				    &parent_pathname))
    ++		result = MATCHED;
     +
     +done:
     +	strbuf_release(&parent_pathname);
    @@ dir.h: struct pattern_list {
     +	 * excludes array above. If non-zero, that check succeeded.
     +	 */
     +	unsigned use_cone_patterns;
    ++	unsigned full_cone;
     +
     +	/*
     +	 * Stores paths where everything starting with those paths
    @@ dir.h: struct pattern_list {
      };
      
      /*
    +@@ dir.h: int is_excluded(struct dir_struct *dir,
    + 		struct index_state *istate,
    + 		const char *name, int *dtype);
    + 
    ++int hashmap_contains_parent(struct hashmap *map,
    ++			    const char *path,
    ++			    struct strbuf *buffer);
    + struct pattern_list *add_pattern_list(struct dir_struct *dir,
    + 				      int group_type, const char *src);
    + int add_patterns_from_file_to_list(const char *fname, const char *base, int baselen,
     
      ## t/t1091-sparse-checkout-builtin.sh ##
     @@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'set sparse-checkout using --stdin' '
    @@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'cone mode: match patter
      test_expect_success 'sparse-checkout disable' '
      	git -C repo sparse-checkout disable &&
      	test_path_is_missing repo/.git/info/sparse-checkout &&
    +
    + ## unpack-trees.c ##
    +@@ unpack-trees.c: int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
    + 		o->skip_sparse_checkout = 1;
    + 	if (!o->skip_sparse_checkout) {
    + 		char *sparse = git_pathdup("info/sparse-checkout");
    ++		pl.use_cone_patterns = core_sparse_checkout_cone;
    + 		if (add_patterns_from_file_to_list(sparse, "", 0, &pl, NULL) < 0)
    + 			o->skip_sparse_checkout = 1;
    + 		else
39:  995c5b8e2b ! 10:  93f2e4e9df sparse-checkout: init and set in cone mode
    @@ Commit message
         we want to avoid the warning that the patterns do not match
         the cone-mode patterns.
     
    +    Helped-by: Eric Wong <[email protected]>
    +    Helped-by: Johannes Schindelin <[email protected]>
         Signed-off-by: Derrick Stolee <[email protected]>
    +    Signed-off-by: Junio C Hamano <[email protected]>
     
      ## builtin/sparse-checkout.c ##
     @@
    @@ builtin/sparse-checkout.c
     +#include "string-list.h"
      
      static char const * const builtin_sparse_checkout_usage[] = {
    - 	N_("git sparse-checkout [init|list|set|disable] <options>"),
    + 	N_("git sparse-checkout (init|list|set|disable) <options>"),
     @@ builtin/sparse-checkout.c: static int update_working_directory(void)
    - 	return result;
    - }
    + enum sparse_checkout_mode {
    + 	MODE_NO_PATTERNS = 0,
    + 	MODE_ALL_PATTERNS = 1,
    ++	MODE_CONE_PATTERNS = 2,
    + };
      
    -+#define SPARSE_CHECKOUT_NONE 0
    -+#define SPARSE_CHECKOUT_FULL 1
    -+#define SPARSE_CHECKOUT_CONE 2
    -+
    - static int sc_set_config(int mode)
    + static int sc_set_config(enum sparse_checkout_mode mode)
      {
      	struct argv_array argv = ARGV_ARRAY_INIT;
     +	struct argv_array cone_argv = ARGV_ARRAY_INIT;
      
      	if (git_config_set_gently("extensions.worktreeConfig", "true")) {
      		error(_("failed to set extensions.worktreeConfig setting"));
    -@@ builtin/sparse-checkout.c: static int sc_set_config(int mode)
    +@@ builtin/sparse-checkout.c: static int sc_set_config(enum sparse_checkout_mode mode)
      		return 1;
      	}
      
     +	argv_array_pushl(&cone_argv, "config", "--worktree",
     +			 "core.sparseCheckoutCone", NULL);
     +
    -+	if (mode == SPARSE_CHECKOUT_CONE)
    ++	if (mode == MODE_CONE_PATTERNS)
     +		argv_array_push(&cone_argv, "true");
     +	else
     +		argv_array_push(&cone_argv, "false");
    @@ builtin/sparse-checkout.c: static int sparse_checkout_init(int argc, const char
      	int res;
      	struct object_id oid;
     +	int mode;
    -+
    + 
    +-	if (sc_set_config(MODE_ALL_PATTERNS))
     +	static struct option builtin_sparse_checkout_init_options[] = {
     +		OPT_BOOL(0, "cone", &init_opts.cone_mode,
     +			 N_("initialize the sparse-checkout in cone mode")),
     +		OPT_END(),
     +	};
    - 
    --	if (sc_set_config(1))
    ++
     +	argc = parse_options(argc, argv, NULL,
     +			     builtin_sparse_checkout_init_options,
     +			     builtin_sparse_checkout_init_usage, 0);
     +
    -+	mode = init_opts.cone_mode ? SPARSE_CHECKOUT_CONE : SPARSE_CHECKOUT_FULL;
    ++	mode = init_opts.cone_mode ? MODE_CONE_PATTERNS : MODE_ALL_PATTERNS;
     +
     +	if (sc_set_config(mode))
      		return 1;
    @@ builtin/sparse-checkout.c: static int sparse_checkout_init(int argc, const char
      
     +static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *path)
     +{
    -+	struct pattern_entry *e = xmalloc(sizeof(struct pattern_entry));
    ++	struct pattern_entry *e = xmalloc(sizeof(*e));
     +	e->patternlen = path->len;
     +	e->pattern = strbuf_detach(path, NULL);
    -+	hashmap_entry_init(e, memhash(e->pattern, e->patternlen));
    ++	hashmap_entry_init(&e->ent, memhash(e->pattern, e->patternlen));
     +
    -+	hashmap_add(&pl->recursive_hashmap, e);
    ++	hashmap_add(&pl->recursive_hashmap, &e->ent);
     +
     +	while (e->patternlen) {
     +		char *slash = strrchr(e->pattern, '/');
     +		char *oldpattern = e->pattern;
     +		size_t newlen;
     +
    -+		if (!slash)
    ++		if (slash == e->pattern)
     +			break;
     +
     +		newlen = slash - e->pattern;
     +		e = xmalloc(sizeof(struct pattern_entry));
     +		e->patternlen = newlen;
     +		e->pattern = xstrndup(oldpattern, newlen);
    -+		hashmap_entry_init(e, memhash(e->pattern, e->patternlen));
    ++		hashmap_entry_init(&e->ent, memhash(e->pattern, e->patternlen));
     +
    -+		if (!hashmap_get(&pl->parent_hashmap, e, NULL))
    -+			hashmap_add(&pl->parent_hashmap, e);
    ++		if (!hashmap_get_entry(&pl->parent_hashmap, e, ent, NULL))
    ++			hashmap_add(&pl->parent_hashmap, &e->ent);
     +	}
     +}
     +
     +static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
     +{
     +	int i;
    -+	struct pattern_entry *entry;
    ++	struct pattern_entry *pe;
     +	struct hashmap_iter iter;
     +	struct string_list sl = STRING_LIST_INIT_DUP;
     +
    -+	hashmap_iter_init(&pl->parent_hashmap, &iter);
    -+	while ((entry = hashmap_iter_next(&iter)))
    -+		string_list_insert(&sl, entry->pattern);
    ++	hashmap_for_each_entry(&pl->parent_hashmap, &iter, pe, ent)
    ++		string_list_insert(&sl, pe->pattern);
     +
     +	string_list_sort(&sl);
     +	string_list_remove_duplicates(&sl, 0);
    @@ builtin/sparse-checkout.c: static int sparse_checkout_init(int argc, const char
     +		char *pattern = sl.items[i].string;
     +
     +		if (strlen(pattern))
    -+			fprintf(fp, "/%s/\n!/%s/*/\n", pattern, pattern);
    ++			fprintf(fp, "%s/\n!%s/*/\n", pattern, pattern);
     +	}
     +
     +	string_list_clear(&sl, 0);
     +
    -+	hashmap_iter_init(&pl->recursive_hashmap, &iter);
    -+	while ((entry = hashmap_iter_next(&iter)))
    -+		string_list_insert(&sl, entry->pattern);
    ++	hashmap_for_each_entry(&pl->recursive_hashmap, &iter, pe, ent)
    ++		string_list_insert(&sl, pe->pattern);
     +
     +	string_list_sort(&sl);
     +	string_list_remove_duplicates(&sl, 0);
     +
     +	for (i = 0; i < sl.nr; i++) {
     +		char *pattern = sl.items[i].string;
    -+		fprintf(fp, "/%s/\n", pattern);
    ++		fprintf(fp, "%s/\n", pattern);
     +	}
     +}
     +
    @@ builtin/sparse-checkout.c: static int write_patterns_and_update(struct pattern_l
      	fclose(fp);
      	free(sparse_filename);
      
    -@@ builtin/sparse-checkout.c: static int write_patterns_and_update(struct pattern_list *pl)
      	return update_working_directory();
      }
      
    @@ builtin/sparse-checkout.c: static int write_patterns_and_update(struct pattern_l
     +	if (!line->len)
     +		return;
     +
    -+	if (line->buf[0] == '/')
    -+		strbuf_remove(line, 0, 1);
    -+
    -+	if (!line->len)
    -+		return;
    ++	if (line->buf[0] != '/')
    ++		strbuf_insert(line, 0, "/", 1);
     +
     +	insert_recursive_pattern(pl, line);
     +}
    @@ builtin/sparse-checkout.c: static int sparse_checkout_set(int argc, const char *
     -		while (!strbuf_getline(&line, stdin)) {
     -			size_t len;
     -			char *buf = strbuf_detach(&line, &len);
    --			add_pattern(buf, buf, len, &pl, 0);
    +-			add_pattern(buf, empty_base, 0, &pl, 0);
     +		hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0);
     +		hashmap_init(&pl.parent_hashmap, pl_hashmap_cmp, NULL, 0);
     +
    @@ builtin/sparse-checkout.c: static int sparse_checkout_set(int argc, const char *
      		}
      	} else {
     -		for (i = 0; i < argc; i++)
    --			add_pattern(argv[i], argv[i], strlen(argv[i]), &pl, 0);
    +-			add_pattern(argv[i], empty_base, 0, &pl, 0);
     +		if (set_opts.use_stdin) {
     +			struct strbuf line = STRBUF_INIT;
     +
     +			while (!strbuf_getline(&line, stdin)) {
     +				size_t len;
     +				char *buf = strbuf_detach(&line, &len);
    -+				add_pattern(buf, buf, len, &pl, 0);
    ++				add_pattern(buf, empty_base, 0, &pl, 0);
     +			}
     +		} else {
     +			for (i = 0; i < argc; i++)
    -+				add_pattern(argv[i], argv[i], strlen(argv[i]), &pl, 0);
    ++				add_pattern(argv[i], empty_base, 0, &pl, 0);
     +		}
      	}
      
    - 	return write_patterns_and_update(&pl);
    -@@ builtin/sparse-checkout.c: static int sparse_checkout_disable(int argc, const char **argv)
    - 	char *sparse_filename;
    - 	FILE *fp;
    - 
    --	if (sc_set_config(1))
    -+	if (sc_set_config(SPARSE_CHECKOUT_FULL))
    - 		die(_("failed to change config"));
    - 
    - 	sparse_filename = get_sparse_checkout_filename();
    -@@ builtin/sparse-checkout.c: static int sparse_checkout_disable(int argc, const char **argv)
    - 	unlink(sparse_filename);
    - 	free(sparse_filename);
    - 
    --	return sc_set_config(0);
    -+	return sc_set_config(SPARSE_CHECKOUT_NONE);
    - }
    - 
    - int cmd_sparse_checkout(int argc, const char **argv, const char *prefix)
    + 	if (!core_apply_sparse_checkout) {
     
      ## dir.c ##
     @@ dir.c: void parse_path_pattern(const char **pattern,
    @@ dir.c: void parse_path_pattern(const char **pattern,
      }
      
     -static int pl_hashmap_cmp(const void *unused_cmp_data,
    --			  const void *a, const void *b, const void *key)
    +-			  const struct hashmap_entry *a,
    +-			  const struct hashmap_entry *b,
    +-			  const void *key)
     +int pl_hashmap_cmp(const void *unused_cmp_data,
    -+		   const void *a, const void *b, const void *key)
    ++		   const struct hashmap_entry *a,
    ++		   const struct hashmap_entry *b,
    ++		   const void *key)
      {
    - 	const struct pattern_entry *ee1 = (const struct pattern_entry *)a;
    - 	const struct pattern_entry *ee2 = (const struct pattern_entry *)b;
    + 	const struct pattern_entry *ee1 =
    + 			container_of(a, struct pattern_entry, ent);
     
      ## dir.h ##
     @@ dir.h: int is_excluded(struct dir_struct *dir,
    @@ dir.h: int is_excluded(struct dir_struct *dir,
      		const char *name, int *dtype);
      
     +int pl_hashmap_cmp(const void *unused_cmp_data,
    -+		   const void *a, const void *b, const void *key);
    -+
    - struct pattern_list *add_pattern_list(struct dir_struct *dir,
    - 				      int group_type, const char *src);
    - int add_patterns_from_file_to_list(const char *fname, const char *base, int baselen,
    ++		   const struct hashmap_entry *a,
    ++		   const struct hashmap_entry *b,
    ++		   const void *key);
    + int hashmap_contains_parent(struct hashmap *map,
    + 			    const char *path,
    + 			    struct strbuf *buffer);
     
      ## t/t1091-sparse-checkout-builtin.sh ##
     @@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'sparse-checkout disable' '
    @@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'sparse-checkout disable
     +		a
     +		deep
     +	EOF
    ++	test_cmp expect dir &&
     +	ls repo/deep >dir  &&
     +	cat >expect <<-EOF &&
     +		a
     +		deeper1
     +	EOF
    ++	test_cmp expect dir &&
     +	ls repo/deep/deeper1 >dir  &&
     +	cat >expect <<-EOF &&
     +		a
    @@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'sparse-checkout disable
     +'
     +
      test_done
    - 
40:  1d4321488e ! 11:  b2522a9757 unpack-trees: hash less in cone mode
    @@ Commit message
         value.
     
         Signed-off-by: Derrick Stolee <[email protected]>
    +    Signed-off-by: Junio C Hamano <[email protected]>
     
      ## dir.c ##
     @@ dir.c: enum pattern_match_result path_matches_pattern_list(
      
      	if (hashmap_contains_path(&pl->recursive_hashmap,
    - 					&parent_pathname)) {
    + 				  &parent_pathname)) {
     -		result = MATCHED;
     +		result = MATCHED_RECURSIVE;
      		goto done;
      	}
      
     @@ dir.c: enum pattern_match_result path_matches_pattern_list(
    - 	while (parent_pathname.len) {
    - 		if (hashmap_contains_path(&pl->recursive_hashmap,
    - 					  &parent_pathname)) {
    --			result = UNDECIDED;
    -+			result = MATCHED_RECURSIVE;
    - 			goto done;
    - 		}
    + 	if (hashmap_contains_parent(&pl->recursive_hashmap,
    + 				    pathname,
    + 				    &parent_pathname))
    +-		result = MATCHED;
    ++		result = MATCHED_RECURSIVE;
      
    + done:
    + 	strbuf_release(&parent_pathname);
     
      ## dir.h ##
     @@ dir.h: enum pattern_match_result {
41:  f89c75bfae <  -:  ---------- fixup! unpack-trees: hash less in cone mode
42:  7be46af2d7 <  -:  ---------- fixup! Merge branch 'sparse-checkout/v1'
43:  73b100d11d <  -:  ---------- fixup! sparse-checkout: use hashmaps for cone patterns
44:  3a07dd6671 ! 12:  705f31cb7b unpack-trees: add progress to clear_ce_flags()
    @@ Commit message
         clear_ce_flags() method to write progress.
     
         Signed-off-by: Derrick Stolee <[email protected]>
    +    Signed-off-by: Junio C Hamano <[email protected]>
     
      ## cache.h ##
     @@ cache.h: static inline unsigned int canon_mode(unsigned int mode)
45:  cb32d7e620 ! 13:  4326bc1811 read-tree: show progress by default
    @@ Commit message
         benefit from progress indicators when a user runs these commands.
     
         Signed-off-by: Derrick Stolee <[email protected]>
    +    Signed-off-by: Junio C Hamano <[email protected]>
     
      ## builtin/read-tree.c ##
     @@ builtin/read-tree.c: int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
46:  577e7fa8f3 <  -:  ---------- sparse-checkout: sanitize for nested folders
47:  f58c8f42e4 <  -:  ---------- fsmonitor: don't fill bitmap with entries to be removed
 -:  ---------- > 14:  f9e96bf90e sparse-checkout: sanitize for nested folders
56:  ab65de39f7 ! 15:  efd9c53b6d sparse-checkout: update working directory in-process
    @@ Metadata
      ## Commit message ##
         sparse-checkout: update working directory in-process
     
    +    The sparse-checkout builtin used 'git read-tree -mu HEAD' to update the
    +    skip-worktree bits in the index and to update the working directory.
    +    This extra process is overly complex, and prone to failure. It also
    +    requires that we write our changes to the sparse-checkout file before
    +    trying to update the index.
    +
    +    Remove this extra process call by creating a direct call to
    +    unpack_trees() in the same way 'git read-tree -mu HEAD' does. In
    +    addition, provide an in-memory list of patterns so we can avoid
    +    reading from the sparse-checkout file. This allows us to test a
    +    proposed change to the file before writing to it.
    +
    +    An earlier version of this patch included a bug when the 'set' command
    +    failed due to the "Sparse checkout leaves no entry on working directory"
    +    error. It would not rollback the index.lock file, so the replay of the
    +    old sparse-checkout specification would fail. A test in t1091 now
    +    covers that scenario.
    +
         Signed-off-by: Derrick Stolee <[email protected]>
    +    Signed-off-by: Junio C Hamano <[email protected]>
     
      ## builtin/read-tree.c ##
     @@ builtin/read-tree.c: int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
    @@ builtin/sparse-checkout.c
     +#include "unpack-trees.h"
      
      static char const * const builtin_sparse_checkout_usage[] = {
    - 	N_("git sparse-checkout [init|list|set|disable] <options>"),
    + 	N_("git sparse-checkout (init|list|set|disable) <options>"),
     @@ builtin/sparse-checkout.c: static int sparse_checkout_list(int argc, const char **argv)
      	return 0;
      }
    @@ builtin/sparse-checkout.c: static int sparse_checkout_list(int argc, const char
     -	if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
     -		error(_("failed to update index with new sparse-checkout paths"));
     -		result = 1;
    +-	}
     +	if (repo_read_index_unmerged(r))
     +		die(_("You need to resolve your current index first"));
     +
    @@ builtin/sparse-checkout.c: static int sparse_checkout_list(int argc, const char
     +	if (!result) {
     +		prime_cache_tree(r, r->index, tree);
     +		write_locked_index(r->index, &lock_file, COMMIT_LOCK);
    - 	}
    ++	} else
    ++		rollback_lock_file(&lock_file);
      
     -	argv_array_clear(&argv);
      	return result;
    @@ builtin/sparse-checkout.c: static int sparse_checkout_init(int argc, const char
      			     builtin_sparse_checkout_init_options,
      			     builtin_sparse_checkout_init_usage, 0);
      
    --	mode = init_opts.cone_mode ? SPARSE_CHECKOUT_CONE : SPARSE_CHECKOUT_FULL;
    +-	mode = init_opts.cone_mode ? MODE_CONE_PATTERNS : MODE_ALL_PATTERNS;
     +	if (init_opts.cone_mode) {
    -+		mode = SPARSE_CHECKOUT_CONE;
    ++		mode = MODE_CONE_PATTERNS;
     +		core_sparse_checkout_cone = 1;
     +	} else
    -+		mode = SPARSE_CHECKOUT_FULL;
    ++		mode = MODE_ALL_PATTERNS;
      
      	if (sc_set_config(mode))
      		return 1;
    @@ builtin/sparse-checkout.c: static int sparse_checkout_init(int argc, const char
      }
      
      static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *path)
    - {
    --	struct pattern_entry *e = xmalloc(sizeof(struct pattern_entry));
    -+	struct pattern_entry *e = xmalloc(sizeof(*e));
    -+
    - 	e->patternlen = path->len;
    - 	e->pattern = strbuf_detach(path, NULL);
    - 	hashmap_entry_init(e, memhash(e->pattern, e->patternlen));
     @@ builtin/sparse-checkout.c: static int write_patterns_and_update(struct pattern_list *pl)
      {
      	char *sparse_filename;
      	FILE *fp;
    -+	int result = update_working_directory(pl);
    ++	int result;
    ++
    ++	result = update_working_directory(pl);
     +
     +	if (result) {
     +		clear_pattern_list(pl);
    @@ builtin/sparse-checkout.c: static int write_patterns_and_update(struct pattern_l
      		write_patterns_to_file(fp, pl);
      
      	fclose(fp);
    --	free(sparse_filename);
    ++
    + 	free(sparse_filename);
    ++	clear_pattern_list(pl);
      
    -+	free(sparse_filename);
    - 	clear_pattern_list(pl);
     -	return update_working_directory();
    -+
     +	return 0;
      }
      
      static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl)
    -@@ builtin/sparse-checkout.c: static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl)
    - 	if (line->buf[0] == '/')
    - 		strbuf_remove(line, 0, 1);
    - 
    --	if (!line->len)
    --		return;
    --
    - 	insert_recursive_pattern(pl, line);
    - }
    - 
    -@@ builtin/sparse-checkout.c: static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
    - {
    - 	int i;
    - 	struct pattern_list pl;
    -+	static const char *empty_base = "";
    - 
    - 	static struct option builtin_sparse_checkout_set_options[] = {
    - 		OPT_BOOL(0, "stdin", &set_opts.use_stdin,
     @@ builtin/sparse-checkout.c: static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
      		struct strbuf line = STRBUF_INIT;
      		hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0);
    @@ builtin/sparse-checkout.c: static int sparse_checkout_set(int argc, const char *
      
      		if (set_opts.use_stdin) {
      			while (!strbuf_getline(&line, stdin))
    -@@ builtin/sparse-checkout.c: static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
    - 			while (!strbuf_getline(&line, stdin)) {
    - 				size_t len;
    - 				char *buf = strbuf_detach(&line, &len);
    --				add_pattern(buf, buf, len, &pl, 0);
    -+				add_pattern(buf, empty_base, 0, &pl, 0);
    - 			}
    - 		} else {
    - 			for (i = 0; i < argc; i++)
    --				add_pattern(argv[i], argv[i], strlen(argv[i]), &pl, 0);
    -+				add_pattern(argv[i], empty_base, 0, &pl, 0);
    - 		}
    - 	}
    - 
     @@ builtin/sparse-checkout.c: static int sparse_checkout_disable(int argc, const char **argv)
      	fprintf(fp, "/*\n");
      	fclose(fp);
      
     -	if (update_working_directory())
     +	core_apply_sparse_checkout = 1;
    -+	core_sparse_checkout_cone = 0;
     +	if (update_working_directory(NULL))
      		die(_("error while refreshing working directory"));
      
      	unlink(sparse_filename);
     
      ## t/t1091-sparse-checkout-builtin.sh ##
    -@@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'cone mode: init and set' '
    - 		a
    - 		deep
    - 	EOF
    -+	test_cmp dir expect &&
    - 	ls repo/deep >dir  &&
    - 	cat >expect <<-EOF &&
    - 		a
    - 		deeper1
    - 	EOF
    -+	test_cmp dir expect &&
    - 	ls repo/deep/deeper1 >dir  &&
    - 	cat >expect <<-EOF &&
    - 		a
    +@@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'cone mode: set with nested folders' '
    + 	test_cmp repo/.git/info/sparse-checkout expect
    + '
    + 
    ++test_expect_success 'revert to old sparse-checkout on bad update' '
    ++	echo update >repo/deep/deeper2/a &&
    ++	cp repo/.git/info/sparse-checkout expect &&
    ++	test_must_fail git -C repo sparse-checkout set deep/deeper1 2>err &&
    ++	test_i18ngrep "Cannot update sparse checkout" err &&
    ++	test_cmp repo/.git/info/sparse-checkout expect &&
    ++	ls repo/deep >dir &&
    ++	cat >expect <<-EOF &&
    ++		a
    ++		deeper1
    ++		deeper2
    ++	EOF
    ++	test_cmp dir expect
    ++'
    ++
    ++test_expect_success 'revert to old sparse-checkout on empty update' '
    ++	git init empty-test &&
    ++	(
    ++		echo >file &&
    ++		git add file &&
    ++		git commit -m "test" &&
    ++		test_must_fail git sparse-checkout set nothing 2>err &&
    ++		test_i18ngrep "Sparse checkout leaves no entry on working directory" err &&
    ++		test_i18ngrep ! ".git/index.lock" err &&
    ++		git sparse-checkout set file
    ++	)
    ++'
    ++
    + test_done
     
      ## unpack-trees.c ##
     @@ unpack-trees.c: int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
    @@ unpack-trees.c: int unpack_trees(unsigned len, struct tree_desc *t, struct unpac
      		o->skip_sparse_checkout = 1;
     -	if (!o->skip_sparse_checkout) {
     +	if (!o->skip_sparse_checkout && !o->pl) {
    - 		if (core_virtualfilesystem) {
    - 			o->pl = &pl;
    - 		} else {
    + 		char *sparse = git_pathdup("info/sparse-checkout");
    + 		pl.use_cone_patterns = core_sparse_checkout_cone;
    + 		if (add_patterns_from_file_to_list(sparse, "", 0, &pl, NULL) < 0)
     @@ unpack-trees.c: int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
      
      done:
    @@ unpack-trees.c: int unpack_trees(unsigned len, struct tree_desc *t, struct unpac
     -	clear_pattern_list(&pl);
     +	if (!o->keep_pattern_list)
     +		clear_pattern_list(&pl);
    - 	trace2_data_intmax("unpack_trees", NULL, "unpack_trees/nr_unpack_entries",
    - 			   (intmax_t)(get_nr_unpack_entry() - nr_unpack_entry_at_start));
    - 	trace2_region_leave("unpack_trees", "unpack_trees", NULL);
    + 	return ret;
    + 
    + return_failed:
     
      ## unpack-trees.h ##
     @@ unpack-trees.h: struct unpack_trees_options {
60:  c963314c01 ! 16:  a879c5bfb5 sparse-checkout: write using lockfile
    @@ Commit message
         ways.
     
         Signed-off-by: Derrick Stolee <[email protected]>
    +    Signed-off-by: Junio C Hamano <[email protected]>
     
      ## builtin/sparse-checkout.c ##
     @@ builtin/sparse-checkout.c: static int write_patterns_and_update(struct pattern_list *pl)
      {
      	char *sparse_filename;
      	FILE *fp;
    --	int result = update_working_directory(pl);
     +	int fd;
     +	struct lock_file lk = LOCK_INIT;
    -+	int result;
    + 	int result;
    + 
    + 	result = update_working_directory(pl);
      
     +	sparse_filename = get_sparse_checkout_filename();
     +	fd = hold_lock_file_for_update(&lk, sparse_filename,
    @@ builtin/sparse-checkout.c: static int write_patterns_and_update(struct pattern_l
      
     -	sparse_filename = get_sparse_checkout_filename();
     -	fp = fopen(sparse_filename, "w");
    -+	fp = fdopen(fd, "w");
    ++	fp = xfdopen(fd, "w");
      
      	if (core_sparse_checkout_cone)
      		write_cone_to_file(fp, pl);
    @@ builtin/sparse-checkout.c: static int write_patterns_and_update(struct pattern_l
      
      	free(sparse_filename);
      	clear_pattern_list(pl);
    +
    + ## t/t1091-sparse-checkout-builtin.sh ##
    +@@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'revert to old sparse-checkout on empty update' '
    + 	)
    + '
    + 
    ++test_expect_success 'fail when lock is taken' '
    ++	test_when_finished rm -rf repo/.git/info/sparse-checkout.lock &&
    ++	touch repo/.git/info/sparse-checkout.lock &&
    ++	test_must_fail git -C repo sparse-checkout set deep 2>err &&
    ++	test_i18ngrep "File exists" err
    ++'
    ++
    + test_done
 -:  ---------- > 17:  20e7bd3da4 sparse-checkout: cone mode should not interact with .gitignore
73:  bfc76dc9e0 = 18:  3641e26973 fsmonitor: fix watchman integration
48:  aeedc362a2 = 19:  5a5aefda1d credential: set trace2_child_class for credential manager children
49:  3598c80600 = 20:  fc9c77fe77 sub-process: do not borrow cmd pointer from caller
50:  51f82404dd ! 21:  3019adb067 sub-process: add subprocess_start_argv()
    @@ sub-process.c
     +#include "quote.h"
      
      int cmd2process_cmp(const void *unused_cmp_data,
    - 		    const void *entry,
    + 		    const struct hashmap_entry *eptr,
     @@ sub-process.c: int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, co
      	return 0;
      }
    @@ sub-process.c: int subprocess_start(struct hashmap *hashmap, struct subprocess_e
     +		return err;
     +	}
     +
    -+	hashmap_entry_init(entry, strhash(entry->cmd));
    ++	hashmap_entry_init(&entry->ent, strhash(entry->cmd));
     +
     +	err = startfn(entry);
     +	if (err) {
    @@ sub-process.c: int subprocess_start(struct hashmap *hashmap, struct subprocess_e
     +		return err;
     +	}
     +
    -+	hashmap_add(hashmap, entry);
    ++	hashmap_add(hashmap, &entry->ent);
     +	return 0;
     +}
     +
51:  e64a86d9e1 = 22:  15d8415ded sha1-file: add function to update existing loose object cache
52:  ed87030bd6 = 23:  8259260346 packfile: add install_packed_git_and_mru()
53:  3a1b48822a = 24:  abfc0a4022 index-pack: avoid immediate object fetch while parsing packfile
54:  80ead377e6 ! 25:  706d2aec7a gvfs-helper: create tool to fetch objects using the GVFS Protocol
    @@ Documentation/config/core.txt: core.gvfs::
     +	TODO
     +
      core.sparseCheckout::
    - 	Enable "sparse checkout" feature. If "false", then sparse-checkout
    - 	is disabled. If "true", then sparse-checkout is enabled with the full
    + 	Enable "sparse checkout" feature. See linkgit:git-sparse-checkout[1]
    + 	for more information.
     
      ## Documentation/config/gvfs.txt (new) ##
     @@
    @@ Makefile: $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS
      	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
      
     
    - ## builtin/index-pack.c ##
    -@@ builtin/index-pack.c: static void fix_unresolved_deltas(struct hashfile *f)
    - 		sorted_by_pos[i] = &ref_deltas[i];
    - 	QSORT(sorted_by_pos, nr_ref_deltas, delta_pos_compare);
    - 
    --	if (repository_format_partial_clone) {
    -+	if (repository_format_partial_clone || core_use_gvfs_helper) {
    - 		/*
    - 		 * Prefetch the delta bases.
    - 		 */
    -
      ## cache.h ##
     @@ cache.h: extern int precomposed_unicode;
      extern int protect_hfs;
    @@ cache.h: extern int precomposed_unicode;
     +extern const char *gvfs_cache_server_url;
     +extern const char *gvfs_shared_cache_pathname;
      
    - int core_apply_sparse_checkout;
    - int core_sparse_checkout_cone;
    + extern int core_apply_sparse_checkout;
    + extern int core_sparse_checkout_cone;
     
      ## config.c ##
     @@
    @@ config.c: int git_default_config(const char *var, const char *value, void *cb)
      	return 0;
      }
     
    - ## diff.c ##
    -@@ diff.c: static void add_if_missing(struct repository *r,
    - void diffcore_std(struct diff_options *options)
    - {
    - 	if (options->repo == the_repository &&
    --	    repository_format_partial_clone) {
    -+	    (repository_format_partial_clone || core_use_gvfs_helper)) {
    - 		/*
    - 		 * Prefetch the diff pairs that are about to be flushed.
    - 		 */
    -
      ## environment.c ##
     @@ environment.c: int protect_hfs = PROTECT_HFS_DEFAULT;
      #endif
    @@ environment.c: int protect_hfs = PROTECT_HFS_DEFAULT;
      /*
       * The character that begins a commented line in user-editable file
     
    - ## fetch-object.c ##
    -@@
    - #include "strbuf.h"
    - #include "transport.h"
    - #include "fetch-object.h"
    -+#include "gvfs-helper-client.h"
    - 
    - static void fetch_refs(const char *remote_name, struct ref *ref)
    - {
    -@@ fetch-object.c: void fetch_objects(const char *remote_name, const struct object_id *oids,
    - 	struct ref *ref = NULL;
    - 	int i;
    - 
    -+	if (core_use_gvfs_helper) {
    -+		enum ghc__created ghc = GHC__CREATED__NOTHING;
    -+
    -+		ghc__queue_oid_array(oids, oid_nr);
    -+		ghc__drain_queue(&ghc);
    -+		return;
    -+	}
    -+
    - 	for (i = 0; i < oid_nr; i++) {
    - 		struct ref *new_ref = alloc_ref(oid_to_hex(&oids[i]));
    - 		oidcpy(&new_ref->old_oid, &oids[i]);
    -
      ## gvfs-helper-client.c (new) ##
     @@
     +#include "cache.h"
    -+#include "commit.h"
     +#include "argv-array.h"
     +#include "trace2.h"
    -+#include "progress.h"
     +#include "oidset.h"
    -+#include "revision.h"
    -+#include "list-objects.h"
    -+#include "list-objects-filter.h"
    -+#include "list-objects-filter-options.h"
     +#include "object.h"
     +#include "object-store.h"
    -+#include "bisect.h"
     +#include "gvfs-helper-client.h"
     +#include "sub-process.h"
     +#include "sigchain.h"
    @@ gvfs-helper.c (new)
     +	return ec;
     +}
     
    + ## promisor-remote.c ##
    +@@
    + #include "promisor-remote.h"
    + #include "config.h"
    + #include "transport.h"
    ++#include "gvfs-helper-client.h"
    + 
    + static char *repository_format_partial_clone;
    + static const char *core_partial_clone_filter_default;
    +@@ promisor-remote.c: static int fetch_objects(const char *remote_name,
    + 	struct ref *ref = NULL;
    + 	int i;
    + 
    ++
    + 	for (i = 0; i < oid_nr; i++) {
    + 		struct ref *new_ref = alloc_ref(oid_to_hex(&oids[i]));
    + 		oidcpy(&new_ref->old_oid, &oids[i]);
    +@@ promisor-remote.c: struct promisor_remote *promisor_remote_find(const char *remote_name)
    + 
    + int has_promisor_remote(void)
    + {
    +-	return !!promisor_remote_find(NULL);
    ++	return core_use_gvfs_helper || !!promisor_remote_find(NULL);
    + }
    + 
    + static int remove_fetched_oids(struct repository *repo,
    +@@ promisor-remote.c: int promisor_remote_get_direct(struct repository *repo,
    + 	int to_free = 0;
    + 	int res = -1;
    + 
    ++	if (core_use_gvfs_helper) {
    ++		enum gh_client__created ghc = GHC__CREATED__NOTHING;
    ++
    ++		trace2_data_intmax("bug", the_repository, "fetch_objects/gvfs-helper", oid_nr);
    ++		gh_client__queue_oid_array(oids, oid_nr);
    ++		return gh_client__drain_queue(&ghc);
    ++	}
    ++
    + 	promisor_remote_init();
    + 
    + 	for (r = promisors; r; r = r->next) {
    +
      ## sha1-file.c ##
     @@
      #include "sigchain.h"
    @@ sha1-file.c: int oid_object_info_extended(struct repository *r, const struct obj
      				if (!read_object_process(oid))
      					goto retry;
      			}
    -
    - ## unpack-trees.c ##
    -@@ unpack-trees.c: static int check_updates(struct unpack_trees_options *o)
    - 		load_gitmodules_file(index, &state);
    - 
    - 	enable_delayed_checkout(&state);
    --	if (repository_format_partial_clone && o->update && !o->dry_run) {
    -+	if ((repository_format_partial_clone || core_use_gvfs_helper) &&
    -+	    o->update && !o->dry_run) {
    - 		/*
    - 		 * Prefetch the objects that are to be checked out in the loop
    - 		 * below.
55:  a9e428fb23 = 26:  73d2d384fd gvfs-helper: fix race condition when creating loose object dirs
57:  fa5c5118f3 <  -:  ---------- sparse-checkout: allow cone-type patterns for full tree
58:  33afd5ed5a <  -:  ---------- sparse-checkout: back out a change with conflicts
59:  15ede03e7a <  -:  ---------- fixup! sparse-checkout: init and set in cone mode
61:  5d39b08ded <  -:  ---------- fixup! gvfs-helper: create tool to fetch objects using the GVFS Protocol
62:  205244f13c ! 27:  7f68d1da5f sha1-file: create shared-cache directory if it doesn't exist
    @@ cache.h: extern int protect_ntfs;
     -extern const char *gvfs_shared_cache_pathname;
     +extern struct strbuf gvfs_shared_cache_pathname;
      
    - int core_apply_sparse_checkout;
    - int core_sparse_checkout_cone;
    + extern int core_apply_sparse_checkout;
    + extern int core_sparse_checkout_cone;
     
      ## config.c ##
     @@ config.c: static int git_default_gvfs_config(const char *var, const char *value)
63:  6317a4e5ed = 28:  c478dfbc2c gvfs-helper-client: rename ghs__ symbols to gh_server__
64:  529fbc44be = 29:  1c31a8c12e gvfs-helper-client: rename ghs__chosen_odb to gh_client__chosen_odb for clarity
65:  52eb2b630d ! 30:  16e40b32af gvfs-helper-client: rename ghc__ symbols to gh_client__ for clarity
    @@ Commit message
     
         Signed-off-by: Jeff Hostetler <[email protected]>
     
    - ## fetch-object.c ##
    -@@ fetch-object.c: void fetch_objects(const char *remote_name, const struct object_id *oids,
    - 	int i;
    - 
    - 	if (core_use_gvfs_helper) {
    --		enum ghc__created ghc = GHC__CREATED__NOTHING;
    -+		enum gh_client__created ghc = GHC__CREATED__NOTHING;
    - 
    --		ghc__queue_oid_array(oids, oid_nr);
    --		ghc__drain_queue(&ghc);
    -+		gh_client__queue_oid_array(oids, oid_nr);
    -+		gh_client__drain_queue(&ghc);
    - 		return;
    - 	}
    - 
    -
      ## gvfs-helper-client.c ##
     @@
      #include "quote.h"
66:  f996ff75aa <  -:  ---------- gvfs-helper: better handling of network errors
68:  e0ff373d83 ! 31:  617fd0555e fixup! gvfs-helper: better handling of network errors
    @@ Metadata
     Author: Jeff Hostetler <[email protected]>
     
      ## Commit message ##
    -    fixup! gvfs-helper: better handling of network errors
    +    gvfs-helper: better handling of network errors
    +
    +    Add trace2 message for CURL and HTTP errors.
    +
    +    Fix typo reporting network error code back to gvfs-helper-client.
     
         Signed-off-by: Jeff Hostetler <[email protected]>
     
      ## gvfs-helper.c ##
    +@@ gvfs-helper.c: static void gh__response_status__set_from_slot(
    + 		strbuf_addf(&status->error_message, "%s (curl)",
    + 			    curl_easy_strerror(status->curl_code));
    + 		status->ec = GH__ERROR_CODE__CURL_ERROR;
    ++
    ++		trace2_data_string("gvfs-helper", NULL,
    ++				   "error/curl", status->error_message.buf);
    + 	} else {
    + 		strbuf_addf(&status->error_message, "HTTP %ld Unexpected",
    + 			    status->response_code);
    + 		status->ec = GH__ERROR_CODE__HTTP_UNEXPECTED_CODE;
    ++
    ++		trace2_data_string("gvfs-helper", NULL,
    ++				   "error/http", status->error_message.buf);
    + 	}
    + 
    + 	if (status->ec != GH__ERROR_CODE__OK)
     @@ gvfs-helper.c: static enum gh__error_code do_sub_cmd__get(int argc, const char **argv)
      }
      
    @@ gvfs-helper.c: static enum gh__error_code do_server_subprocess_get(void)
      			goto cleanup;
      		}
      
    --	ec = status.ec;
     +	/*
     +	 * We only use status.ec to tell the client whether the request
     +	 * was complete, incomplete, or had IO errors.  We DO NOT return
67:  74c49a1c4e = 32:  891a51b40c gvfs-helper-client: properly update loose cache with fetched OID
69:  b9ef9f40de <  -:  ---------- sparse-checkout: update locking pattern
70:  bd71c8d79b = 33:  df4a8db155 gvfs-helper: V2 robust retry and throttling
71:  5c65e9a00a = 34:  ae0c9ccdfe gvfs-helper: expose gvfs/objects GET and POST semantics
72:  6e92f6a4d8 ! 35:  740de11def gvfs-helper: dramatically reduce progress noise
    @@ gvfs-helper.c: static void setup_gvfs_objects_progress(struct gh__request_params
      }
      
      /*
    +
    + ## unpack-trees.c ##
    +@@ unpack-trees.c: int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
    + 	memset(&pl, 0, sizeof(pl));
    + 	if (!core_apply_sparse_checkout || !o->update)
    + 		o->skip_sparse_checkout = 1;
    +-	if (!o->skip_sparse_checkout) {
    ++	if (!o->skip_sparse_checkout && !o->pl) {
    + 		if (core_virtualfilesystem) {
    + 			o->pl = &pl;
    + 		} else {
 -:  ---------- > 36:  8cf9286267 gvfs-helper-client.h: define struct object_id

derrickstolee and others added 17 commits October 23, 2019 12:42
The sparse-checkout feature is mostly hidden to users, as its
only documentation is supplementary information in the docs for
'git read-tree'. In addition, users need to know how to edit the
.git/info/sparse-checkout file with the right patterns, then run
the appropriate 'git read-tree -mu HEAD' command. Keeping the
working directory in sync with the sparse-checkout file requires
care.

Begin an effort to make the sparse-checkout feature a porcelain
feature by creating a new 'git sparse-checkout' builtin. This
builtin will be the preferred mechanism for manipulating the
sparse-checkout file and syncing the working directory.

The documentation provided is adapted from the "git read-tree"
documentation with a few edits for clarity in the new context.
Extra sections are added to hint toward a future change to
a more restricted pattern set.

Helped-by: Elijah Newren <[email protected]>
Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
Getting started with a sparse-checkout file can be daunting. Help
users start their sparse enlistment using 'git sparse-checkout init'.
This will set 'core.sparseCheckout=true' in their config, write
an initial set of patterns to the sparse-checkout file, and update
their working directory.

Make sure to use the `extensions.worktreeConfig` setting and write
the sparse checkout config to the worktree-specific config file.
This avoids confusing interactions with other worktrees.

The use of running another process for 'git read-tree' is sub-
optimal. This will be removed in a later change.

Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
When someone wants to clone a large repository, but plans to work
using a sparse-checkout file, they either need to do a full
checkout first and then reduce the patterns they included, or
clone with --no-checkout, set up their patterns, and then run
a checkout manually. This requires knowing a lot about the repo
shape and how sparse-checkout works.

Add a new '--sparse' option to 'git clone' that initializes the
sparse-checkout file to include the following patterns:

	/*
	!/*/

These patterns include every file in the root directory, but
no directories. This allows a repo to include files like a
README or a bootstrapping script to grow enlistments from that
point.

During the 'git sparse-checkout init' call, we must first look
to see if HEAD is valid, since 'git clone' does not have a valid
HEAD at the point where it initializes the sparse-checkout. The
following checkout within the clone command will create the HEAD
ref and update the working directory correctly.

Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
The 'git sparse-checkout set' subcommand takes a list of patterns
as arguments and writes them to the sparse-checkout file. Then, it
updates the working directory using 'git read-tree -mu HEAD'.

The 'set' subcommand will replace the entire contents of the
sparse-checkout file. The write_patterns_and_update() method is
extracted from cmd_sparse_checkout() to make it easier to implement
'add' and/or 'remove' subcommands in the future.

If the core.sparseCheckout config setting is disabled, then enable
the config setting in the worktree config. If we set the config
this way and the sparse-checkout fails, then re-disable the config
setting.

Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
The 'git sparse-checkout set' subcommand takes a list of patterns
and places them in the sparse-checkout file. Then, it updates the
working directory to match those patterns. For a large list of
patterns, the command-line call can get very cumbersome.

Add a '--stdin' option to instead read patterns over standard in.

Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
The instructions for disabling a sparse-checkout to a full
working directory are complicated and non-intuitive. Add a
subcommand, 'git sparse-checkout disable', to perform those
steps for the user.

Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
When Git updates the working directory with the sparse-checkout
feature enabled, the unpack_trees() method calls clear_ce_flags()
to update the skip-wortree bits on the cache entries. This
check can be expensive, depending on the patterns used.

Add trace2 regions around the method, including some flag
information, so we can get granular performance data during
experiments. This data will be used to measure improvements
to the pattern-matching algorithms for sparse-checkout.

Signed-off-by: Jeff Hostetler <[email protected]>
Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
The sparse-checkout feature can have quadratic performance as
the number of patterns and number of entries in the index grow.
If there are 1,000 patterns and 1,000,000 entries, this time can
be very significant.

Create a new Boolean config option, core.sparseCheckoutCone, to
indicate that we expect the sparse-checkout file to contain a
more limited set of patterns. This is a separate config setting
from core.sparseCheckout to avoid breaking older clients by
introducing a tri-state option.

The config option does nothing right now, but will be expanded
upon in a later commit.

Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
The parent and recursive patterns allowed by the "cone mode"
option in sparse-checkout are restrictive enough that we
can avoid using the regex parsing. Everything is based on
prefix matches, so we can use hashsets to store the prefixes
from the sparse-checkout file. When checking a path, we can
strip path entries from the path and check the hashset for
an exact match.

As a test, I created a cone-mode sparse-checkout file for the
Linux repository that actually includes every file. This was
constructed by taking every folder in the Linux repo and creating
the pattern pairs here:

	/$folder/
	!/$folder/*/

This resulted in a sparse-checkout file sith 8,296 patterns.
Running 'git read-tree -mu HEAD' on this file had the following
performance:

    core.sparseCheckout=false: 0.21 s (0.00 s)
     core.sparseCheckout=true: 3.75 s (3.50 s)
 core.sparseCheckoutCone=true: 0.23 s (0.01 s)

The times in parentheses above correspond to the time spent
in the first clear_ce_flags() call, according to the trace2
performance traces.

While this example is contrived, it demonstrates how these
patterns can slow the sparse-checkout feature.

Helped-by: Eric Wong <[email protected]>
Helped-by: Johannes Schindelin <[email protected]>
Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
To make the cone pattern set easy to use, update the behavior of
'git sparse-checkout [init|set]'.

Add '--cone' flag to 'git sparse-checkout init' to set the config
option 'core.sparseCheckoutCone=true'.

When running 'git sparse-checkout set' in cone mode, a user only
needs to supply a list of recursive folder matches. Git will
automatically add the necessary parent matches for the leading
directories.

When testing 'git sparse-checkout set' in cone mode, check the
error stream to ensure we do not see any errors. Specifically,
we want to avoid the warning that the patterns do not match
the cone-mode patterns.

Helped-by: Eric Wong <[email protected]>
Helped-by: Johannes Schindelin <[email protected]>
Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
The sparse-checkout feature in "cone mode" can use the fact that
the recursive patterns are "connected" to the root via parent
patterns to decide if a directory is entirely contained in the
sparse-checkout or entirely removed.

In these cases, we can skip hashing the paths within those
directories and simply set the skipworktree bit to the correct
value.

Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
When a large repository has many sparse-checkout patterns, the
process for updating the skip-worktree bits can take long enough
that a user gets confused why nothing is happening. Update the
clear_ce_flags() method to write progress.

Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
The read-tree builtin has a --verbose option that signals to show
progress and other data while updating the index. Update this to
be on by default when stderr is a terminal window.

This will help tools like 'git sparse-checkout' to automatically
benefit from progress indicators when a user runs these commands.

Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
If a user provides folders A/ and A/B/ for inclusion in a cone-mode
sparse-checkout file, the parsing logic will notice that A/ appears
both as a "parent" type pattern and as a "recursive" type pattern.
This is unexpected and hence will complain via a warning and revert
to the old logic for checking sparse-checkout patterns.

Prevent this from happening accidentally by sanitizing the folders
for this type of inclusion in the 'git sparse-checkout' builtin.
This happens in two ways:

1. Do not include any parent patterns that also appear as recursive
   patterns.

2. Do not include any recursive patterns deeper than other recursive
   patterns.

In order to minimize duplicate code for scanning parents, create
hashmap_contains_parent() method. It takes a strbuf buffer to
avoid reallocating a buffer when calling in a tight loop.

Helped-by: Eric Wong <[email protected]>
Helped-by: Johannes Schindelin <[email protected]>
Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
The sparse-checkout builtin used 'git read-tree -mu HEAD' to update the
skip-worktree bits in the index and to update the working directory.
This extra process is overly complex, and prone to failure. It also
requires that we write our changes to the sparse-checkout file before
trying to update the index.

Remove this extra process call by creating a direct call to
unpack_trees() in the same way 'git read-tree -mu HEAD' does. In
addition, provide an in-memory list of patterns so we can avoid
reading from the sparse-checkout file. This allows us to test a
proposed change to the file before writing to it.

An earlier version of this patch included a bug when the 'set' command
failed due to the "Sparse checkout leaves no entry on working directory"
error. It would not rollback the index.lock file, so the replay of the
old sparse-checkout specification would fail. A test in t1091 now
covers that scenario.

Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
If two 'git sparse-checkout set' subcommands are launched at the
same time, the behavior can be unexpected as they compete to write
the sparse-checkout file and update the working directory.

Take a lockfile around the writes to the sparse-checkout file. In
addition, acquire this lock around the working directory update
to avoid two commands updating the working directory in different
ways.

Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
During the development of the sparse-checkout "cone mode" feature,
an incorrect placement of the initializer for "use_cone_patterns = 1"
caused warnings to show up when a .gitignore file was present with
non-cone-mode patterns. This was fixed in the original commit
introducing the cone mode, but now we should add a test to avoid
hitting this problem again in the future.

Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
@wilbaker
Copy link
Member

wilbaker commented Oct 23, 2019

727b1180f7 fsmonitor: don't fill bitmap with entries to be removed

@derrickstolee FYI the fsmonitor fix just made it into master:

git@22dd22d

And that commit into master is after the 2.24-rc0 commit (git@d966095).

I can fix up our sparse-checkout feature branch to match the commit that was merged into git's master, but I wasn't sure about the best place to do it.

Would it be best for me to wait until we have a final features/sparse-checkout-2.24.0 branch and then do a PR to fix things up into there? Let me know what would be easiest for you, thanks!

@derrickstolee
Copy link
Author

727b118 fsmonitor: don't fill bitmap with entries to be removed

@derrickstolee FYI the fsmonitor fix just made it into master:

git@22dd22d

Awesome!

And that commit into master is after the 2.24-rc0 commit (git@d966095).

I can fix up our sparse-checkout feature branch to match the commit that was merged into git's master, but I wasn't sure about the best place to do it.

Would it be best for me to wait until we have a final features/sparse-checkout-2.24.0 branch and then do a PR to fix things up into there? Let me know what would be easiest for you, thanks!

This PR is just a test. I'll rebase on all of the release candidates. -rc1 will include your commit, so I'll drop it from here. The final features/sparse-checkout-2.24.0 will include v2.24.0, v2.24.0.windows.1, vfs-2.24.0, and the remaining sparse-checkout and gvfs-helper commits.

@wilbaker
Copy link
Member

This PR is just a test. I'll rebase on all of the release candidates. -rc1 will include your commit, so I'll drop it from here.

Ok thanks! In that case I won't submit a !fixup PR to take the version that's in git's master.

@derrickstolee derrickstolee force-pushed the tentative/features/sparse-checkout-2.24.0 branch from 436d25c to 4bda886 Compare October 25, 2019 17:32
@dscho
Copy link
Member

dscho commented Oct 28, 2019

+    Signed-off-by: Junio C Hamano <[email protected]>

I probably did the exact same thing in the past... but... maybe remove those, since you're the committer, not Junio?

Teach subprocess_start() to use a copy of the passed `cmd` string
rather than borrowing the buffer from the caller.

Some callers of subprocess_start() pass the value returned from
find_hook() which points to a static buffer and therefore is only
good until the next call to find_hook().  This could cause problems
for the long-running background processes managed by sub-process.c
where later calls to subprocess_find_entry() to get an existing
process will fail.  This could cause more than 1 long-running
process to be created.

TODO Need to confirm, but if only read_object_hook() uses
TODO subprocess_start() in this manner, we could drop this
TODO commit when we drop support for read_object_hook().

Signed-off-by: Jeff Hostetler <[email protected]>
Add function to start a subprocess with an argv.

Signed-off-by: Jeff Hostetler <[email protected]>
Create a function to add a new object to the loose object cache
after the existing odb/xx/ directory was scanned.

This will be used in a later commit to keep the loose object
cache fresh after dynamically fetching an individual object
and without requiring the odb/xx/ directory to be rescanned.

Signed-off-by: Jeff Hostetler <[email protected]>
Create a function to install a new packfile into the packed-git
list and add it to the head of the MRU list.

This function will be used in a later commit to install packfiles
created by dynamic object fetching.

Signed-off-by: Jeff Hostetler <[email protected]>
Prevent packfile parsing from accidentally dynamically fetching
each individual object found in the packfile.

When index-pack parses the input packfile, it does a lookup in the
ODB to test for conflicts/collisions.  This can accidentally cause
the object to be individually fetched when gvfs-helper (or
read-object-hook or partial-clone) is enabled.

Signed-off-by: Jeff Hostetler <[email protected]>
@derrickstolee derrickstolee force-pushed the tentative/features/sparse-checkout-2.24.0 branch from ff14bb3 to da561c2 Compare November 4, 2019 18:58
@derrickstolee derrickstolee changed the title [RC0] features/sparse-checkout-2.24.0 features/sparse-checkout-2.24.0 Nov 4, 2019
@derrickstolee derrickstolee changed the base branch from tentative/vfs-2.24.0 to features/sparse-checkout-2.24.0 November 4, 2019 19:04
@derrickstolee derrickstolee force-pushed the tentative/features/sparse-checkout-2.24.0 branch 3 times, most recently from ea9ee3d to 1169bc5 Compare November 5, 2019 14:08
jeffhostetler and others added 12 commits November 6, 2019 08:54
Create gvfs-helper.  This is a helper tool to use the GVFS Protocol
REST API to fetch objects and configuration data from a GVFS cache-server
or Git server.  This tool uses libcurl to send object requests to either
server.  This tool creates loose objects and/or packfiles.

Create gvfs-helper-client.  This code resides within git proper and
uses the sub-process API to manage gvfs-helper as a long-running background
process.

Signed-off-by: Jeff Hostetler <[email protected]>
When two gvfs-helper processes are the first to create a loose object
directory, the processes (A and B in the timeline below) could have
the following race:

1. A sees that the directory does not exist.
2. B sees that the directory does not exist.
3. A creates the directory with success.
4. B fails to create the directory and fails.

Instead of having B fail here, just check for the directory's
existence before reporting an error. That solves the race and
allows tests to pass.

Signed-off-by: Derrick Stolee <[email protected]>
The config variable `gvfs.sharedCache` contains the pathname to an alternate
<odb> that will be used by `gvfs-helper` to store dynamically-fetched missing
objects.  If this directory does not exist on disk, `prepare_alt_odb()` omits
this directory from the in-memory list of alternates.  This causes `git`
commands (and `gvfs-helper` in particular) to fall-back to `.git/objects` for
storage of these objects.  This disables the shared-cache and leads to poorer
performance.

Teach `alt_obj_usable()` and `prepare_alt_odb()`, match up the directory
named in `gvfs.sharedCache` with an entry in `.git/objects/info/alternates`
and force-create the `<odb>` root directory (and the associated `<odb>/pack`
directory) if necessary.

If the value of `gvfs.sharedCache` refers to a directory that is NOT listed
as an alternate, create an in-memory alternate entry in the odb-list.  (This
is similar to how GIT_ALTERNATE_OBJECT_DIRECTORIES works.)

This work happens the first time that `prepare_alt_odb()` is called.

Furthermore, teach the `--shared-cache=<odb>` command line option in
`gvfs-helper` (which is runs after the first call to `prepare_alt_odb()`)
to override the inherited shared-cache (and again, create the ODB directory
if necessary).

Signed-off-by: Jeff Hostetler <[email protected]>
Add trace2 message for CURL and HTTP errors.

Fix typo reporting network error code back to gvfs-helper-client.

Signed-off-by: Jeff Hostetler <[email protected]>
Fix parsing of the "loose <odb>" response from `gvfs-helper` and
use the actually parsed OID when updating the loose oid cache.

Previously, an uninitialized "struct oid" was used to update
the cache.  This did not cause any corruption, but could cause
extra fetches for objects visited multiple times.

Signed-off-by: Jeff Hostetler <[email protected]>
Add robust-retry mechanism to automatically retry a request after network
errors.  This includes retry after:
   [] transient network problems reported by CURL.
   [] http 429 throttling (with associated Retry-After)
   [] http 503 server unavailable (with associated Retry-After)

Add voluntary throttling using Azure X-RateLimit-* hints to avoid being
soft-throttled (tarpitted) or hard-throttled (429) on later requests.

Add global (outside of a single request) azure-throttle data to track the
rate limit hints from the cache-server and main Git server independently.

Add exponential retry backoff.  This is used for transient network problems
when we don't have a Retry-After hint.

Move the call to index-pack earlier in the response/error handling sequence
so that if we receive a 200 but yet the packfile is truncated/corrupted, we
can use the regular retry logic to get it again.

Refactor the way we create tempfiles for packfiles to use
<odb>/pack/tempPacks/ rather than working directly in the <odb>/pack/
directory.

Move the code to create a new tempfile to the start of a single request
attempt (initial and retry attempts), rather than at the overall start
of a request.  This gives us a fresh tempfile for each network request
attempt.  This simplifies the retry mechanism and isolates us from the file
ownership issues hidden within the tempfile class.  And avoids the need to
truncate previous incomplete results.  This was necessary because index-pack
was pulled into the retry loop.

Minor: Add support for logging X-VSS-E2EID to telemetry on network errors.

Minor: rename variable:
    params.b_no_cache_server --> params.b_permit_cache_server_if_defined.
This variable is used to indicate whether we should try to use the
cache-server when it is defined.  Got rid of double-negative logic.

Minor: rename variable:
    params.label --> params.tr2_label
Clarify that this variable is only used with trace2 logging.

Minor: Move the code to automatically map cache-server 400 responses
to normal 401 response earlier in the response/error handling sequence
to simplify later retry logic.

Minor: Decorate trace2 messages with "(cs)" or "(main)" to identify the
server in log messages.  Add params->server_type to simplify this.

Signed-off-by: Jeff Hostetler <[email protected]>
Expose the differences in the semantics of GET and POST for
the "gvfs/objects" API:

    HTTP GET: fetches a single loose object over the network.
              When a commit object is requested, it just returns
	      the single object.

    HTTP POST: fetches a batch of objects over the network.
               When the oid-set contains a commit object, all
	       referenced trees are also included in the response.

gvfs-helper is updated to take "get" and "post" command line options.
the gvfs-helper "server" mode is updated to take "objects.get" and
"objects.post" verbs.

For convenience, the "get" option and the "objects.get" verb
do allow more than one object to be requested.  gvfs-helper will
automatically issue a series of (single object) HTTP GET requests
and creating a series of loose objects.

The "post" option and the "objects.post" verb will perform bulk
object fetching using the batch-size chunking.  Individual HTTP
POST requests containing more than one object will be created
as a packfile.  A HTTP POST for a single object will create a
loose object.

This commit also contains some refactoring to eliminate the
assumption that POST is always associated with packfiles.

In gvfs-helper-client.c, gh_client__get_immediate() now uses the
"objects.get" verb and ignores any currently queued objects.

In gvfs-helper-client.c, the OIDSET built by gh_client__queue_oid()
is only processed when gh_client__drain_queue() is called.  The queue
is processed using the "object.post" verb.

Signed-off-by: Jeff Hostetler <[email protected]>
During development, it was very helpful to see the gvfs-helper do its
work to request a pack-file or download a loose object. When these
messages appear during normal use, it leads to a very noisy terminal
output.

Remove all progress indicators when downloading loose objects. We know
that these can be numbered in the thousands in certain kinds of history
calls, and would litter the terminal output with noise. This happens
during 'git fetch' or 'git pull' as well when the tip commits are
checked for the new refs.

Remove the "Requesting packfile with %ld objects" message, as this
operation is very fast. We quickly follow up with the more valuable
"Receiving packfile %ld%ld with %ld objects". When a large "git
checkout" causes many pack-file downloads, it is good to know that Git
is asking for data from the server.

Signed-off-by: Derrick Stolee <[email protected]>
@derrickstolee derrickstolee force-pushed the tentative/features/sparse-checkout-2.24.0 branch from bd8dd7f to 8cf9286 Compare November 6, 2019 13:54
@derrickstolee derrickstolee marked this pull request as ready for review November 6, 2019 14:02
@derrickstolee
Copy link
Author

I got the C# layer to work, so this should be ready to go! @jeffhostetler

@derrickstolee
Copy link
Author

+    Signed-off-by: Junio C Hamano <[email protected]>

I probably did the exact same thing in the past... but... maybe remove those, since you're the committer, not Junio?

I'm taking the commits that Junio made and queued. The next rebase will not include these as they should be in v2.25.0.

derrickstolee added a commit to microsoft/scalar that referenced this pull request Nov 7, 2019
@derrickstolee derrickstolee deleted the tentative/features/sparse-checkout-2.24.0 branch January 13, 2020 13:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants