Subject: [PATCH 06/12] Avoid accessing working directory outside sparse prefix

From: Nguyễn Thái Ngọc Duy <[email-masked]>
Date: Wed, 23 Jul 2008 21:56:46 +0700

Low-level functions such as checkout_entry() will refuse to write
outside sparse prefix. Other functions need lstat() will simply
assume files are good. The rest should die()

On UI side, all pathspec input will be filtered by filter_pathspec()
or filter_pathspec_1() to:
 - keep pathspec if it's inside sparse prefix
 - expand pathspec if it's outside sparse prefix, but is a parent
   directory of sparse prefix
 - otherwise, die()

Signed-off-by: Nguyễn Thái Ngọc Duy <[email-masked]>
---
 builtin-add.c             |    4 +-
 builtin-apply.c           |    5 ++
 builtin-checkout-index.c  |    2 +
 builtin-checkout.c        |    2 +-
 builtin-clean.c           |    3 +
 builtin-commit.c          |    2 +-
 builtin-grep.c            |   26 +++++++------
 builtin-ls-files.c        |    4 +-
 builtin-ls-tree.c         |    2 +-
 builtin-merge-recursive.c |    2 +-
 builtin-mv.c              |    2 +-
 builtin-rm.c              |    7 +++-
 builtin-update-index.c    |   37 ++++++++++++++----
 cache.h                   |    3 +
 diff-lib.c                |    2 +-
 diff.c                    |    3 +-
 entry.c                   |    3 +
 read-cache.c              |    5 ++
 setup.c                   |   90 ++++++++++++++++++++++++++++++++++++++++++++-
 sha1_file.c               |    3 +
 unpack-trees.c            |   29 +++++++++++++-
 21 files changed, 199 insertions(+), 37 deletions(-)

diff --git a/builtin-add.c b/builtin-add.c
index fc3f96e..8f6efca 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -151,7 +151,7 @@ static void refresh(int verbose, const char **pathspec)
 
 static const char **validate_pathspec(int argc, const char **argv, const char *prefix)
 {
-	const char **pathspec = get_pathspec(prefix, argv);
+	const char **pathspec = get_filtered_pathspec(prefix, argv);
 
 	return pathspec;
 }
@@ -278,7 +278,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 		fprintf(stderr, "Maybe you wanted to say 'git add .'?\n");
 		return 0;
 	}
-	pathspec = get_pathspec(prefix, argv);
+	pathspec = get_filtered_pathspec(prefix, argv);
 
 	/*
 	 * If we are adding new files, we need to scan the working
diff --git a/builtin-apply.c b/builtin-apply.c
index 2216a0b..25d3523 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -2389,6 +2389,9 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
 	if (!old_name)
 		return 0;
 
+	if (outside_sparse_prefix(old_name))
+		return error("%s: could not apply outside sparse checkout", old_name);
+
 	assert(patch->is_new <= 0);
 
 	if (!(patch->is_copy || patch->is_rename) &&
@@ -2491,6 +2494,8 @@ static int check_patch(struct patch *patch)
 		    cache_name_pos(new_name, strlen(new_name)) >= 0 &&
 		    !ok_if_exists)
 			return error("%s: already exists in index", new_name);
+		if (outside_sparse_prefix(new_name))
+			return error("%s: could not apply outside sparse checkout", new_name);
 		if (!cached) {
 			int err = check_to_create_blob(new_name, ok_if_exists);
 			if (err)
diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c
index 71ebabf..f0e829c 100644
--- a/builtin-checkout-index.c
+++ b/builtin-checkout-index.c
@@ -134,6 +134,8 @@ static void checkout_all(const char *prefix, int prefix_length)
 		    (ce_namelen(ce) <= prefix_length ||
 		     memcmp(prefix, ce->name, prefix_length)))
 			continue;
+		if (outside_sparse_prefix(ce->name))
+			continue;
 		if (last_ce && to_tempfile) {
 			if (ce_namelen(last_ce) != ce_namelen(ce)
 			    || memcmp(last_ce->name, ce->name, ce_namelen(ce)))
diff --git a/builtin-checkout.c b/builtin-checkout.c
index fbd5105..eec1bde 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -472,7 +472,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		die("git checkout: -f and -m are incompatible");
 
 	if (argc) {
-		const char **pathspec = get_pathspec(prefix, argv);
+		const char **pathspec = get_filtered_pathspec(prefix, argv);
 
 		if (!pathspec)
 			die("invalid path specification");
diff --git a/builtin-clean.c b/builtin-clean.c
index 48bf29f..5f0a632 100644
--- a/builtin-clean.c
+++ b/builtin-clean.c
@@ -76,6 +76,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 		setup_standard_excludes(&dir);
 
 	pathspec = get_pathspec(prefix, argv);
+	pathspec = filter_pathspec(pathspec);
+	if (!pathspec && sparse_checkout_enabled())
+		pathspec = (const char **)get_sparse_prefix();
 	read_cache();
 
 	/*
diff --git a/builtin-commit.c b/builtin-commit.c
index f2d301a..aaa208d 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -237,7 +237,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
 		die("index file corrupt");
 
 	if (*argv)
-		pathspec = get_pathspec(prefix, argv);
+		pathspec = get_filtered_pathspec(prefix, argv);
 
 	/*
 	 * Non partial, non as-is commit.
diff --git a/builtin-grep.c b/builtin-grep.c
index ee96d01..c9dc30a 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -776,19 +776,21 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
 			verify_filename(prefix, argv[j]);
 	}
 
-	if (i < argc) {
-		paths = get_pathspec(prefix, argv + i);
-		if (opt.prefix_length && opt.relative) {
-			/* Make sure we do not get outside of paths */
-			for (i = 0; paths[i]; i++)
-				if (strncmp(prefix, paths[i], opt.prefix_length))
-					die("git-grep: cannot generate relative filenames containing '..'");
-		}
-	}
+	if (i < argc)
+		paths = get_filtered_pathspec(prefix, argv + i);
 	else if (prefix) {
-		paths = xcalloc(2, sizeof(const char *));
-		paths[0] = prefix;
-		paths[1] = NULL;
+		static const char *fake_argv[2];
+		fake_argv[0] = prefix;
+		fake_argv[1] = NULL;
+		paths = get_filtered_pathspec(NULL, fake_argv);
+	}
+	if (!paths && sparse_checkout_enabled())
+		paths = (const char **)get_sparse_prefix();
+	if (paths && opt.prefix_length && opt.relative) {
+		/* Make sure we do not get outside of paths */
+		for (i = 0; paths[i]; i++)
+			if (strncmp(prefix, paths[i], opt.prefix_length))
+				die("git-grep: cannot generate relative filenames containing '..'");
 	}
 
 	if (!list.nr)
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index 4d18873..37041cd 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -571,7 +571,9 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
 	if (require_work_tree && !is_inside_work_tree())
 		setup_work_tree();
 
-	pathspec = get_pathspec(prefix, argv + i);
+	pathspec = get_filtered_pathspec(prefix, argv + i);
+	if (!pathspec && sparse_checkout_enabled())
+		pathspec = (const char **)get_sparse_prefix();
 
 	/* Verify that the pathspec matches the prefix */
 	if (pathspec)
diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c
index d25767a..45b1ef5 100644
--- a/builtin-ls-tree.c
+++ b/builtin-ls-tree.c
@@ -185,7 +185,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	if (get_sha1(argv[1], sha1))
 		die("Not a valid object name %s", argv[1]);
 
-	pathspec = get_pathspec(prefix, argv + 2);
+	pathspec = get_filtered_pathspec(prefix, argv + 2);
 	tree = parse_tree_indirect(sha1);
 	if (!tree)
 		die("not a tree object");
diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c
index 7ce015b..5c62743 100644
--- a/builtin-merge-recursive.c
+++ b/builtin-merge-recursive.c
@@ -526,7 +526,7 @@ static void update_file_flags(const unsigned char *sha,
 	if (index_only)
 		update_wd = 0;
 
-	if (update_wd) {
+	if (update_wd && !outside_sparse_prefix(path)) {
 		enum object_type type;
 		void *buf;
 		unsigned long size;
diff --git a/builtin-mv.c b/builtin-mv.c
index 736a0b8..0591aa2 100644
--- a/builtin-mv.c
+++ b/builtin-mv.c
@@ -33,7 +33,7 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec,
 				result[i] = last_slash + 1;
 		}
 	}
-	return get_pathspec(prefix, result);
+	return get_filtered_pathspec(prefix, result);
 }
 
 static void show_list(const char *label, struct string_list *list)
diff --git a/builtin-rm.c b/builtin-rm.c
index ee8247b..082dd95 100644
--- a/builtin-rm.c
+++ b/builtin-rm.c
@@ -158,7 +158,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 	if (read_cache() < 0)
 		die("index file corrupt");
 
-	pathspec = get_pathspec(prefix, argv);
+	pathspec = get_filtered_pathspec(prefix, argv);
+	if (!pathspec)
+		return 0;
+
 	seen = NULL;
 	for (i = 0; pathspec[i] ; i++)
 		/* nothing */;
@@ -166,6 +169,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 
 	for (i = 0; i < active_nr; i++) {
 		struct cache_entry *ce = active_cache[i];
+		if (outside_sparse_prefix(ce->name))
+			continue;
 		if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
 			continue;
 		add_list(ce->name);
diff --git a/builtin-update-index.c b/builtin-update-index.c
index 38eb53c..25e3e98 100644
--- a/builtin-update-index.c
+++ b/builtin-update-index.c
@@ -242,6 +242,9 @@ static void chmod_path(int flip, const char *path)
 	struct cache_entry *ce;
 	unsigned int mode;
 
+	if (outside_sparse_prefix(path))
+		die("%s: cannot update sparse outside %s", path, get_sparse_prefix_str());
+
 	pos = cache_name_pos(path, strlen(path));
 	if (pos < 0)
 		goto fail;
@@ -521,7 +524,7 @@ static int do_reupdate(int ac, const char **av,
 	 */
 	int pos;
 	int has_head = 1;
-	const char **pathspec = get_pathspec(prefix, av + 1);
+	const char **pathspec = get_filtered_pathspec(prefix, av + 1);
 
 	if (read_ref("HEAD", head_sha1))
 		/* If there is no HEAD, that means it is an initial
@@ -582,7 +585,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 
 	for (i = 1 ; i < argc; i++) {
 		const char *path = argv[i];
-		const char *p;
+		const char *p, **pathspec;
 
 		if (allow_options && *path == '-') {
 			if (!strcmp(path, "--")) {
@@ -703,9 +706,17 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 			die("unknown option %s", path);
 		}
 		p = prefix_path(prefix, prefix_length, path);
-		update_one(p, NULL, 0);
-		if (set_executable_bit)
-			chmod_path(set_executable_bit, p);
+		pathspec = filter_pathspec_1(p);
+		if (pathspec) {
+			const char **pp = pathspec;
+			while (*pp) {
+				update_one(*pp, NULL, 0);
+				if (set_executable_bit)
+					chmod_path(set_executable_bit, *pp);
+				pp ++;
+			}
+			free(pathspec);
+		}
 		if (p < path || p > path + strlen(path))
 			free((char*)p);
 	}
@@ -715,7 +726,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 		strbuf_init(&buf, 0);
 		strbuf_init(&nbuf, 0);
 		while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
-			const char *p;
+			const char *p, **pathspec;
 			if (line_termination && buf.buf[0] == '"') {
 				strbuf_reset(&nbuf);
 				if (unquote_c_style(&nbuf, buf.buf, NULL))
@@ -723,9 +734,17 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 				strbuf_swap(&buf, &nbuf);
 			}
 			p = prefix_path(prefix, prefix_length, buf.buf);
-			update_one(p, NULL, 0);
-			if (set_executable_bit)
-				chmod_path(set_executable_bit, p);
+			pathspec = filter_pathspec_1(p);
+			if (pathspec) {
+				const char **pp = pathspec;
+				while (*pp) {
+					update_one(*pp, NULL, 0);
+					if (set_executable_bit)
+						chmod_path(set_executable_bit, *pp);
+					pp++;
+				}
+				free(pathspec);
+			}
 			if (p < buf.buf || p > buf.buf + buf.len)
 				free((char *)p);
 		}
diff --git a/cache.h b/cache.h
index f6bbadc..b9a1d96 100644
--- a/cache.h
+++ b/cache.h
@@ -336,6 +336,9 @@ extern int check_index_prefix(const struct index_state *index, int is_merge);
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
 
 extern const char **get_pathspec(const char *prefix, const char **pathspec);
+extern const char **filter_pathspec(const char **pathspec);
+extern const char **filter_pathspec_1(const char *path);
+extern const char **get_filtered_pathspec(const char *prefix, const char **pathspec);
 extern void setup_work_tree(void);
 extern const char *setup_git_directory_gently(int *);
 extern const char *setup_git_directory(void);
diff --git a/diff-lib.c b/diff-lib.c
index 2908146..9382114 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -356,7 +356,7 @@ static void do_oneway_diff(struct unpack_trees_options *o,
 	 * But with the revision flag parsing, that's found in
 	 * "!revs->ignore_merges".
 	 */
-	cached = o->index_only;
+	cached = outside_sparse_prefix(idx->name) || o->index_only;
 	match_missing = !revs->ignore_merges;
 
 	if (cached && idx && ce_stage(idx)) {
diff --git a/diff.c b/diff.c
index a07812c..e344916 100644
--- a/diff.c
+++ b/diff.c
@@ -1721,7 +1721,8 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
 	 * If ce matches the file in the work tree, we can reuse it.
 	 */
 	if (ce_uptodate(ce) ||
-	    (!lstat(name, &st) && !ce_match_stat(ce, &st, 0)))
+	    (!outside_sparse_prefix(name) &&
+	     !lstat(name, &st) && !ce_match_stat(ce, &st, 0)))
 		return 1;
 
 	return 0;
diff --git a/entry.c b/entry.c
index 222aaa3..e2921c2 100644
--- a/entry.c
+++ b/entry.c
@@ -201,6 +201,9 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
 
 	memcpy(path, state->base_dir, len);
 	strcpy(path + len, ce->name);
+	if (outside_sparse_prefix(path))
+		die("%s: unable to checkout entry outside sparse checkout", path);
+
 
 	if (!lstat(path, &st)) {
 		unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
diff --git a/read-cache.c b/read-cache.c
index 4fea40e..dd42c7c 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -232,6 +232,8 @@ int ie_match_stat(const struct index_state *istate,
 	int ignore_valid = options & CE_MATCH_IGNORE_VALID;
 	int assume_racy_is_modified = options & CE_MATCH_RACY_IS_DIRTY;
 
+	if (outside_sparse_prefix(ce->name))
+		return 0;
 	/*
 	 * If it's marked as always valid in the index, it's
 	 * valid whatever the checked-out copy says.
@@ -953,6 +955,9 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
 		return ce;
 	}
 
+	if (outside_sparse_prefix(ce->name))
+		return ce;
+
 	if (lstat(ce->name, &st) < 0) {
 		if (err)
 			*err = errno;
diff --git a/setup.c b/setup.c
index 6cf9094..4ae68fd 100644
--- a/setup.c
+++ b/setup.c
@@ -164,7 +164,7 @@ void verify_filename(const char *prefix, const char *arg)
 	if (*arg == '-')
 		die("bad flag '%s' used after filename", arg);
 	name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
-	if (!lstat(name, &st))
+	if (!outside_sparse_prefix(name) && !lstat(name, &st))
 		return;
 	if (errno == ENOENT)
 		die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
@@ -187,7 +187,7 @@ void verify_non_filename(const char *prefix, const char *arg)
 	if (*arg == '-')
 		return; /* flag */
 	name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
-	if (!lstat(name, &st))
+	if (!outside_sparse_prefix(name) && !lstat(name, &st))
 		die("ambiguous argument '%s': both revision and filename\n"
 		    "Use '--' to separate filenames from revisions", arg);
 	if (errno != ENOENT && errno != ENOTDIR)
@@ -229,6 +229,92 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
 }
 
 /*
+ * if path is inside sparse prefix, leave it as-is
+ * if path is parent of sparse prefix, expand it to sparse prefix
+ * if path is outside sparse prefix, die()
+ */
+static void filter_pathspec_internal(const char *path, const char ***dst, int *dst_alloc, int *dst_nr)
+{
+	char **iprefix;
+	int filtered = 1;
+
+	for (iprefix = get_sparse_prefix(); *iprefix; iprefix++) {
+		const char *new_dst = NULL;
+		if (!prefixcmp(path, *iprefix))
+			new_dst = path;
+		else if (!prefixcmp(*iprefix, path))
+			new_dst = *iprefix;
+
+		if (!new_dst)
+			continue;
+
+		if (*dst_alloc <= *dst_nr) {
+			*dst_alloc = alloc_nr(*dst_alloc);
+			*dst = xrealloc(*dst, *dst_alloc * sizeof(**dst));
+		}
+		(*dst)[(*dst_nr)++] = new_dst;
+		filtered = 0;
+
+		/*
+		 * if it's inside sparse prefix,
+		 * no need to search more
+		 */
+		if (new_dst == path)
+			break;
+	}
+	if (filtered)
+		die("pathspec %s is outside sparse checkout %s", path, get_sparse_prefix_str());
+}
+
+const char **filter_pathspec(const char **pathspec)
+{
+	const char **src, **dst;
+	int dst_alloc = 0;
+	int dst_nr = 0;
+
+	if (!sparse_checkout_enabled())
+		return pathspec;
+	if (!pathspec || !*pathspec)
+		return NULL;
+
+	src = pathspec;
+	dst = NULL;
+	while (*src) {
+		filter_pathspec_internal(*src, &dst, &dst_alloc, &dst_nr);
+		src++;
+	}
+	if (!dst)
+		return NULL;
+	dst[dst_nr] = NULL;
+	return dst;
+}
+
+const char **filter_pathspec_1(const char *path)
+{
+	int dst_alloc = 0, dst_nr = 0;
+	const char **dst = NULL;
+
+	if (!path)
+		return NULL;
+	if (!sparse_checkout_enabled()) {
+		dst = xmalloc(2*sizeof(*dst));
+		dst[0] = path;
+		dst[1] = NULL;
+		return dst;
+	}
+	filter_pathspec_internal(path, &dst, &dst_alloc, &dst_nr);
+	if (!dst)
+		return NULL;
+	dst[dst_nr] = NULL;
+	return dst;
+}
+
+const char **get_filtered_pathspec(const char *prefix, const char **pathspec)
+{
+	return filter_pathspec(get_pathspec(prefix,pathspec));
+}
+
+/*
  * Test if it looks like we're at a git directory.
  * We want to see:
  *
diff --git a/sha1_file.c b/sha1_file.c
index e281c14..60f9e2a 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -2422,6 +2422,9 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write
 	char *target;
 	size_t len;
 
+	if (outside_sparse_prefix(path))
+		die("%s: cannot index a file outside sparse checkout", path);
+
 	switch (st->st_mode & S_IFMT) {
 	case S_IFREG:
 		fd = open(path, O_RDONLY);
diff --git a/unpack-trees.c b/unpack-trees.c
index 0a6723b..0a30d68 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -84,6 +84,19 @@ static void unlink_entry(struct cache_entry *ce)
 	}
 }
 
+static int check_sparse_checkout(struct unpack_trees_options *o)
+{
+	unsigned cnt = 0;
+	struct index_state *index = &o->result;
+
+	for (cnt = 0; cnt < index->cache_nr; cnt++) {
+		struct cache_entry *ce = index->cache[cnt];
+		if (ce_stage(ce) && outside_sparse_prefix(ce->name))
+			return 0;
+	}
+	return 1;
+}
+
 static struct checkout state;
 static int check_updates(struct unpack_trees_options *o)
 {
@@ -96,6 +109,8 @@ static int check_updates(struct unpack_trees_options *o)
 	if (o->update && o->verbose_update) {
 		for (total = cnt = 0; cnt < index->cache_nr; cnt++) {
 			struct cache_entry *ce = index->cache[cnt];
+			if (outside_sparse_prefix(ce->name))
+				continue;
 			if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
 				total++;
 		}
@@ -108,6 +123,8 @@ static int check_updates(struct unpack_trees_options *o)
 	for (i = 0; i < index->cache_nr; i++) {
 		struct cache_entry *ce = index->cache[i];
 
+		if (outside_sparse_prefix(ce->name))
+			continue;
 		if (ce->ce_flags & CE_REMOVE) {
 			display_progress(progress, ++cnt);
 			if (o->update)
@@ -121,6 +138,8 @@ static int check_updates(struct unpack_trees_options *o)
 	for (i = 0; i < index->cache_nr; i++) {
 		struct cache_entry *ce = index->cache[i];
 
+		if (outside_sparse_prefix(ce->name))
+			continue;
 		if (ce->ce_flags & CE_UPDATE) {
 			display_progress(progress, ++cnt);
 			ce->ce_flags &= ~CE_UPDATE;
@@ -411,6 +430,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
 	if (o->check_index_prefix && !check_index_prefix(&o->result, o->merge && !o->prefix))
 		return unpack_failed(o, "Merge outside index prefix");
 
+	if (sparse_checkout_enabled() && !check_sparse_checkout(o))
+		return unpack_failed(o, "Merge conflicts outside sparse checkout");
+
 	o->src_index = NULL;
 	ret = check_updates(o) ? (-2) : 0;
 	if (o->dst_index)
@@ -445,7 +467,7 @@ static int verify_uptodate(struct cache_entry *ce,
 {
 	struct stat st;
 
-	if (o->index_only || o->reset)
+	if (o->index_only || o->reset || outside_sparse_prefix(ce->name))
 		return 0;
 
 	if (!lstat(ce->name, &st)) {
@@ -583,7 +605,7 @@ static int verify_absent(struct cache_entry *ce, const char *action,
 {
 	struct stat st;
 
-	if (o->index_only || o->reset || !o->update)
+	if (o->index_only || o->reset || !o->update || outside_sparse_prefix(ce->name))
 		return 0;
 
 	if (has_symlink_leading_path(ce_namelen(ce), ce->name))
@@ -994,7 +1016,8 @@ int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o)
 		int update = 0;
 		if (o->reset) {
 			struct stat st;
-			if (lstat(old->name, &st) ||
+			if (outside_sparse_prefix(old->name) ||
+			    lstat(old->name, &st) ||
 			    ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID))
 				update |= CE_UPDATE;
 		}
-- 
1.5.5.GIT