Subject: [PATCH 10/12] git-checkout: support --full and --path to manipulate sparse checkout

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

"git checkout --full" will quit sparse checkout mode while
"git checkout --sparse=prefix" will turn on sparse checkout or
update sparse prefix.

twoway_merge has been updated to deal with sparse checkout update.
Files that are in old sparse prefix only will get removed while
files that are in new sparse prefix only will get added.

Signed-off-by: Nguyễn Thái Ngọc Duy <[email-masked]>
---
 builtin-checkout.c |   44 +++++++++++++++++++
 cache.h            |    2 +
 unpack-trees.c     |  120 ++++++++++++++++++++++++++++++++++++++++++++++++----
 unpack-trees.h     |    3 +-
 4 files changed, 160 insertions(+), 9 deletions(-)

diff --git a/builtin-checkout.c b/builtin-checkout.c
index eec1bde..410a53a 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -160,6 +160,9 @@ struct checkout_opts {
 	char *new_branch;
 	int new_branch_log;
 	enum branch_track track;
+
+	char *sparse_prefix;
+	int no_sparse_checkout;
 };
 
 static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree)
@@ -255,7 +258,25 @@ static int merge_working_tree(struct checkout_opts *opts,
 		tree = parse_tree_indirect(new->commit->object.sha1);
 		init_tree_desc(&trees[1], tree->buffer, tree->size);
 
+		if (opts->no_sparse_checkout)
+			topts.new_sparse_prefix = 1;
+
+		if (opts->sparse_prefix) {
+			char **new_sparse_prefix = split_prefix(opts->sparse_prefix);
+
+			if (!new_sparse_prefix) {
+				error("new sparse prefix invalid");
+				return 1;
+			}
+			topts.new_sparse_prefix = 1;
+			topts.unpack_data = new_sparse_prefix;
+		}
+
 		ret = unpack_trees(2, trees, &topts);
+
+		if (topts.new_sparse_prefix && topts.unpack_data)
+			free_prefix_list((char **)topts.unpack_data);
+
 		if (ret == -1) {
 			/*
 			 * Unpack couldn't do a trivial merge; either
@@ -299,6 +320,14 @@ static int merge_working_tree(struct checkout_opts *opts,
 	    commit_locked_index(lock_file))
 		die("unable to write new index file");
 
+	/*
+	 * update sparse checkout accordingly
+	 * show_local_changes will need it
+	 */
+	if (opts->no_sparse_checkout)
+		free_prefix_list(save_sparse_prefix());
+	if (opts->sparse_prefix)
+		set_sparse_prefix(opts->sparse_prefix);
 	if (!opts->force)
 		show_local_changes(&new->commit->object);
 
@@ -409,6 +438,13 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
 
 	update_refs_for_switch(opts, &old, new);
 
+	/* now save new sparse prefix */
+	if (opts->no_sparse_checkout)
+		git_config_set("core.sparsecheckout", NULL);
+
+	if (opts->sparse_prefix)
+		git_config_set("core.sparsecheckout", opts->sparse_prefix);
+
 	ret = post_checkout_hook(old.commit, new->commit, 1);
 	return ret || opts->writeout_error;
 }
@@ -428,6 +464,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			BRANCH_TRACK_EXPLICIT),
 		OPT_BOOLEAN('f', NULL, &opts.force, "force"),
 		OPT_BOOLEAN('m', NULL, &opts.merge, "merge"),
+		OPT_STRING(0, "path", &opts.sparse_prefix, "prefixes", "update sparse checkout"),
+		OPT_BOOLEAN(0, "full", &opts.no_sparse_checkout, "quit sparse checkout"),
 		OPT_END(),
 	};
 
@@ -468,6 +506,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	if (!opts.new_branch && (opts.track != git_branch_track))
 		die("git checkout: --track and --no-track require -b");
 
+	if (opts.no_sparse_checkout && opts.sparse_prefix)
+		die("git checkout: --path and --full are incompatible");
+
 	if (opts.force && opts.merge)
 		die("git checkout: -f and -m are incompatible");
 
@@ -486,6 +527,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			}
 		}
 
+		if (opts.no_sparse_checkout || opts.sparse_prefix)
+			die("git checkout: updating paths is incompatible with setting sparse checkout");
+
 		return checkout_paths(source_tree, pathspec);
 	}
 
diff --git a/cache.h b/cache.h
index b9a1d96..9e7f146 100644
--- a/cache.h
+++ b/cache.h
@@ -138,6 +138,8 @@ struct cache_entry {
 #define CE_HASHED    (0x100000)
 #define CE_UNHASHED  (0x200000)
 
+#define CE_WD_REMOVE (0x400000)
+
 /*
  * Copy the sha1 and stat state of a cache entry from one to
  * another. But we never change the name, or the hash state!
diff --git a/unpack-trees.c b/unpack-trees.c
index 0a30d68..a88c53f 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -105,13 +105,29 @@ static int check_updates(struct unpack_trees_options *o)
 	struct index_state *index = &o->result;
 	int i;
 	int errs = 0;
+	char **old_sparse_prefix = NULL;
+	char **new_sparse_prefix = NULL;
+
+	/*
+	 * for sparse checkout update, we need to widen the prefix a bit
+	 * so that updating in new sparse prefix won't get caught by
+	 * sparse prefix checks
+	 */
+	if (o->new_sparse_prefix) {
+		old_sparse_prefix = save_sparse_prefix();
+		if (o->unpack_data) {
+			new_sparse_prefix = o->unpack_data;
+			new_sparse_prefix = combine_prefix_list(old_sparse_prefix, new_sparse_prefix);
+			restore_sparse_prefix(new_sparse_prefix);
+		}
+	}
 
 	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))
+			if (ce->ce_flags & (CE_UPDATE | CE_REMOVE | CE_WD_REMOVE))
 				total++;
 		}
 
@@ -125,12 +141,14 @@ static int check_updates(struct unpack_trees_options *o)
 
 		if (outside_sparse_prefix(ce->name))
 			continue;
-		if (ce->ce_flags & CE_REMOVE) {
+		if (ce->ce_flags & (CE_REMOVE | CE_WD_REMOVE)) {
 			display_progress(progress, ++cnt);
 			if (o->update)
 				unlink_entry(ce);
-			remove_index_entry_at(&o->result, i);
-			i--;
+			if (ce->ce_flags & CE_REMOVE) {
+				remove_index_entry_at(&o->result, i);
+				i--;
+			}
 			continue;
 		}
 	}
@@ -149,6 +167,10 @@ static int check_updates(struct unpack_trees_options *o)
 		}
 	}
 	stop_progress(&progress);
+	if (o->new_sparse_prefix) {
+		restore_sparse_prefix(old_sparse_prefix);
+		free_prefix_list(new_sparse_prefix);
+	}
 	return errs != 0;
 }
 
@@ -680,8 +702,8 @@ static int verify_absent(struct cache_entry *ce, const char *action,
 	return 0;
 }
 
-static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
-		struct unpack_trees_options *o)
+static int merged_entry_internal(struct cache_entry *merge, struct cache_entry *old,
+		struct unpack_trees_options *o, int set, int clear)
 {
 	int update = CE_UPDATE;
 
@@ -708,10 +730,28 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
 		invalidate_ce_path(merge, o);
 	}
 
-	add_entry(o, merge, update, CE_STAGEMASK);
+	add_entry(o, merge, (update & (~clear)) | set, CE_STAGEMASK);
 	return 1;
 }
 
+static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
+		struct unpack_trees_options *o)
+{
+	return merged_entry_internal(merge, old, o, 0, 0);
+}
+
+static int merged_entry_and_no_add(struct cache_entry *merge, struct cache_entry *old,
+		struct unpack_trees_options *o)
+{
+	return merged_entry_internal(merge, old, o, 0, CE_UPDATE);
+}
+
+static int merged_entry_and_add(struct cache_entry *merge, struct cache_entry *old,
+		struct unpack_trees_options *o)
+{
+	return merged_entry_internal(merge, old, o, CE_UPDATE, 0);
+}
+
 static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
 		struct unpack_trees_options *o)
 {
@@ -734,6 +774,20 @@ static int keep_entry(struct cache_entry *ce, struct unpack_trees_options *o)
 	return 1;
 }
 
+static int keep_entry_and_add(struct cache_entry *ce, struct unpack_trees_options *o)
+{
+	add_entry(o, ce, CE_UPDATE, 0);
+	return 1;
+}
+
+static int keep_entry_and_remove(struct cache_entry *ce, struct unpack_trees_options *o)
+{
+	if (verify_uptodate(ce, o))
+		return -1;
+	add_entry(o, ce, CE_WD_REMOVE, 0);
+	return 1;
+}
+
 #if DBRT_DEBUG
 static void show_stage_entry(FILE *o,
 			     const char *label, const struct cache_entry *ce)
@@ -918,12 +972,31 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
  * over a merge failure when it makes sense.  For details of the
  * "carry forward" rule, please see <Documentation/git-read-tree.txt>.
  *
+ * Two-way merge with sparse prefix update.
+ *
+ * The rules are as same as plain two-way merge, except:
+ *  1) if it's in old sparse checkout, but not the new one, mark it CE_WD_REMOVE
+ *  2) if it's in the new one, but not the old one, mark it CE_UPDATE
+ *  3) otherwise, let old two-way merge take it
+ *
+ *  case 1)
+ *     keep index implies CE_WD_REMOVE (remove workdir only, keep file in index)
+ *     use M implies no CE_UPDATE
+ *     if index is not clean, fail
+ *
+ *  case 2)
+ *     keep index implies CE_UPDATE
+ *     use M implies CE_UPDATE
+ *     index can't be unclean, so ignore this
+ *
  */
 int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o)
 {
 	struct cache_entry *current = src[0];
 	struct cache_entry *oldtree = src[1];
 	struct cache_entry *newtree = src[2];
+	int remove = 0, add = 0;
+	char **new_sparse_prefix = (char **)o->unpack_data;
 
 	if (o->merge_size != 2)
 		return error("Cannot do a twoway merge of %d trees",
@@ -935,6 +1008,13 @@ int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o)
 		newtree = NULL;
 
 	if (current) {
+		if (o->new_sparse_prefix) {
+			remove = !outside_sparse_prefix(current->name) &&
+				 outside_prefix_list(new_sparse_prefix, current->name);
+			add = outside_sparse_prefix(current->name) &&
+			      !outside_prefix_list(new_sparse_prefix, current->name);
+		}
+
 		if ((!oldtree && !newtree) || /* 4 and 5 */
 		    (!oldtree && newtree &&
 		     same(current, newtree)) || /* 6 and 7 */
@@ -943,6 +1023,12 @@ int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o)
 		    (oldtree && newtree &&
 		     !same(oldtree, newtree) && /* 18 and 19 */
 		     same(current, newtree))) {
+			if (o->new_sparse_prefix) {
+				if (remove)
+					return keep_entry_and_remove(current, o);
+				if (add)
+					return keep_entry_and_add(current, o);
+			}
 			return keep_entry(current, o);
 		}
 		else if (oldtree && !newtree && same(current, oldtree)) {
@@ -952,6 +1038,12 @@ int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o)
 		else if (oldtree && newtree &&
 			 same(current, oldtree) && !same(current, newtree)) {
 			/* 20 or 21 */
+			if (o->new_sparse_prefix) {
+				if (remove)
+					return merged_entry_and_no_add(newtree, current, o);
+				if (add)
+					return merged_entry_and_add(newtree, current, o);
+			}
 			return merged_entry(newtree, current, o);
 		}
 		else {
@@ -965,8 +1057,20 @@ int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o)
 			return -1;
 		}
 	}
-	else if (newtree)
+	else if (newtree) {
+		if (o->new_sparse_prefix) {
+			remove = !outside_sparse_prefix(newtree->name) &&
+				outside_prefix_list(new_sparse_prefix, newtree->name);
+			add = outside_sparse_prefix(newtree->name) &&
+				!outside_prefix_list(new_sparse_prefix, newtree->name);
+
+			if (remove)
+				return merged_entry_and_no_add(newtree, current, o);
+			if (add)
+				return merged_entry_and_add(newtree, current, o);
+		}
 		return merged_entry(newtree, current, o);
+	}
 	return deleted_entry(oldtree, current, o);
 }
 
diff --git a/unpack-trees.h b/unpack-trees.h
index a1b46f9..5a29fc4 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -27,7 +27,8 @@ struct unpack_trees_options {
 		     aggressive:1,
 		     skip_unmerged:1,
 		     gently:1,
-		     check_index_prefix:1;
+		     check_index_prefix:1,
+		     new_sparse_prefix:1; /* unpack_data must contain new prefix */
 	const char *prefix;
 	int pos;
 	struct dir_struct *dir;
-- 
1.5.5.GIT