diff options
| -rw-r--r-- | cache.h | 1 | ||||
| -rw-r--r-- | git-resolve-script | 6 | ||||
| -rw-r--r-- | read-tree.c | 123 | 
3 files changed, 107 insertions, 23 deletions
| @@ -84,6 +84,7 @@ struct cache_entry {  #define CE_NAMEMASK  (0x0fff)  #define CE_STAGEMASK (0x3000) +#define CE_UPDATE    (0x4000)  #define CE_STAGESHIFT 12  #define create_ce_flags(len, stage) htons((len) | ((stage) << CE_STAGESHIFT)) diff --git a/git-resolve-script b/git-resolve-script index 4fc7a6dce3..186b234b24 100644 --- a/git-resolve-script +++ b/git-resolve-script @@ -39,14 +39,13 @@ if [ "$common" == "$head" ]; then  	echo "Destroying all noncommitted data!"  	echo "Kill me within 3 seconds.."  	sleep 3 -	git-read-tree -m $merge || exit 1 -	git-checkout-cache -f -u -a +	git-read-tree -u -m $head $merge || exit 1  	echo $merge > "$GIT_DIR"/HEAD  	git-diff-tree -p ORIG_HEAD HEAD | git-apply --stat  	exit 0  fi  echo "Trying to merge $merge into $head" -git-read-tree -m $common $head $merge || exit 1 +git-read-tree -u -m $common $head $merge || exit 1  merge_msg="Merge of $merge_repo"  result_tree=$(git-write-tree  2> /dev/null)  if [ $? -ne 0 ]; then @@ -58,5 +57,4 @@ fi  result_commit=$(echo "$merge_msg" | git-commit-tree $result_tree -p $head -p $merge)  echo "Committed merge $result_commit"  echo $result_commit > "$GIT_DIR"/HEAD -git-checkout-cache -f -u -a  git-diff-tree -p ORIG_HEAD HEAD | git-apply --stat diff --git a/read-tree.c b/read-tree.c index 3746984125..1a575eb0d2 100644 --- a/read-tree.c +++ b/read-tree.c @@ -6,6 +6,7 @@  #include "cache.h"  static int stage = 0; +static int update = 0;  static int unpack_tree(unsigned char *sha1)  { @@ -130,27 +131,29 @@ static void trivially_merge_cache(struct cache_entry **src, int nr)  	struct cache_entry **dst = src;  	struct cache_entry *old = NULL; -	while (nr) { +	while (nr--) {  		struct cache_entry *ce, *result; -		ce = src[0]; +		ce = *src++;  		/* We throw away original cache entries except for the stat information */  		if (!ce_stage(ce)) {  			if (old)  				reject_merge(old);  			old = ce; -			src++; -			nr--;  			active_nr--;  			continue;  		}  		if (old && !path_matches(old, ce))  			reject_merge(old);  		if (nr > 2 && (result = merge_entries(ce, src[1], src[2])) != NULL) { +			result->ce_flags |= htons(CE_UPDATE);  			/*  			 * See if we can re-use the old CE directly?  			 * That way we get the uptodate stat info. +			 * +			 * This also removes the UPDATE flag on +			 * a match.  			 */  			if (old && same(old, result)) {  				*result = *old; @@ -172,42 +175,113 @@ static void trivially_merge_cache(struct cache_entry **src, int nr)  		 */  		CHECK_OLD(ce);  		*dst++ = ce; -		src++; -		nr--;  	}  	if (old)  		reject_merge(old);  } -static void merge_stat_info(struct cache_entry **src, int nr) +/* + * Two-way merge. + * + * The rule is:  + *  - every current entry has to match the old tree + *  - if the current entry matches the new tree, we leave it + *    as-is. Otherwise we require that it be up-to-date. + */ +static void twoway_merge(struct cache_entry **src, int nr)  {  	static struct cache_entry null_entry; +	struct cache_entry *old = NULL, *stat = &null_entry;  	struct cache_entry **dst = src; -	struct cache_entry *old = &null_entry; -	while (nr) { -		struct cache_entry *ce; +	while (nr--) { +		struct cache_entry *ce = *src++; +		int stage = ce_stage(ce); + +		switch (stage) { +		case 0: +			if (old) +				reject_merge(old); +			old = ce; +			stat = ce; +			active_nr--; +			continue; + +		case 1: +			active_nr--; +			if (!old) +				continue; +			if (!path_matches(old, ce) || !same(old, ce)) +				reject_merge(old); +			continue; + +		case 2: +			ce->ce_flags |= htons(CE_UPDATE); +			if (old) { +				if (!path_matches(old, ce)) +					reject_merge(old); +				/* +				 * This also removes the UPDATE flag on +				 * a match +				 */ +				if (same(old, ce)) +					*ce = *old; +				else +					verify_uptodate(old); +				old = NULL; +			} +			ce->ce_flags &= ~htons(CE_STAGEMASK); +			*dst++ = ce; +			continue; +		} +		die("impossible two-way stage"); +	} +	if (old) +		reject_merge(old); +} + +static void merge_stat_info(struct cache_entry **src, int nr) +{ +	static struct cache_entry null_entry; +	struct cache_entry **dst = src; +	struct cache_entry *stat = &null_entry; -		ce = src[0]; +	while (nr--) { +		struct cache_entry *ce = *src++;  		/* We throw away original cache entries except for the stat information */  		if (!ce_stage(ce)) { -			old = ce; -			src++; -			nr--; +			stat = ce;  			active_nr--;  			continue;  		} -		if (path_matches(ce, old) && same(ce, old)) -			*ce = *old; +		if (path_matches(ce, stat) && same(ce, stat)) +			*ce = *stat;  		ce->ce_flags &= ~htons(CE_STAGEMASK);  		*dst++ = ce; -		src++; -		nr--;  	}  } -static char *read_tree_usage = "git-read-tree (<sha> | -m <sha1> [<sha2> <sha3>])"; +static void check_updates(struct cache_entry **src, int nr) +{ +	static struct checkout state = { +		.base_dir = "", +		.force = 1, +		.quiet = 1, +		.refresh_cache = 1, +	}; +	unsigned short mask = htons(CE_UPDATE); +	while (nr--) { +		struct cache_entry *ce = *src++; +		if (ce->ce_flags & mask) { +			ce->ce_flags &= ~mask; +			if (update) +				checkout_entry(ce, &state); +		} +	} +} + +static char *read_tree_usage = "git-read-tree (<sha> | -m <sha1> [<sha2> [<sha3>]])";  int main(int argc, char **argv)  { @@ -228,6 +302,12 @@ int main(int argc, char **argv)  	for (i = 1; i < argc; i++) {  		const char *arg = argv[i]; +		/* "-u" means "update", meaning that a merge will update the working directory */ +		if (!strcmp(arg, "-u")) { +			update = 1; +			continue; +		} +  		/* "-m" stands for "merge", meaning we start in stage 1 */  		if (!strcmp(arg, "-m")) {  			int i; @@ -254,6 +334,11 @@ int main(int argc, char **argv)  		switch (stage) {  		case 4:	/* Three-way merge */  			trivially_merge_cache(active_cache, active_nr); +			check_updates(active_cache, active_nr); +			break; +		case 3:	/* Update from one tree to another */ +			twoway_merge(active_cache, active_nr); +			check_updates(active_cache, active_nr);  			break;  		case 2:	/* Just read a tree, merge with old cache contents */  			merge_stat_info(active_cache, active_nr); | 
