{"thread":{"id":"14624","subject":"[PATCH 06/12] Avoid accessing working directory outside sparse prefix","startedAt":"2008-07-23T14:56:46Z","lastAt":"2008-07-23T14:56:46Z","messageCount":1,"participants":["Nguyễn Thái Ngọc Duy"],"isPatch":true,"patchVersion":1,"patchTotal":12},"messages":[{"id":"84512","messageId":"20080723145646.GA29112@laptop","threadId":"14624","inReplyTo":null,"subject":"[PATCH 06/12] Avoid accessing working directory outside sparse prefix","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2008-07-23T14:56:46Z","receivedAt":"2008-07-23T14:56:46Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Low-level functions such as checkout_entry() will refuse to write\noutside sparse prefix. Other functions need lstat() will simply\nassume files are good. The rest should die()\n\nOn UI side, all pathspec input will be filtered by filter_pathspec()\nor filter_pathspec_1() to:\n - keep pathspec if it's inside sparse prefix\n - expand pathspec if it's outside sparse prefix, but is a parent\n   directory of sparse prefix\n - otherwise, die()\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin-add.c             |    4 +-\n builtin-apply.c           |    5 ++\n builtin-checkout-index.c  |    2 +\n builtin-checkout.c        |    2 +-\n builtin-clean.c           |    3 +\n builtin-commit.c          |    2 +-\n builtin-grep.c            |   26 +++++++------\n builtin-ls-files.c        |    4 +-\n builtin-ls-tree.c         |    2 +-\n builtin-merge-recursive.c |    2 +-\n builtin-mv.c              |    2 +-\n builtin-rm.c              |    7 +++-\n builtin-update-index.c    |   37 ++++++++++++++----\n cache.h                   |    3 +\n diff-lib.c                |    2 +-\n diff.c                    |    3 +-\n entry.c                   |    3 +\n read-cache.c              |    5 ++\n setup.c                   |   90 ++++++++++++++++++++++++++++++++++++++++++++-\n sha1_file.c               |    3 +\n unpack-trees.c            |   29 +++++++++++++-\n 21 files changed, 199 insertions(+), 37 deletions(-)\n\ndiff --git a/builtin-add.c b/builtin-add.c\nindex fc3f96e..8f6efca 100644\n--- a/builtin-add.c\n+++ b/builtin-add.c\n@@ -151,7 +151,7 @@ static void refresh(int verbose, const char **pathspec)\n \n static const char **validate_pathspec(int argc, const char **argv, const char *prefix)\n {\n-\tconst char **pathspec = get_pathspec(prefix, argv);\n+\tconst char **pathspec = get_filtered_pathspec(prefix, argv);\n \n \treturn pathspec;\n }\n@@ -278,7 +278,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \t\tfprintf(stderr, \"Maybe you wanted to say 'git add .'?\\n\");\n \t\treturn 0;\n \t}\n-\tpathspec = get_pathspec(prefix, argv);\n+\tpathspec = get_filtered_pathspec(prefix, argv);\n \n \t/*\n \t * If we are adding new files, we need to scan the working\ndiff --git a/builtin-apply.c b/builtin-apply.c\nindex 2216a0b..25d3523 100644\n--- a/builtin-apply.c\n+++ b/builtin-apply.c\n@@ -2389,6 +2389,9 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s\n \tif (!old_name)\n \t\treturn 0;\n \n+\tif (outside_sparse_prefix(old_name))\n+\t\treturn error(\"%s: could not apply outside sparse checkout\", old_name);\n+\n \tassert(patch->is_new <= 0);\n \n \tif (!(patch->is_copy || patch->is_rename) &&\n@@ -2491,6 +2494,8 @@ static int check_patch(struct patch *patch)\n \t\t    cache_name_pos(new_name, strlen(new_name)) >= 0 &&\n \t\t    !ok_if_exists)\n \t\t\treturn error(\"%s: already exists in index\", new_name);\n+\t\tif (outside_sparse_prefix(new_name))\n+\t\t\treturn error(\"%s: could not apply outside sparse checkout\", new_name);\n \t\tif (!cached) {\n \t\t\tint err = check_to_create_blob(new_name, ok_if_exists);\n \t\t\tif (err)\ndiff --git a/builtin-checkout-index.c b/builtin-checkout-index.c\nindex 71ebabf..f0e829c 100644\n--- a/builtin-checkout-index.c\n+++ b/builtin-checkout-index.c\n@@ -134,6 +134,8 @@ static void checkout_all(const char *prefix, int prefix_length)\n \t\t    (ce_namelen(ce) <= prefix_length ||\n \t\t     memcmp(prefix, ce->name, prefix_length)))\n \t\t\tcontinue;\n+\t\tif (outside_sparse_prefix(ce->name))\n+\t\t\tcontinue;\n \t\tif (last_ce && to_tempfile) {\n \t\t\tif (ce_namelen(last_ce) != ce_namelen(ce)\n \t\t\t    || memcmp(last_ce->name, ce->name, ce_namelen(ce)))\ndiff --git a/builtin-checkout.c b/builtin-checkout.c\nindex fbd5105..eec1bde 100644\n--- a/builtin-checkout.c\n+++ b/builtin-checkout.c\n@@ -472,7 +472,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\tdie(\"git checkout: -f and -m are incompatible\");\n \n \tif (argc) {\n-\t\tconst char **pathspec = get_pathspec(prefix, argv);\n+\t\tconst char **pathspec = get_filtered_pathspec(prefix, argv);\n \n \t\tif (!pathspec)\n \t\t\tdie(\"invalid path specification\");\ndiff --git a/builtin-clean.c b/builtin-clean.c\nindex 48bf29f..5f0a632 100644\n--- a/builtin-clean.c\n+++ b/builtin-clean.c\n@@ -76,6 +76,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \t\tsetup_standard_excludes(&dir);\n \n \tpathspec = get_pathspec(prefix, argv);\n+\tpathspec = filter_pathspec(pathspec);\n+\tif (!pathspec && sparse_checkout_enabled())\n+\t\tpathspec = (const char **)get_sparse_prefix();\n \tread_cache();\n \n \t/*\ndiff --git a/builtin-commit.c b/builtin-commit.c\nindex f2d301a..aaa208d 100644\n--- a/builtin-commit.c\n+++ b/builtin-commit.c\n@@ -237,7 +237,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)\n \t\tdie(\"index file corrupt\");\n \n \tif (*argv)\n-\t\tpathspec = get_pathspec(prefix, argv);\n+\t\tpathspec = get_filtered_pathspec(prefix, argv);\n \n \t/*\n \t * Non partial, non as-is commit.\ndiff --git a/builtin-grep.c b/builtin-grep.c\nindex ee96d01..c9dc30a 100644\n--- a/builtin-grep.c\n+++ b/builtin-grep.c\n@@ -776,19 +776,21 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\t\tverify_filename(prefix, argv[j]);\n \t}\n \n-\tif (i < argc) {\n-\t\tpaths = get_pathspec(prefix, argv + i);\n-\t\tif (opt.prefix_length && opt.relative) {\n-\t\t\t/* Make sure we do not get outside of paths */\n-\t\t\tfor (i = 0; paths[i]; i++)\n-\t\t\t\tif (strncmp(prefix, paths[i], opt.prefix_length))\n-\t\t\t\t\tdie(\"git-grep: cannot generate relative filenames containing '..'\");\n-\t\t}\n-\t}\n+\tif (i < argc)\n+\t\tpaths = get_filtered_pathspec(prefix, argv + i);\n \telse if (prefix) {\n-\t\tpaths = xcalloc(2, sizeof(const char *));\n-\t\tpaths[0] = prefix;\n-\t\tpaths[1] = NULL;\n+\t\tstatic const char *fake_argv[2];\n+\t\tfake_argv[0] = prefix;\n+\t\tfake_argv[1] = NULL;\n+\t\tpaths = get_filtered_pathspec(NULL, fake_argv);\n+\t}\n+\tif (!paths && sparse_checkout_enabled())\n+\t\tpaths = (const char **)get_sparse_prefix();\n+\tif (paths && opt.prefix_length && opt.relative) {\n+\t\t/* Make sure we do not get outside of paths */\n+\t\tfor (i = 0; paths[i]; i++)\n+\t\t\tif (strncmp(prefix, paths[i], opt.prefix_length))\n+\t\t\t\tdie(\"git-grep: cannot generate relative filenames containing '..'\");\n \t}\n \n \tif (!list.nr)\ndiff --git a/builtin-ls-files.c b/builtin-ls-files.c\nindex 4d18873..37041cd 100644\n--- a/builtin-ls-files.c\n+++ b/builtin-ls-files.c\n@@ -571,7 +571,9 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)\n \tif (require_work_tree && !is_inside_work_tree())\n \t\tsetup_work_tree();\n \n-\tpathspec = get_pathspec(prefix, argv + i);\n+\tpathspec = get_filtered_pathspec(prefix, argv + i);\n+\tif (!pathspec && sparse_checkout_enabled())\n+\t\tpathspec = (const char **)get_sparse_prefix();\n \n \t/* Verify that the pathspec matches the prefix */\n \tif (pathspec)\ndiff --git a/builtin-ls-tree.c b/builtin-ls-tree.c\nindex d25767a..45b1ef5 100644\n--- a/builtin-ls-tree.c\n+++ b/builtin-ls-tree.c\n@@ -185,7 +185,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \tif (get_sha1(argv[1], sha1))\n \t\tdie(\"Not a valid object name %s\", argv[1]);\n \n-\tpathspec = get_pathspec(prefix, argv + 2);\n+\tpathspec = get_filtered_pathspec(prefix, argv + 2);\n \ttree = parse_tree_indirect(sha1);\n \tif (!tree)\n \t\tdie(\"not a tree object\");\ndiff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c\nindex 7ce015b..5c62743 100644\n--- a/builtin-merge-recursive.c\n+++ b/builtin-merge-recursive.c\n@@ -526,7 +526,7 @@ static void update_file_flags(const unsigned char *sha,\n \tif (index_only)\n \t\tupdate_wd = 0;\n \n-\tif (update_wd) {\n+\tif (update_wd && !outside_sparse_prefix(path)) {\n \t\tenum object_type type;\n \t\tvoid *buf;\n \t\tunsigned long size;\ndiff --git a/builtin-mv.c b/builtin-mv.c\nindex 736a0b8..0591aa2 100644\n--- a/builtin-mv.c\n+++ b/builtin-mv.c\n@@ -33,7 +33,7 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec,\n \t\t\t\tresult[i] = last_slash + 1;\n \t\t}\n \t}\n-\treturn get_pathspec(prefix, result);\n+\treturn get_filtered_pathspec(prefix, result);\n }\n \n static void show_list(const char *label, struct string_list *list)\ndiff --git a/builtin-rm.c b/builtin-rm.c\nindex ee8247b..082dd95 100644\n--- a/builtin-rm.c\n+++ b/builtin-rm.c\n@@ -158,7 +158,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n \tif (read_cache() < 0)\n \t\tdie(\"index file corrupt\");\n \n-\tpathspec = get_pathspec(prefix, argv);\n+\tpathspec = get_filtered_pathspec(prefix, argv);\n+\tif (!pathspec)\n+\t\treturn 0;\n+\n \tseen = NULL;\n \tfor (i = 0; pathspec[i] ; i++)\n \t\t/* nothing */;\n@@ -166,6 +169,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n \n \tfor (i = 0; i < active_nr; i++) {\n \t\tstruct cache_entry *ce = active_cache[i];\n+\t\tif (outside_sparse_prefix(ce->name))\n+\t\t\tcontinue;\n \t\tif (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))\n \t\t\tcontinue;\n \t\tadd_list(ce->name);\ndiff --git a/builtin-update-index.c b/builtin-update-index.c\nindex 38eb53c..25e3e98 100644\n--- a/builtin-update-index.c\n+++ b/builtin-update-index.c\n@@ -242,6 +242,9 @@ static void chmod_path(int flip, const char *path)\n \tstruct cache_entry *ce;\n \tunsigned int mode;\n \n+\tif (outside_sparse_prefix(path))\n+\t\tdie(\"%s: cannot update sparse outside %s\", path, get_sparse_prefix_str());\n+\n \tpos = cache_name_pos(path, strlen(path));\n \tif (pos < 0)\n \t\tgoto fail;\n@@ -521,7 +524,7 @@ static int do_reupdate(int ac, const char **av,\n \t */\n \tint pos;\n \tint has_head = 1;\n-\tconst char **pathspec = get_pathspec(prefix, av + 1);\n+\tconst char **pathspec = get_filtered_pathspec(prefix, av + 1);\n \n \tif (read_ref(\"HEAD\", head_sha1))\n \t\t/* If there is no HEAD, that means it is an initial\n@@ -582,7 +585,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \n \tfor (i = 1 ; i < argc; i++) {\n \t\tconst char *path = argv[i];\n-\t\tconst char *p;\n+\t\tconst char *p, **pathspec;\n \n \t\tif (allow_options && *path == '-') {\n \t\t\tif (!strcmp(path, \"--\")) {\n@@ -703,9 +706,17 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\t\tdie(\"unknown option %s\", path);\n \t\t}\n \t\tp = prefix_path(prefix, prefix_length, path);\n-\t\tupdate_one(p, NULL, 0);\n-\t\tif (set_executable_bit)\n-\t\t\tchmod_path(set_executable_bit, p);\n+\t\tpathspec = filter_pathspec_1(p);\n+\t\tif (pathspec) {\n+\t\t\tconst char **pp = pathspec;\n+\t\t\twhile (*pp) {\n+\t\t\t\tupdate_one(*pp, NULL, 0);\n+\t\t\t\tif (set_executable_bit)\n+\t\t\t\t\tchmod_path(set_executable_bit, *pp);\n+\t\t\t\tpp ++;\n+\t\t\t}\n+\t\t\tfree(pathspec);\n+\t\t}\n \t\tif (p < path || p > path + strlen(path))\n \t\t\tfree((char*)p);\n \t}\n@@ -715,7 +726,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_init(&buf, 0);\n \t\tstrbuf_init(&nbuf, 0);\n \t\twhile (strbuf_getline(&buf, stdin, line_termination) != EOF) {\n-\t\t\tconst char *p;\n+\t\t\tconst char *p, **pathspec;\n \t\t\tif (line_termination && buf.buf[0] == '\"') {\n \t\t\t\tstrbuf_reset(&nbuf);\n \t\t\t\tif (unquote_c_style(&nbuf, buf.buf, NULL))\n@@ -723,9 +734,17 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\t\t\tstrbuf_swap(&buf, &nbuf);\n \t\t\t}\n \t\t\tp = prefix_path(prefix, prefix_length, buf.buf);\n-\t\t\tupdate_one(p, NULL, 0);\n-\t\t\tif (set_executable_bit)\n-\t\t\t\tchmod_path(set_executable_bit, p);\n+\t\t\tpathspec = filter_pathspec_1(p);\n+\t\t\tif (pathspec) {\n+\t\t\t\tconst char **pp = pathspec;\n+\t\t\t\twhile (*pp) {\n+\t\t\t\t\tupdate_one(*pp, NULL, 0);\n+\t\t\t\t\tif (set_executable_bit)\n+\t\t\t\t\t\tchmod_path(set_executable_bit, *pp);\n+\t\t\t\t\tpp++;\n+\t\t\t\t}\n+\t\t\t\tfree(pathspec);\n+\t\t\t}\n \t\t\tif (p < buf.buf || p > buf.buf + buf.len)\n \t\t\t\tfree((char *)p);\n \t\t}\ndiff --git a/cache.h b/cache.h\nindex f6bbadc..b9a1d96 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -336,6 +336,9 @@ extern int check_index_prefix(const struct index_state *index, int is_merge);\n #define ALTERNATE_DB_ENVIRONMENT \"GIT_ALTERNATE_OBJECT_DIRECTORIES\"\n \n extern const char **get_pathspec(const char *prefix, const char **pathspec);\n+extern const char **filter_pathspec(const char **pathspec);\n+extern const char **filter_pathspec_1(const char *path);\n+extern const char **get_filtered_pathspec(const char *prefix, const char **pathspec);\n extern void setup_work_tree(void);\n extern const char *setup_git_directory_gently(int *);\n extern const char *setup_git_directory(void);\ndiff --git a/diff-lib.c b/diff-lib.c\nindex 2908146..9382114 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -356,7 +356,7 @@ static void do_oneway_diff(struct unpack_trees_options *o,\n \t * But with the revision flag parsing, that's found in\n \t * \"!revs->ignore_merges\".\n \t */\n-\tcached = o->index_only;\n+\tcached = outside_sparse_prefix(idx->name) || o->index_only;\n \tmatch_missing = !revs->ignore_merges;\n \n \tif (cached && idx && ce_stage(idx)) {\ndiff --git a/diff.c b/diff.c\nindex a07812c..e344916 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -1721,7 +1721,8 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int\n \t * If ce matches the file in the work tree, we can reuse it.\n \t */\n \tif (ce_uptodate(ce) ||\n-\t    (!lstat(name, &st) && !ce_match_stat(ce, &st, 0)))\n+\t    (!outside_sparse_prefix(name) &&\n+\t     !lstat(name, &st) && !ce_match_stat(ce, &st, 0)))\n \t\treturn 1;\n \n \treturn 0;\ndiff --git a/entry.c b/entry.c\nindex 222aaa3..e2921c2 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -201,6 +201,9 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t\n \n \tmemcpy(path, state->base_dir, len);\n \tstrcpy(path + len, ce->name);\n+\tif (outside_sparse_prefix(path))\n+\t\tdie(\"%s: unable to checkout entry outside sparse checkout\", path);\n+\n \n \tif (!lstat(path, &st)) {\n \t\tunsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);\ndiff --git a/read-cache.c b/read-cache.c\nindex 4fea40e..dd42c7c 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -232,6 +232,8 @@ int ie_match_stat(const struct index_state *istate,\n \tint ignore_valid = options & CE_MATCH_IGNORE_VALID;\n \tint assume_racy_is_modified = options & CE_MATCH_RACY_IS_DIRTY;\n \n+\tif (outside_sparse_prefix(ce->name))\n+\t\treturn 0;\n \t/*\n \t * If it's marked as always valid in the index, it's\n \t * valid whatever the checked-out copy says.\n@@ -953,6 +955,9 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,\n \t\treturn ce;\n \t}\n \n+\tif (outside_sparse_prefix(ce->name))\n+\t\treturn ce;\n+\n \tif (lstat(ce->name, &st) < 0) {\n \t\tif (err)\n \t\t\t*err = errno;\ndiff --git a/setup.c b/setup.c\nindex 6cf9094..4ae68fd 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -164,7 +164,7 @@ void verify_filename(const char *prefix, const char *arg)\n \tif (*arg == '-')\n \t\tdie(\"bad flag '%s' used after filename\", arg);\n \tname = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;\n-\tif (!lstat(name, &st))\n+\tif (!outside_sparse_prefix(name) && !lstat(name, &st))\n \t\treturn;\n \tif (errno == ENOENT)\n \t\tdie(\"ambiguous argument '%s': unknown revision or path not in the working tree.\\n\"\n@@ -187,7 +187,7 @@ void verify_non_filename(const char *prefix, const char *arg)\n \tif (*arg == '-')\n \t\treturn; /* flag */\n \tname = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;\n-\tif (!lstat(name, &st))\n+\tif (!outside_sparse_prefix(name) && !lstat(name, &st))\n \t\tdie(\"ambiguous argument '%s': both revision and filename\\n\"\n \t\t    \"Use '--' to separate filenames from revisions\", arg);\n \tif (errno != ENOENT && errno != ENOTDIR)\n@@ -229,6 +229,92 @@ const char **get_pathspec(const char *prefix, const char **pathspec)\n }\n \n /*\n+ * if path is inside sparse prefix, leave it as-is\n+ * if path is parent of sparse prefix, expand it to sparse prefix\n+ * if path is outside sparse prefix, die()\n+ */\n+static void filter_pathspec_internal(const char *path, const char ***dst, int *dst_alloc, int *dst_nr)\n+{\n+\tchar **iprefix;\n+\tint filtered = 1;\n+\n+\tfor (iprefix = get_sparse_prefix(); *iprefix; iprefix++) {\n+\t\tconst char *new_dst = NULL;\n+\t\tif (!prefixcmp(path, *iprefix))\n+\t\t\tnew_dst = path;\n+\t\telse if (!prefixcmp(*iprefix, path))\n+\t\t\tnew_dst = *iprefix;\n+\n+\t\tif (!new_dst)\n+\t\t\tcontinue;\n+\n+\t\tif (*dst_alloc <= *dst_nr) {\n+\t\t\t*dst_alloc = alloc_nr(*dst_alloc);\n+\t\t\t*dst = xrealloc(*dst, *dst_alloc * sizeof(**dst));\n+\t\t}\n+\t\t(*dst)[(*dst_nr)++] = new_dst;\n+\t\tfiltered = 0;\n+\n+\t\t/*\n+\t\t * if it's inside sparse prefix,\n+\t\t * no need to search more\n+\t\t */\n+\t\tif (new_dst == path)\n+\t\t\tbreak;\n+\t}\n+\tif (filtered)\n+\t\tdie(\"pathspec %s is outside sparse checkout %s\", path, get_sparse_prefix_str());\n+}\n+\n+const char **filter_pathspec(const char **pathspec)\n+{\n+\tconst char **src, **dst;\n+\tint dst_alloc = 0;\n+\tint dst_nr = 0;\n+\n+\tif (!sparse_checkout_enabled())\n+\t\treturn pathspec;\n+\tif (!pathspec || !*pathspec)\n+\t\treturn NULL;\n+\n+\tsrc = pathspec;\n+\tdst = NULL;\n+\twhile (*src) {\n+\t\tfilter_pathspec_internal(*src, &dst, &dst_alloc, &dst_nr);\n+\t\tsrc++;\n+\t}\n+\tif (!dst)\n+\t\treturn NULL;\n+\tdst[dst_nr] = NULL;\n+\treturn dst;\n+}\n+\n+const char **filter_pathspec_1(const char *path)\n+{\n+\tint dst_alloc = 0, dst_nr = 0;\n+\tconst char **dst = NULL;\n+\n+\tif (!path)\n+\t\treturn NULL;\n+\tif (!sparse_checkout_enabled()) {\n+\t\tdst = xmalloc(2*sizeof(*dst));\n+\t\tdst[0] = path;\n+\t\tdst[1] = NULL;\n+\t\treturn dst;\n+\t}\n+\tfilter_pathspec_internal(path, &dst, &dst_alloc, &dst_nr);\n+\tif (!dst)\n+\t\treturn NULL;\n+\tdst[dst_nr] = NULL;\n+\treturn dst;\n+}\n+\n+const char **get_filtered_pathspec(const char *prefix, const char **pathspec)\n+{\n+\treturn filter_pathspec(get_pathspec(prefix,pathspec));\n+}\n+\n+/*\n  * Test if it looks like we're at a git directory.\n  * We want to see:\n  *\ndiff --git a/sha1_file.c b/sha1_file.c\nindex e281c14..60f9e2a 100644\n--- a/sha1_file.c\n+++ b/sha1_file.c\n@@ -2422,6 +2422,9 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write\n \tchar *target;\n \tsize_t len;\n \n+\tif (outside_sparse_prefix(path))\n+\t\tdie(\"%s: cannot index a file outside sparse checkout\", path);\n+\n \tswitch (st->st_mode & S_IFMT) {\n \tcase S_IFREG:\n \t\tfd = open(path, O_RDONLY);\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 0a6723b..0a30d68 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -84,6 +84,19 @@ static void unlink_entry(struct cache_entry *ce)\n \t}\n }\n \n+static int check_sparse_checkout(struct unpack_trees_options *o)\n+{\n+\tunsigned cnt = 0;\n+\tstruct index_state *index = &o->result;\n+\n+\tfor (cnt = 0; cnt < index->cache_nr; cnt++) {\n+\t\tstruct cache_entry *ce = index->cache[cnt];\n+\t\tif (ce_stage(ce) && outside_sparse_prefix(ce->name))\n+\t\t\treturn 0;\n+\t}\n+\treturn 1;\n+}\n+\n static struct checkout state;\n static int check_updates(struct unpack_trees_options *o)\n {\n@@ -96,6 +109,8 @@ static int check_updates(struct unpack_trees_options *o)\n \tif (o->update && o->verbose_update) {\n \t\tfor (total = cnt = 0; cnt < index->cache_nr; cnt++) {\n \t\t\tstruct cache_entry *ce = index->cache[cnt];\n+\t\t\tif (outside_sparse_prefix(ce->name))\n+\t\t\t\tcontinue;\n \t\t\tif (ce->ce_flags & (CE_UPDATE | CE_REMOVE))\n \t\t\t\ttotal++;\n \t\t}\n@@ -108,6 +123,8 @@ static int check_updates(struct unpack_trees_options *o)\n \tfor (i = 0; i < index->cache_nr; i++) {\n \t\tstruct cache_entry *ce = index->cache[i];\n \n+\t\tif (outside_sparse_prefix(ce->name))\n+\t\t\tcontinue;\n \t\tif (ce->ce_flags & CE_REMOVE) {\n \t\t\tdisplay_progress(progress, ++cnt);\n \t\t\tif (o->update)\n@@ -121,6 +138,8 @@ static int check_updates(struct unpack_trees_options *o)\n \tfor (i = 0; i < index->cache_nr; i++) {\n \t\tstruct cache_entry *ce = index->cache[i];\n \n+\t\tif (outside_sparse_prefix(ce->name))\n+\t\t\tcontinue;\n \t\tif (ce->ce_flags & CE_UPDATE) {\n \t\t\tdisplay_progress(progress, ++cnt);\n \t\t\tce->ce_flags &= ~CE_UPDATE;\n@@ -411,6 +430,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n \tif (o->check_index_prefix && !check_index_prefix(&o->result, o->merge && !o->prefix))\n \t\treturn unpack_failed(o, \"Merge outside index prefix\");\n \n+\tif (sparse_checkout_enabled() && !check_sparse_checkout(o))\n+\t\treturn unpack_failed(o, \"Merge conflicts outside sparse checkout\");\n+\n \to->src_index = NULL;\n \tret = check_updates(o) ? (-2) : 0;\n \tif (o->dst_index)\n@@ -445,7 +467,7 @@ static int verify_uptodate(struct cache_entry *ce,\n {\n \tstruct stat st;\n \n-\tif (o->index_only || o->reset)\n+\tif (o->index_only || o->reset || outside_sparse_prefix(ce->name))\n \t\treturn 0;\n \n \tif (!lstat(ce->name, &st)) {\n@@ -583,7 +605,7 @@ static int verify_absent(struct cache_entry *ce, const char *action,\n {\n \tstruct stat st;\n \n-\tif (o->index_only || o->reset || !o->update)\n+\tif (o->index_only || o->reset || !o->update || outside_sparse_prefix(ce->name))\n \t\treturn 0;\n \n \tif (has_symlink_leading_path(ce_namelen(ce), ce->name))\n@@ -994,7 +1016,8 @@ int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o)\n \t\tint update = 0;\n \t\tif (o->reset) {\n \t\t\tstruct stat st;\n-\t\t\tif (lstat(old->name, &st) ||\n+\t\t\tif (outside_sparse_prefix(old->name) ||\n+\t\t\t    lstat(old->name, &st) ||\n \t\t\t    ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID))\n \t\t\t\tupdate |= CE_UPDATE;\n \t\t}\n-- \n1.5.5.GIT\n"}]}