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); |