checkout-index: add --ignore-skip-worktree-bits option

Update `checkout-index` to no longer refresh files that have the
`skip-worktree` bit set, exiting with an error if `skip-worktree` filenames
are directly provided to `checkout-index`. The newly-added
`--ignore-skip-worktree-bits` option provides a mechanism to replicate the
old behavior, checking out *all* files specified (even those with
`skip-worktree` enabled).

The ability to toggle whether files should be checked-out based on
`skip-worktree` already exists in `git checkout` and `git restore` (both of
which have an `--ignore-skip-worktree-bits` option). The change to, by
default, ignore `skip-worktree` files is especially helpful for
sparse-checkout; it prevents inadvertent creation of files outside the
sparse definition on disk and eliminates the need to expand a sparse index
when using the `--all` option.

Internal usage of `checkout-index` in `git stash` and `git filter-branch` do
not make explicit use of files with `skip-worktree` enabled, so
`--ignore-skip-worktree-bits` is not added to them.

Helped-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Victoria Dye <vdye@github.com>
Reviewed-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Victoria Dye 2022-01-11 18:05:02 +00:00 committed by Junio C Hamano
parent b553ef6749
commit 88078f543b
3 changed files with 38 additions and 12 deletions

View File

@ -12,6 +12,7 @@ SYNOPSIS
'git checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>] 'git checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>]
[--stage=<number>|all] [--stage=<number>|all]
[--temp] [--temp]
[--ignore-skip-worktree-bits]
[-z] [--stdin] [-z] [--stdin]
[--] [<file>...] [--] [<file>...]
@ -37,8 +38,9 @@ OPTIONS
-a:: -a::
--all:: --all::
checks out all files in the index. Cannot be used checks out all files in the index except for those with the
together with explicit filenames. skip-worktree bit set (see `--ignore-skip-worktree-bits`).
Cannot be used together with explicit filenames.
-n:: -n::
--no-create:: --no-create::
@ -59,6 +61,10 @@ OPTIONS
write the content to temporary files. The temporary name write the content to temporary files. The temporary name
associations will be written to stdout. associations will be written to stdout.
--ignore-skip-worktree-bits::
Check out all files, including those with the skip-worktree bit
set.
--stdin:: --stdin::
Instead of taking list of paths from the command line, Instead of taking list of paths from the command line,
read list of paths from the standard input. Paths are read list of paths from the standard input. Paths are

View File

@ -7,6 +7,7 @@
#define USE_THE_INDEX_COMPATIBILITY_MACROS #define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h" #include "builtin.h"
#include "config.h" #include "config.h"
#include "dir.h"
#include "lockfile.h" #include "lockfile.h"
#include "quote.h" #include "quote.h"
#include "cache-tree.h" #include "cache-tree.h"
@ -17,6 +18,7 @@
#define CHECKOUT_ALL 4 #define CHECKOUT_ALL 4
static int nul_term_line; static int nul_term_line;
static int checkout_stage; /* default to checkout stage0 */ static int checkout_stage; /* default to checkout stage0 */
static int ignore_skip_worktree; /* default to 0 */
static int to_tempfile; static int to_tempfile;
static char topath[4][TEMPORARY_FILENAME_LENGTH + 1]; static char topath[4][TEMPORARY_FILENAME_LENGTH + 1];
@ -65,6 +67,7 @@ static int checkout_file(const char *name, const char *prefix)
int namelen = strlen(name); int namelen = strlen(name);
int pos = cache_name_pos(name, namelen); int pos = cache_name_pos(name, namelen);
int has_same_name = 0; int has_same_name = 0;
int is_skipped = 1;
int did_checkout = 0; int did_checkout = 0;
int errs = 0; int errs = 0;
@ -78,6 +81,9 @@ static int checkout_file(const char *name, const char *prefix)
break; break;
has_same_name = 1; has_same_name = 1;
pos++; pos++;
if (!ignore_skip_worktree && ce_skip_worktree(ce))
break;
is_skipped = 0;
if (ce_stage(ce) != checkout_stage if (ce_stage(ce) != checkout_stage
&& (CHECKOUT_ALL != checkout_stage || !ce_stage(ce))) && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
continue; continue;
@ -106,6 +112,9 @@ static int checkout_file(const char *name, const char *prefix)
fprintf(stderr, "git checkout-index: %s ", name); fprintf(stderr, "git checkout-index: %s ", name);
if (!has_same_name) if (!has_same_name)
fprintf(stderr, "is not in the cache"); fprintf(stderr, "is not in the cache");
else if (is_skipped)
fprintf(stderr, "has skip-worktree enabled; "
"use '--ignore-skip-worktree-bits' to checkout");
else if (checkout_stage) else if (checkout_stage)
fprintf(stderr, "does not exist at stage %d", fprintf(stderr, "does not exist at stage %d",
checkout_stage); checkout_stage);
@ -125,6 +134,8 @@ static int checkout_all(const char *prefix, int prefix_length)
ensure_full_index(&the_index); ensure_full_index(&the_index);
for (i = 0; i < active_nr ; i++) { for (i = 0; i < active_nr ; i++) {
struct cache_entry *ce = active_cache[i]; struct cache_entry *ce = active_cache[i];
if (!ignore_skip_worktree && ce_skip_worktree(ce))
continue;
if (ce_stage(ce) != checkout_stage if (ce_stage(ce) != checkout_stage
&& (CHECKOUT_ALL != checkout_stage || !ce_stage(ce))) && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
continue; continue;
@ -185,6 +196,8 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
struct option builtin_checkout_index_options[] = { struct option builtin_checkout_index_options[] = {
OPT_BOOL('a', "all", &all, OPT_BOOL('a', "all", &all,
N_("check out all files in the index")), N_("check out all files in the index")),
OPT_BOOL(0, "ignore-skip-worktree-bits", &ignore_skip_worktree,
N_("do not skip files with skip-worktree set")),
OPT__FORCE(&force, N_("force overwrite of existing files"), 0), OPT__FORCE(&force, N_("force overwrite of existing files"), 0),
OPT__QUIET(&quiet, OPT__QUIET(&quiet,
N_("no warning for existing files and files not in index")), N_("no warning for existing files and files not in index")),

View File

@ -772,9 +772,14 @@ test_expect_success 'checkout-index inside sparse definition' '
test_expect_success 'checkout-index outside sparse definition' ' test_expect_success 'checkout-index outside sparse definition' '
init_repos && init_repos &&
# File does not exist on disk yet for sparse checkouts, so checkout-index # Without --ignore-skip-worktree-bits, outside-of-cone files will trigger
# succeeds without -f # an error
test_sparse_match git checkout-index -- folder1/a && test_sparse_match test_must_fail git checkout-index -- folder1/a &&
test_i18ngrep "folder1/a has skip-worktree enabled" sparse-checkout-err &&
test_path_is_missing folder1/a &&
# With --ignore-skip-worktree-bits, outside-of-cone files are checked out
test_sparse_match git checkout-index --ignore-skip-worktree-bits -- folder1/a &&
test_cmp sparse-checkout/folder1/a sparse-index/folder1/a && test_cmp sparse-checkout/folder1/a sparse-index/folder1/a &&
test_cmp sparse-checkout/folder1/a full-checkout/folder1/a && test_cmp sparse-checkout/folder1/a full-checkout/folder1/a &&
@ -783,8 +788,8 @@ test_expect_success 'checkout-index outside sparse definition' '
run_on_sparse mkdir -p folder1 && run_on_sparse mkdir -p folder1 &&
run_on_all cp ../new-a folder1/a && run_on_all cp ../new-a folder1/a &&
test_all_match test_must_fail git checkout-index -- folder1/a && test_all_match test_must_fail git checkout-index --ignore-skip-worktree-bits -- folder1/a &&
test_all_match git checkout-index -f -- folder1/a && test_all_match git checkout-index -f --ignore-skip-worktree-bits -- folder1/a &&
test_cmp sparse-checkout/folder1/a sparse-index/folder1/a && test_cmp sparse-checkout/folder1/a sparse-index/folder1/a &&
test_cmp sparse-checkout/folder1/a full-checkout/folder1/a test_cmp sparse-checkout/folder1/a full-checkout/folder1/a
' '
@ -799,14 +804,16 @@ test_expect_success 'checkout-index with folders' '
test_all_match test_must_fail git checkout-index -f -- folder1/ test_all_match test_must_fail git checkout-index -f -- folder1/
' '
# NEEDSWORK: even in sparse checkouts, checkout-index --all will create all test_expect_success 'checkout-index --all' '
# files (even those outside the sparse definition) on disk. However, these files
# don't appear in the percentage of tracked files in git status.
test_expect_failure 'checkout-index --all' '
init_repos && init_repos &&
test_all_match git checkout-index --all && test_all_match git checkout-index --all &&
test_sparse_match test_path_is_missing folder1 test_sparse_match test_path_is_missing folder1 &&
# --ignore-skip-worktree-bits will cause `skip-worktree` files to be
# checked out, causing the outside-of-cone `folder1` to exist on-disk
test_all_match git checkout-index --ignore-skip-worktree-bits --all &&
test_all_match test_path_exists folder1
' '
test_expect_success 'clean' ' test_expect_success 'clean' '