X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=merge-tree.c;h=9dcaab7a85fdb63b2140923a95b216917bfc0993;hb=16cee38ae2bff93a4e4c512550fb4ccac035a3a1;hp=00ae4e8d3208e09f2cf7a38202a126f728cadb48;hpb=33deb63a36f523c513cf29598d9c05fe78a23cac;p=git.git diff --git a/merge-tree.c b/merge-tree.c index 00ae4e8d..9dcaab7a 100644 --- a/merge-tree.c +++ b/merge-tree.c @@ -1,128 +1,176 @@ #include "cache.h" +#include "tree-walk.h" -struct tree_entry { - unsigned mode; - unsigned char *sha1; - char *path; - struct tree_entry *next; -}; +static const char merge_tree_usage[] = "git-merge-tree "; +static int resolve_directories = 1; -static struct tree_entry *read_tree(unsigned char *sha1) +static void merge_trees(struct tree_desc t[3], const char *base); + +/* An empty entry never compares same, not even to another empty entry */ +static int same_entry(struct name_entry *a, struct name_entry *b) { - char type[20]; - unsigned long size; - void *buf = read_sha1_file(sha1, type, &size); - struct tree_entry *ret = NULL, **tp = &ret; - - if (!buf || strcmp(type, "tree")) - die("unable to read 'tree' object %s", sha1_to_hex(sha1)); - while (size) { - int len = strlen(buf)+1; - struct tree_entry * entry = malloc(sizeof(struct tree_entry)); - if (size < len + 20 || sscanf(buf, "%o", &entry->mode) != 1) - die("corrupt 'tree' object %s", sha1_to_hex(sha1)); - entry->path = strchr(buf, ' ')+1; - entry->sha1 = buf + len; - entry->next = NULL; - *tp = entry; - tp = &entry->next; - len += 20; - buf += len; - size -= len; - } - return ret; + return a->sha1 && + b->sha1 && + !memcmp(a->sha1, b->sha1, 20) && + a->mode == b->mode; } -static void show(const struct tree_entry *a, const char *path) +static const char *sha1_to_hex_zero(const unsigned char *sha1) { - printf("select %o %s %s%c", a->mode, sha1_to_hex(a->sha1), path, 0); + if (sha1) + return sha1_to_hex(sha1); + return "0000000000000000000000000000000000000000"; } -static void merge(const struct tree_entry *a, const struct tree_entry *b, const struct tree_entry *c, const char *path) +static void resolve(const char *base, struct name_entry *branch1, struct name_entry *result) { - char hex_a[60], hex_b[60], hex_c[60]; - strcpy(hex_a, sha1_to_hex(a->sha1)); - strcpy(hex_b, sha1_to_hex(b->sha1)); - strcpy(hex_c, sha1_to_hex(c->sha1)); - printf("merge %o->%o,%o %s->%s,%s %s%c", - a->mode, b->mode, c->mode, - hex_a, hex_b, hex_c, path, 0); + /* If it's already branch1, don't bother showing it */ + if (!branch1) + return; + + printf("0 %06o->%06o %s->%s %s%s\n", + branch1->mode, result->mode, + sha1_to_hex_zero(branch1->sha1), + sha1_to_hex_zero(result->sha1), + base, result->path); } -static int same(const struct tree_entry *a, const struct tree_entry *b) +static int unresolved_directory(const char *base, struct name_entry n[3]) { - return a->mode == b->mode && !memcmp(a->sha1, b->sha1, 20); + int baselen; + char *newbase; + struct name_entry *p; + struct tree_desc t[3]; + void *buf0, *buf1, *buf2; + + if (!resolve_directories) + return 0; + p = n; + if (!p->mode) { + p++; + if (!p->mode) + p++; + } + if (!S_ISDIR(p->mode)) + return 0; + baselen = strlen(base); + newbase = xmalloc(baselen + p->pathlen + 2); + memcpy(newbase, base, baselen); + memcpy(newbase + baselen, p->path, p->pathlen); + memcpy(newbase + baselen + p->pathlen, "/", 2); + + buf0 = fill_tree_descriptor(t+0, n[0].sha1); + buf1 = fill_tree_descriptor(t+1, n[1].sha1); + buf2 = fill_tree_descriptor(t+2, n[2].sha1); + merge_trees(t, newbase); + + free(buf0); + free(buf1); + free(buf2); + free(newbase); + return 1; } -static void merge_entry(const struct tree_entry *src, const struct tree_entry *dst1, const struct tree_entry *dst2) +static void unresolved(const char *base, struct name_entry n[3]) { - static unsigned char nullsha1[20]; - static const struct tree_entry none = { 0, nullsha1, "", NULL }; - const char *path = NULL; - const struct tree_entry *a, *b, *c; - - a = &none; - b = &none; - c = &none; - if (src) { a = src; path = src->path; } - if (dst1) { b = dst1; path = dst1->path; } - if (dst2) { c = dst2; path = dst2->path; } - if (same(b, c)) { - show(b, path); + if (unresolved_directory(base, n)) return; + if (n[0].sha1) + printf("1 %06o %s %s%s\n", n[0].mode, sha1_to_hex(n[0].sha1), base, n[0].path); + if (n[1].sha1) + printf("2 %06o %s %s%s\n", n[1].mode, sha1_to_hex(n[1].sha1), base, n[1].path); + if (n[2].sha1) + printf("3 %06o %s %s%s\n", n[2].mode, sha1_to_hex(n[2].sha1), base, n[2].path); +} + +/* + * Merge two trees together (t[1] and t[2]), using a common base (t[0]) + * as the origin. + * + * This walks the (sorted) trees in lock-step, checking every possible + * name. Note that directories automatically sort differently from other + * files (see "base_name_compare"), so you'll never see file/directory + * conflicts, because they won't ever compare the same. + * + * IOW, if a directory changes to a filename, it will automatically be + * seen as the directory going away, and the filename being created. + * + * Think of this as a three-way diff. + * + * The output will be either: + * - successful merge + * "0 mode sha1 filename" + * NOTE NOTE NOTE! FIXME! We really really need to walk the index + * in parallel with this too! + * + * - conflict: + * "1 mode sha1 filename" + * "2 mode sha1 filename" + * "3 mode sha1 filename" + * where not all of the 1/2/3 lines may exist, of course. + * + * The successful merge rules are the same as for the three-way merge + * in git-read-tree. + */ +static void threeway_callback(int n, unsigned long mask, struct name_entry *entry, const char *base) +{ + /* Same in both? */ + if (same_entry(entry+1, entry+2)) { + if (entry[0].sha1) { + resolve(base, NULL, entry+1); + return; + } } - if (same(a, b)) { - show(c, path); - return; + + if (same_entry(entry+0, entry+1)) { + if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) { + resolve(base, entry+1, entry+2); + return; + } } - if (same(a, c)) { - show(b, path); - return; + + if (same_entry(entry+0, entry+2)) { + if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) { + resolve(base, NULL, entry+1); + return; + } } - merge(a, b, c, path); + + unresolved(base, entry); } -/* For two entries, select the smaller one, clear the bigger one */ -static void smaller(struct tree_entry **ap, struct tree_entry **bp) +static void merge_trees(struct tree_desc t[3], const char *base) { - struct tree_entry *a = *ap, *b = *bp; - if (a && b) { - int cmp = cache_name_compare(a->path, strlen(a->path), b->path, strlen(b->path)); - if (cmp) { - if (cmp < 0) - *bp = NULL; - else - *ap = NULL; - } - } + traverse_trees(3, t, base, threeway_callback); } -static void merge_tree(struct tree_entry *src, struct tree_entry *dst1, struct tree_entry *dst2) +static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) { - while (src || dst1 || dst2) { - struct tree_entry *a, *b, *c; - a = src; - b = dst1; - c = dst2; - smaller(&a,&b); - smaller(&a,&c); - smaller(&b,&c); - if (a) src = a->next; - if (b) dst1 = b->next; - if (c) dst2 = c->next; - merge_entry(a,b,c); - } + unsigned char sha1[20]; + void *buf; + + if (get_sha1(rev, sha1)) + die("unknown rev %s", rev); + buf = fill_tree_descriptor(desc, sha1); + if (!buf) + die("%s is not a tree", rev); + return buf; } int main(int argc, char **argv) { - unsigned char src[20], dst1[20], dst2[20]; - - if (argc != 4 || - get_sha1_hex(argv[1], src) || - get_sha1_hex(argv[2], dst1) || - get_sha1_hex(argv[3], dst2)) - usage("merge-tree "); - merge_tree(read_tree(src), read_tree(dst1), read_tree(dst2)); + struct tree_desc t[3]; + void *buf1, *buf2, *buf3; + + if (argc < 4) + usage(merge_tree_usage); + + buf1 = get_tree_descriptor(t+0, argv[1]); + buf2 = get_tree_descriptor(t+1, argv[2]); + buf3 = get_tree_descriptor(t+2, argv[3]); + merge_trees(t, ""); + free(buf1); + free(buf2); + free(buf3); return 0; }